Можете ли вы округлить объект .NET TimeSpan? - PullRequest
19 голосов
/ 04 декабря 2008

Можете ли вы округлить объект .NET TimeSpan?

У меня есть Timespan значение: 00: 00: 00.6193789

Существует ли простой способ сохранить объект TimeSpan, но округлить его до значения, подобного
00: 00: 00,62

Ответы [ 10 ]

29 голосов
/ 05 января 2010

Извините, ребята, но оба вопрос и популярный ответ пока неверны: -)

Вопрос неправильный, потому что Тиндалл просит путь к раунду , но показывает пример усечения .

Будет ли ответ Дина неверным, поскольку он также обращается к усечению , а не округлению . (Полагаю, можно утверждать, что ответ правильный на один из двух вопросов, но давайте пока оставим философию в стороне ...)

Вот простая техника для округления :

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

2010.01.06 ОБНОВЛЕНИЕ: готовое решение

Приведенный выше материал полезен, если вы ищете идеи; С тех пор у меня было время реализовать упакованное решение для тех, кто ищет готовый код.

Обратите внимание, что это некомментированный код. Полностью прокомментированная версия с 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);
        }
    }
}

2010.02.01 ОБНОВЛЕНИЕ: теперь доступно пакетное решение

Я только что выпустил новую версию своих библиотек с открытым исходным кодом вчера, раньше, чем ожидалось, включая RoundedTimeSpan, который я описал выше. Код здесь ; для API запустите здесь , затем перейдите к RoundedTimeSpan в пространстве имен CleanCode.Data. Библиотека CleanCode.DLL включает в себя код, показанный выше, но предоставляет его в готовом пакете. Обратите внимание, что я немного улучшил метод ToString(int), описанный выше, с тех пор, как опубликовал его в 2010.01.06.

17 голосов
/ 04 декабря 2008

TimeSpan - это не более, чем обертка вокруг элемента 'Ticks'. Довольно просто создать новый TimeSpan из округленной версии другого тика 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
9 голосов
/ 28 сентября 2016

Если вы хотите TimeSpan, его однострочник:

public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
    return TimeSpan.FromTicks( (long)( Math.Round( span.TotalSeconds, nDigits ) * TimeSpan.TicksPerSecond) );
    // A simpler one-liner, if not doing more than 3 digits (TimeSpan.FromSeconds rounds to nearest millisecond):
    //return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}

Если вы хотите строку:

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 );
}

Кредиты:

Ответ cc1960 показывает использование FromSeconds, но он округляется до целых секунд. Мой ответ обобщается до указанного количества цифр.

Ответ Эд предлагает использовать строку формата и содержит ссылку на документ форматирования.


Для округления до кратного некоторой другой единицы, например 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
4 голосов
/ 17 декабря 2016

Учитывая некоторые комментарии о округлении до секунд, я подумал, что округление до любого 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 + 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));
3 голосов
/ 04 декабря 2008
new TimeSpan(tmspan.Hours, tmspan.Minutes, tmspan.Seconds, (int)Math.Round(Convert.ToDouble(tmspan.Milliseconds / 10)));
1 голос
/ 09 июня 2016

Мое решение:

    static TimeSpan RoundToSec(TimeSpan ts)
    {
        return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
    }
1 голос
/ 19 февраля 2016

Вот хороший метод расширения:

    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
1 голос
/ 04 декабря 2008

Не уверен насчет TimeSpan, но вы можете проверить этот пост в DateTimes:
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/

0 голосов
/ 30 июня 2018

Метод расширения, если вам нужно вместо этого работать с DateTime, но все же хотите округлить время. В моем случае я хотел округлить до минуты.

public static DateTime RoundToMinute(this DateTime date)
{
    var roundedTimeSpan = TimeSpan.FromMinutes(Math.Round(date.TimeOfDay.TotalMinutes));
    return new DateTime(date.Year, date.Month, date.Day, roundedTimeSpan.Hours, roundedTimeSpan.Minutes, 0);
}
0 голосов
/ 26 июня 2014

Еще один способ округлить миллисекунды до ближайшей секунды.

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)));
...