Как я могу округлить время до ближайших X минут?


160

Есть простая функция для округления UPDateTime до ближайших 15 минут?

Например

2011-08-11 16:59 становится 2011-08-11 17:00

2011-08-11 17:00 остается как 2011-08-11 17:00

2011-08-11 17:01 становится 2011-08-11 17:15

Ответы:


287
DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Kind);
}

Пример:

var dt1 = RoundUp(DateTime.Parse("2011-08-11 16:59"), TimeSpan.FromMinutes(15));
// dt1 == {11/08/2011 17:00:00}

var dt2 = RoundUp(DateTime.Parse("2011-08-11 17:00"), TimeSpan.FromMinutes(15));
// dt2 == {11/08/2011 17:00:00}

var dt3 = RoundUp(DateTime.Parse("2011-08-11 17:01"), TimeSpan.FromMinutes(15));
// dt3 == {11/08/2011 17:15:00}

13
Это решение только что вошло в мою служебную библиотеку как метод расширения.
Джелтон

1
Следите за временем округления, близким к верхнему пределу. Это может вызвать исключение, если рассчитанные тики превышают DateTime.MaxValue.Ticks. Будьте в безопасности и возьмите минимум вашего расчетного значения и DateTime.MaxValue.Ticks.
Пол Рафф

4
Вы не теряете информацию из объекта DateTime с этим методом? Понравился вид и часовой пояс, если там установлены?
Эврен Кузукуоглу

11
@ user14 .. (+ d.Ticks - 1) гарантирует, что при необходимости округлится. / И * округляются. Пример раунда 12 до следующих 5: (12 + 5 - 1) = 16, 16/5 = 3 (потому что это целочисленный тип данных), 3 * 5 = 15. Тада :)
Диего Френер

12
@dtb одно небольшое дополнение, в противном случае оно, вероятно, немного ошибочно: вам нужно сохранить DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
дату и время в порядке

107

Придумали решение, которое не предполагает умножения и деления long чисел.

public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
    var modTicks = dt.Ticks % d.Ticks;
    var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
    return new DateTime(dt.Ticks + delta, dt.Kind);
}

public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta, dt.Kind);
}

public static DateTime RoundToNearest(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    bool roundUp = delta > d.Ticks / 2;
    var offset = roundUp ? d.Ticks : 0;

    return new DateTime(dt.Ticks + offset - delta, dt.Kind);
}

Использование:

var date = new DateTime(2010, 02, 05, 10, 35, 25, 450); // 2010/02/05 10:35:25
var roundedUp = date.RoundUp(TimeSpan.FromMinutes(15)); // 2010/02/05 10:45:00
var roundedDown = date.RoundDown(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00
var roundedToNearest = date.RoundToNearest(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00

8
Я точно думал, что это будет быстрее, чем умножение и деление, но мои тесты показывают, что это не так. Это более 10000000 итераций, метод модуля занял ~ 610 мс на моей машине, в то время как метод mult / div занял ~ 500 мс. Я полагаю, что FPU не заботятся о старых проблемах. Вот мой тестовый код: pastie.org/8610460
viggity

1
Отличное использование расширений. Спасибо!
TravisWhidden

1
@ Alovchin Спасибо. Я обновил ответ. Я создал этот ideone с вашим кодом, чтобы показать разницу: ideone.com/EVKFp5
redent84

1
Это довольно старый, но последний %d.Ticksв RoundUpнеобходимости? d.Ticks - (dt.Ticks % d.Ticks))будет обязательно меньше d.Ticks, поэтому ответ должен быть таким же правильным?
Нейт Даймонд

1
Просто отметим, что модуль требует операции деления на процессоре. Но я согласен, что это более элегантно, чем использовать свойство разбивки целочисленных делений.
Алекс

19

если вам нужно округлить до ближайшего временного интервала (не вверх), то я предлагаю использовать следующее

    static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
    {
        int f=0;
        double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
        if (m >= 0.5)
            f=1;            
        return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
    }

Этот ответ не округляется правильно. У user1978424 есть единственная запись, которая правильно показывает, как округлить до ближайшего интервала ниже: (по иронии судьбы, потому что вопрос был округлен вверх)
stitty

8
void Main()
{
    var date1 = new DateTime(2011, 8, 11, 16, 59, 00);
    date1.Round15().Dump();

    var date2 = new DateTime(2011, 8, 11, 17, 00, 02);
    date2.Round15().Dump();

    var date3 = new DateTime(2011, 8, 11, 17, 01, 23);
    date3.Round15().Dump();

    var date4 = new DateTime(2011, 8, 11, 17, 00, 00);
    date4.Round15().Dump();
}

public static class Extentions
{
    public static DateTime Round15(this DateTime value)
    {   
        var ticksIn15Mins = TimeSpan.FromMinutes(15).Ticks;

        return (value.Ticks % ticksIn15Mins == 0) ? value : new DateTime((value.Ticks / ticksIn15Mins + 1) * ticksIn15Mins);
    }
}

Полученные результаты:

8/11/2011 5:00:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:00:00 PM

3
2011-08-11 17:00:01получает обрезается до2011-08-11 17:00:00
JYelton

1
@JYelton: Спасибо за указание +1. Я изменил свой код, чтобы учесть это.
Влад Безден

Предоставление кода в формате Linqpad для простой проверки - это отличная экономия времени. Очень прост в использовании.
Адам Гарнер

6

Так как я ненавижу изобретать велосипед, я, вероятно, следую этому алгоритму, чтобы округлить значение DateTime до указанного приращения времени (Timespan):

  • Преобразуйте DateTimeокругляемое значение в десятичное значение с плавающей запятой, представляющее целое и дробное число TimeSpanединиц.
  • Округлите его до целого числа, используя Math.Round().
  • Вернитесь к тикам, умножив округленное целое число на количество тиков в TimeSpanединице.
  • Создайте новое DateTimeзначение из округленного числа тиков и верните его вызывающей стороне.

Вот код:

public static class DateTimeExtensions
{

    public static DateTime Round( this DateTime value , TimeSpan unit )
    {
        return Round( value , unit , default(MidpointRounding) ) ;
    }

    public static DateTime Round( this DateTime value , TimeSpan unit , MidpointRounding style )
    {
        if ( unit <= TimeSpan.Zero ) throw new ArgumentOutOfRangeException("unit" , "value must be positive") ;

        Decimal  units        = (decimal) value.Ticks / (decimal) unit.Ticks ;
        Decimal  roundedUnits = Math.Round( units , style ) ;
        long     roundedTicks = (long) roundedUnits * unit.Ticks ;
        DateTime instance     = new DateTime( roundedTicks ) ;

        return instance ;
    }

}

Это хороший код для округления до ближайшего DateTime , но я также хочу возможность округлять до нескольких unit . Переход MidpointRounding.AwayFromZeroк Roundне имеет желаемого эффекта. Есть ли у вас что-то еще, принимая MidpointRoundingаргумент?
HappyNomad

2

Моя версия

DateTime newDateTimeObject = oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);

