Можете ли вы обойти объект .NET TimeSpan
?
У меня есть значение Timespan
: 00:00:00.6193789
Есть ли простой способ сохранить его как объект TimeSpan
, но округлить его до чего-то вроде
00:00:00.62?
Можете ли вы обойти объект .NET TimeSpan
?
У меня есть значение Timespan
: 00:00:00.6193789
Есть ли простой способ сохранить его как объект TimeSpan
, но округлить его до чего-то вроде
00:00:00.62?
Извините, ребята, но и вопрос, и популярный ответ до сих пор неверны :-)
Вопрос неправильный, потому что Тиндалл запрашивает способ округлить, но показывает пример усечения.
Ответ Уилла Дина неверен, поскольку он также касается усечения, а не округления. (Полагаю, можно утверждать, что ответ правильный на один из двух вопросов, но давайте пока оставим философию в стороне...)
Вот простой способ округления:
int precision = 2; // Specify how many digits past the decimal point
TimeSpan t1 = new TimeSpan(19365678); // sample input value
const int TIMESPAN_SIZE = 7; // it always has seven digits
// convert the digitsToShow into a rounding/truncating mask
int factor = (int)Math.Pow(10,(TIMESPAN_SIZE - precision));
Console.WriteLine("Input: " + t1);
TimeSpan truncatedTimeSpan = new TimeSpan(t1.Ticks - (t1.Ticks % factor));
Console.WriteLine("Truncated: " + truncatedTimeSpan);
TimeSpan roundedTimeSpan =
new TimeSpan(((long)Math.Round((1.0*t1.Ticks/factor))*factor));
Console.WriteLine("Rounded: " + roundedTimeSpan);
С входным значением и количеством цифр в образце кода это результат:
Input: 00:00:01.9365678
Truncated: 00:00:01.9300000
Rounded: 00:00:01.9400000
Измените точность с 2 цифр на 5 цифр и вместо этого получите следующее:
Input: 00:00:01.9365678
Truncated: 00:00:01.9365600
Rounded: 00:00:01.9365700
И даже измените его на 0, чтобы получить такой результат:
Input: 00:00:01.9365678
Truncated: 00:00:01
Rounded: 00:00:02
Наконец, если вы хотите чуть больше контроля над выводом, добавьте немного форматирования. Вот один пример, показывающий, что вы можете отделить точность от количества отображаемых цифр. Точность снова установлена на 2, но отображаются 3 цифры, как указано в последнем аргументе строки управления форматированием:
Console.WriteLine("Rounded/formatted: " +
string.Format("{0:00}:{1:00}:{2:00}.{3:000}",
roundedTimeSpan.Hours, roundedTimeSpan.Minutes,
roundedTimeSpan.Seconds, roundedTimeSpan.Milliseconds));
// Input: 00:00:01.9365678
// Truncated: 00:00:01.9300000
// Rounded: 00:00:01.9400000
// Rounded/formatted: 00:00:01.940
Приведенный выше материал полезен, если вы ищете идеи; С тех пор у меня было время реализовать пакетное решение для тех, кто ищет готовый к использованию код.
Обратите внимание, что это раскомментированный код. Полностью прокомментированная версия с комментариями XML-doc будет доступна в моей библиотеке с открытым исходным кодом к концу квартала. . Хотя я не решался публиковать его «сырым» в таком виде, я полагаю, что он все же может быть полезен заинтересованным читателям.
Этот код улучшает мой код выше, который, хотя и округлен, по-прежнему показывает 7 разрядов, дополненных нулями. Эта готовая версия округляется и обрезается до указанного количества цифр.
Вот пример вызова:
Console.Write(new RoundedTimeSpan(19365678, 2).ToString());
// Result = 00:00:01.94
А вот и полный файл RoundedTimeSpan.cs:
using System;
namespace CleanCode.Data
{
public struct RoundedTimeSpan
{
private const int TIMESPAN_SIZE = 7; // it always has seven digits
private TimeSpan roundedTimeSpan;
private int precision;
public RoundedTimeSpan(long ticks, int precision)
{
if (precision < 0) { throw new ArgumentException("precision must be non-negative"); }
this.precision = precision;
int factor = (int)System.Math.Pow(10, (TIMESPAN_SIZE - precision));
// This is only valid for rounding milliseconds-will *not* work on secs/mins/hrs!
roundedTimeSpan = new TimeSpan(((long)System.Math.Round((1.0 * ticks / factor)) * factor));
}
public TimeSpan TimeSpan { get { return roundedTimeSpan; } }
public override string ToString()
{
return ToString(precision);
}
public string ToString(int length)
{ // this method revised 2010.01.31
int digitsToStrip = TIMESPAN_SIZE - length;
string s = roundedTimeSpan.ToString();
if (!s.Contains(".") && length == 0) { return s; }
if (!s.Contains(".")) { s += "." + new string('0', TIMESPAN_SIZE); }
int subLength = s.Length - digitsToStrip;
return subLength < 0 ? "" : subLength > s.Length ? s : s.Substring(0, subLength);
}
}
}
Я только вчера выпустил новую версию своих библиотек с открытым исходным кодом, раньше, чем ожидалось, включая RoundedTimeSpan, о котором я говорил выше. Код находится здесь; для API начните здесь, затем перейдите к RoundedTimeSpan
в пространстве имен CleanCode.Data
. Библиотека CleanCode.DLL включает показанный выше код, но предоставляет его в готовом пакете. Обратите внимание, что я сделал небольшое улучшение в методе ToString(int)
выше, так как я опубликовал его 2010.01.06.
TimeSpan — это не более чем оболочка для члена «Ticks». Довольно легко создать новый TimeSpan из округленной версии Ticks другого TimeSpan.
TimeSpan t1 = new TimeSpan(2345678);
Console.WriteLine(t1);
TimeSpan t2 = new TimeSpan(t1.Ticks - (t1.Ticks % 100000));
Console.WriteLine(t2);
Дает:
00:00:00.2345678
00:00:00.2300000
Самый простой однострочный вариант, если вы округляете до целых секунд:
public static TimeSpan RoundSeconds( TimeSpan span ) {
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds ) );
}
Чтобы округлить до трех цифр (например, до десятых, сотых долей секунды или миллисекунд):
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
// TimeSpan.FromSeconds rounds to nearest millisecond, so nDigits should be 3 or less - won't get good answer beyond 3 digits.
Debug.Assert( nDigits <= 3 );
return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}
Для более чем 3 цифр это немного сложнее, но все же однострочник. Это также можно использовать для 3 или менее цифр - это замена версии, показанной выше:
public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
return TimeSpan.FromTicks( (long)( Math.Round( span.TotalSeconds, nDigits ) * TimeSpan.TicksPerSecond) );
}
Если вам нужна строка (согласно комментарию, этот метод работает только до 7 цифр):
public static string RoundSecondsAsString( TimeSpan span, int nDigits ) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nDigits; i++)
sb.Append( "f" );
return span.ToString( @"hh\:mm\:ss\." + sb );
}
Для времени дня в виде часов и минут с округлением:
public static TimeSpan RoundMinutes(TimeSpan span)
{
return TimeSpan.FromMinutes(Math.Round(span.TotalMinutes));
}
Если у вас есть DateTime и вы хотите извлечь время дня, округленное:
DateTime dt = DateTime.Now();
TimeSpan hhmm = RoundMinutes(dt.TimeOfDay);
Чтобы отобразить округленное 24-часовое время:
string hhmmStr = RoundMinutes(dt.TimeOfDay).ToString(@"hh\:mm");
Чтобы отобразить время суток в текущей культуре:
string hhmmStr = new DateTime().Add(RoundMinutes(dt.TimeOfDay)).ToShortTimeString();
Кредиты:
Ответ cc1960 показывает использование FromSeconds, но он округлил до целых секунд. Мой ответ обобщается до указанного количества цифр.
Ответ Эда предлагает использовать строку формата и включает ссылку на документ форматирования.
Крис Марисик показывает, как применить ToShortTimeString
к TimeSpan (сначала преобразовав в DateTime
).
Чтобы округлить до кратности какой-либо другой единицы, например 1/30 секунды:
// Rounds span to multiple of "unitInSeconds".
// NOTE: This will be close to the requested multiple,
// but is not exact when unit cannot be exactly represented by a double.
// e.g. "unitInSeconds = 1/30" isn't EXACTLY 1/30,
// so the returned value won't be exactly a multiple of 1/30.
public static double RoundMultipleAsSeconds( TimeSpan span, double unitInSeconds )
{
return unitInSeconds * Math.Round( span.TotalSeconds / unitInSeconds );
}
public static TimeSpan RoundMultipleAsTimeSpan( TimeSpan span, double unitInSeconds )
{
return TimeSpan.FromTicks( (long)(RoundMultipleAsSeconds( span, unitInSeconds ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundMultipleAsSeconds( span, unitInSeconds ) );
}
// Rounds "span / n".
// NOTE: This version might be a hair closer in some cases,
// but probably not enough to matter, and can only represent units that are "1 / N" seconds.
public static double RoundOneOverNAsSeconds( TimeSpan span, double n )
{
return Math.Round( span.TotalSeconds * n ) / n;
}
public static TimeSpan RoundOneOverNAsTimeSpan( TimeSpan span, double n )
{
return TimeSpan.FromTicks( (long)(RoundOneOverNAsSeconds( span, n ) * TimeSpan.TicksPerSecond) );
// IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
//return TimeSpan.FromSeconds( RoundOneOverNAsSeconds( span, n ) );
}
Чтобы использовать один из них для округления до кратных 1/30 секунды:
private void Test()
{
long ticks = (long) (987.654321 * TimeSpan.TicksPerSecond);
TimeSpan span = TimeSpan.FromTicks( ticks );
TestRound( span, 30 );
TestRound( TimeSpan.FromSeconds( 987 ), 30 );
}
private static void TestRound(TimeSpan span, int n)
{
var answer1 = RoundMultipleAsSeconds( span, 1.0 / n );
var answer2 = RoundMultipleAsTimeSpan( span, 1.0 / n );
var answer3 = RoundOneOverNAsSeconds( span, n );
var answer4 = RoundOneOverNAsTimeSpan( span, n );
}
Результаты, просмотренные в отладчике:
// for 987.654321 seconds:
answer1 987.66666666666663 double
answer2 {00:16:27.6666666} System.TimeSpan
answer3 987.66666666666663 double
answer4 {00:16:27.6666666} System.TimeSpan
// for 987 seconds:
answer1 987 double
answer2 {00:16:27} System.TimeSpan
answer3 987 double
answer4 {00:16:27} System.TimeSpan
public static TimeSpan RoundSeconds(this TimeSpan span, int nDigits = 0)
- person NetMage; 17.12.2016
RoundSecondsAsString
: помните, что максимальное количество цифр (f
), которое вы можете добавить, равно 7! Также в конце будет потерянная десятичная точка, если nDigits == 0
- person marsze; 27.12.2018
Учитывая некоторые комментарии об округлении до секунд, я подумал, что округление до любого TimeSpan было бы неплохо:
public static TimeSpan Round(this TimeSpan ts, TimeSpan rnd) {
if (rnd == TimeSpan.Zero)
return ts;
else {
var rndTicks = rnd.Ticks;
var ansTicks = ts.Ticks + Math.Sign(ts.Ticks) * rndTicks / 2;
return TimeSpan.FromTicks(ansTicks - ansTicks % rndTicks);
}
}
public static TimeSpan Round(this TimeSpan ts) => ts.Round(TimeSpan.FromSeconds(1));
Учитывая потенциальные неточности округления до тиков при работе с дробными единицами (согласно @ToolmakerSteve), я добавляю опцию дробного округления, когда вам нужна более высокая точность и округляются до долей секунды компьютера:
public static TimeSpan RoundToFraction(this TimeSpan ts, long num, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)Math.Round(Math.Round((double)ts.Ticks * (double)den / num / TimeSpan.TicksPerSecond) * (double)num / den * TimeSpan.TicksPerSecond));
public static TimeSpan RoundToFraction(this TimeSpan ts, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)(Math.Round((double)ts.Ticks * den / TimeSpan.TicksPerSecond) / den * TimeSpan.TicksPerSecond));
rnd
. Таким образом, ошибка в 1/30 секунды не имеет значения практически для любого использования. Чем меньше становится rnd
, тем больше это может стать проблемой. Я просто хотел отметить тот факт, что здесь есть потенциальная неточность, которая происходит из-за того, что TimeSpan не является значением с плавающей запятой, поэтому становится более неточным по мере того, как значения становятся меньше. (Решением в таких случаях было бы не пытаться представить rnd
как TimeSpan
; вместо этого использовать количество секунд с плавающей запятой.)
- person ToolmakerSteve; 05.08.2017
TimeSpan
, составляет один тик, не означает ли это, что вы не можете равномерно округлить TimeSpan
до 1/30 секунды, независимо от того, как вы выполняете математику?
- person NetMage; 07.08.2017
TimeSpan.FromSeconds
указано с точностью до миллисекунды (!).
- person NetMage; 03.01.2019
FromSeconds
- он точен только до миллисекунды. Я написал свой собственный, который преобразуется в тики, а затем в TimeSpan
.
- person NetMage; 04.01.2019
FromTicks
.
- person NetMage; 04.01.2019
TimeSpan.FromDays(-4).Round(TimeSpan.FromDays(1))
приводит к -3 дням.
- person Gyromite; 17.03.2020
TimeSpan
.
- person NetMage; 17.03.2020
Не уверен насчет TimeSpan, но вы можете проверить этот пост на DateTimes:
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/
Вот хороший метод расширения:
public static TimeSpan RoundToSeconds(this TimeSpan timespan, int seconds = 1)
{
long offset = (timespan.Ticks >= 0) ? TimeSpan.TicksPerSecond / 2 : TimeSpan.TicksPerSecond / -2;
return TimeSpan.FromTicks((timespan.Ticks + offset) / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond);
}
И вот несколько примеров:
DateTime dt1 = DateTime.Now.RoundToSeconds(); // round to full seconds
DateTime dt2 = DateTime.Now.RoundToSeconds(5 * 60); // round to full 5 minutes
Мое решение:
static TimeSpan RoundToSec(TimeSpan ts)
{
return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
}
Еще один способ округлить миллисекунды до ближайшей секунды.
private const long TicksPer1000Milliseconds = 1000 * TimeSpan.TicksPerMillisecond;
// Round milliseconds to nearest second
// To round up, add the sub-second ticks required to reach the next second
// To round down, subtract the sub-second ticks
elapsedTime = new TimeSpan(elapsedTime.Ticks + (elapsedTime.Milliseconds >= 500 ? TicksPer1000Milliseconds - (elapsedTime.Ticks % TicksPer1000Milliseconds) : -(elapsedTime.Ticks % TicksPer1000Milliseconds)));
Метод расширения, если вам нужно вместо этого работать с DateTime
, но вы все равно хотите округлить время. В моем случае я хотел округлить до минуты.
public static DateTime RoundToMinute(this DateTime date)
{
return DateTime.MinValue.AddMinutes(Math.Round((date - DateTime.MinValue).TotalMinutes));
}