Як я можу закріпити час на найближчі Х хвилин?


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
Це рішення якраз перетворило його в мою утиліту як метод розширення.
JYelton

1
Слідкуйте за часом округлення, які знаходяться біля верхньої крайності. Це може спричинити викид виключення, якщо обчислені вами тики перевищують DateTime.MaxValue.Ticks. Будьте в безпеці і приймайте мінімум обчисленого значення та DateTime.MaxValue.Ticks.
Пол Рафф

4
Ви цим способом не втрачаєте інформацію від об’єкта DateTime? Як вид і часовий пояс, якщо вони встановлені?
Evren Kuzucuoglu

11
@ user14 .. (+ d.Ticks - 1) гарантує, що він закруглиться, якщо це необхідно. / І * заокруглюються. Приклад раунду 12 до наступних 5: (12 + 5 - 1) = 16,
16/5

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

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 мс на моїй машині, тоді як метод мульти / діва займав ~ 500 мс. Я думаю, що ФПУ старі проблеми не викликають. Ось мій тестовий код: pastie.org/8610460
viggity

1
Велике використання розширень. Дякую!
ТревісВідкритий

1
@Alovchin Дякую Я оновив відповідь. Я створив цей ідеон з вашим кодом, щоб показати різницю: 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 має єдиний пост, який правильно показує, як округнути до найближчого інтервалу внизу: (за іронією долі, коли це питання було
якнайкраще

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 до визначеного збільшення часу (проміжок часу):

  • Перетворіть 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) .Minute * 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

Ви можете використовувати цей метод, він використовує вказану дату, щоб переконатися, що він підтримує будь-який тип глобалізації та дати, раніше вказаний в об'єкті date date.

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.