Как метод, он будет блокировать, как это

public static DateTime GetNextQuarterHour(DateTime oldDateTimeObject)
{
    return oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);
}

и называется так

DateTime thisIsNow = DateTime.Now;
DateTime nextQuarterHour = GetNextQuarterHour(thisIsNow);

это не учитывает секунды
Алекс Норклифф

1

Элегантный?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)

1
Более правильная версия: x.AddSeconds (900 - (x.AddSeconds (-1). Минуты * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), который заботится о условие "пребывания".
Олаф

1

Осторожно: приведенная выше формула неверна, то есть следующее:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks);
}

должен быть переписан как:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks/2) / d.Ticks) * d.Ticks);
}

1
Я не согласен. Поскольку целочисленное деление / d.Ticksокругляется до ближайшего 15-минутного интервала (давайте назовем эти «блоки»), добавление только половины блока не гарантирует округления в большую сторону. Рассмотрим, когда у вас есть 4,25 блоков. Если вы добавите 0,5 блока, а затем проверьте, сколько у вас целочисленных блоков, у вас останется только 4. Добавление правильного действия на один тик меньше полного блока. Он гарантирует, что вы всегда переходите к следующему диапазону блоков (до округления вниз), но предотвращает перемещение между точными блоками. (IE, если вы добавили полный блок к 4,0 блокам, 5,0 округлились бы до 5, когда вы хотите, чтобы 4,99 были 4).
Брендан Мур

1

Более подробное решение, которое использует модуль и избегает ненужных вычислений.

public static class DateTimeExtensions
{
    public static DateTime RoundUp(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, true);
    }

    public static DateTime RoundDown(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, false);
    }

    private static DateTime Round(DateTime dt, TimeSpan ts, bool up)
    {
        var remainder = dt.Ticks % ts.Ticks;
        if (remainder == 0)
        {
            return dt;
        }

        long delta;
        if (up)
        {
            delta = ts.Ticks - remainder;
        }
        else
        {
            delta = -remainder;
        }

        return dt.AddTicks(delta);
    }
}

0

Это простое решение для округления до ближайшей 1 минуты. Он сохраняет информацию о TimeZone и Kind для DateTime. Его можно изменить в соответствии с вашими потребностями (если вам нужно округлить до ближайших 5 минут и т. Д.).

DateTime dbNowExact = DateTime.Now;
DateTime dbNowRound1 = (dbNowExact.Millisecond == 0 ? dbNowExact : dbNowExact.AddMilliseconds(1000 - dbNowExact.Millisecond));
DateTime dbNowRound2 = (dbNowRound1.Second == 0 ? dbNowRound1 : dbNowRound1.AddSeconds(60 - dbNowRound1.Second));
DateTime dbNow = dbNowRound2;

0

Вы можете использовать этот метод, он использует указанную дату, чтобы гарантировать, что он поддерживает любой тип глобализации и тип datetime, ранее указанный в объекте datetime.

const long LNG_OneMinuteInTicks = 600000000;
/// <summary>
/// Round the datetime to the nearest minute
/// </summary>
/// <param name = "dateTime"></param>
/// <param name = "numberMinutes">The number minute use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, int numberMinutes = 1)
{
    long roundedMinutesInTicks = LNG_OneMinuteInTicks * numberMinutes;
    long remainderTicks = dateTime.Ticks % roundedMinutesInTicks;
    if (remainderTicks < roundedMinutesInTicks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundedMinutesInTicks - remainderTicks);
}

.Net Fiddle Test

Если вы хотите использовать TimeSpan для округления, вы можете использовать это.

/// <summary>
/// Round the datetime
/// </summary>
/// <example>Round(dt, TimeSpan.FromMinutes(5)); => round the time to the nearest 5 minutes.</example>
/// <param name = "dateTime"></param>
/// <param name = "roundBy">The time use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, TimeSpan roundBy)
{            
    long remainderTicks = dateTime.Ticks % roundBy.Ticks;
    if (remainderTicks < roundBy.Ticks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundBy.Ticks - remainderTicks);
}

TimeSpan Fiddle


Что произойдет, если вы хотите округлить до ближайшей 7-й минуты var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// должно быть 9:42, но ни один из этих методов не работает так?
DotnetShadow

Правка выглядит так, будто ответ @soulflyman даст правильный результат
DotnetShadow
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.