Jak mogę zaokrąglić czas do najbliższych X minut?


160

Czy istnieje prosty funkcja zaokrąglania UPDateTime do najbliższych 15 minut?

Na przykład

2011-08-11 16:59 staje się 2011-08-11 17:00

2011-08-11 17:00 pozostaje jako 2011-08-11 17:00

2011-08-11 17:01 staje się 2011-08-11 17:15

Odpowiedzi:


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

Przykład:

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
To rozwiązanie właśnie trafiło do mojej biblioteki narzędziowej jako metoda rozszerzenia.
— JYelton,

1
Uważaj na czasy zaokrągleń, które są bliskie górnej skrajności. Może to spowodować zgłoszenie wyjątku, jeśli obliczone znaczniki są większe niż DateTime.MaxValue.Ticks. Bądź bezpieczny i weź minimum obliczonej wartości i DateTime.MaxValue.Ticks.
— Paul Raff

4
Czy nie tracisz informacji z obiektu DateTime za pomocą tej metody? Jak rodzaj i strefa czasowa, jeśli są ustawione?
— Evren Kuzucuoglu,

11
@ user14 .. (+ d.Ticks - 1) upewnia się, że w razie potrzeby zostanie zaokrąglony w górę. / I * są zaokrąglane. Przykładowa runda od 12 do następnych 5: (12 + 5 - 1) = 16, 16/5 = 3 (ponieważ jest to typ danych całkowitoliczbowych), 3 * 5 = 15. tada :)
— Diego Frehner

12
@dtb jeden mały dodatek, w przeciwnym razie prawdopodobnie jest trochę zbugowany: musisz zachować rodzaj DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
— daty i godziny

107

Pojawiło się rozwiązanie, które nie wymaga mnożenia i dzielenia long liczb.

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

Stosowanie:

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
Pomyślałem na pewno, że będzie to szybsze niż użycie mnożenia i dzielenia, ale moje testy pokazują, że tak nie jest. To ponad 10000000 iteracji, metoda modułu zajęła na moim komputerze ~ 610 ms, a metoda mult / div ~ 500 ms. Myślę, że FPU sprawiają, że stare obawy nie są problemem. Oto mój kod testowy: pastie.org/8610460
— viggity

1
Świetne wykorzystanie rozszerzeń. Dzięki!
— TravisWhidden

1
@Alovchin Thanks. Zaktualizowałem odpowiedź. Stworzyłem ten ideone z Twoim kodem, aby pokazać różnicę: ideone.com/EVKFp5
— redent84

1
To jest dość stary, ale jest ostatnią %d.Ticksw RoundUpniezbędne? d.Ticks - (dt.Ticks % d.Ticks))będzie koniecznie mniejsze niż d.Ticks, więc odpowiedź powinna być taka sama, poprawna?
— Nate Diamond

1
Zwracam uwagę, że moduł wymaga operacji podziału na procesorze. Ale zgadzam się, że jest to bardziej eleganckie niż użycie właściwości rouding down dzielenia liczb całkowitych.
— Alex

19

jeśli chcesz zaokrąglić do najbliższego przedziału czasu (nie w górę), sugeruję użycie poniższego

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

Ta odpowiedź nie zaokrągla się poprawnie. user1978424 ma jedyny post, który pokazuje poprawnie, jak zaokrąglić do najbliższego przedziału poniżej: (ironicznie głosowano w dół, ponieważ pytanie było zaokrąglane w górę)
— 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);
    }
}

Wyniki:

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:01zostaje obcięty do2011-08-11 17:00:00
— JYelton

1
@JYelton: Dzięki za wskazanie +1. Zmieniłem kod, żeby to dostosować.
— Vlad Bezden,

Zapewnienie kodu w formacie Linqpad w celu łatwej weryfikacji to świetna oszczędność czasu. Bardzo łatwy w użyciu.
— Adam Garner,

6

Ponieważ nienawidzę odkrywać koła na nowo, prawdopodobnie zastosowałbym ten algorytm, aby zaokrąglić wartość DateTime do określonego przyrostu czasu (Timespan):

  • Przekonwertuj DateTimewartość, która ma być zaokrąglona, ​​na dziesiętną wartość zmiennoprzecinkową reprezentującą całkowitą i ułamkową liczbę TimeSpanjednostek.
  • Zaokrąglij to do liczby całkowitej, używając Math.Round().
  • Skaluj z powrotem do taktów, mnożąc zaokrągloną liczbę całkowitą przez liczbę taktów w TimeSpanjednostce.
  • Utwórz wystąpienie nowej DateTimewartości z zaokrąglonej liczby taktów i zwróć ją do dzwoniącego.

Oto kod:

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

}

To dobry kod do zaokrąglania do najbliższej DateTime , ale chcę też mieć możliwość zaokrąglania w górę do wielokrotności unit . Przechodzenie MidpointRounding.AwayFromZerodo Roundnie daje pożądanego efektu. Czy przyjmując MidpointRoundingargument , masz na myśli coś innego ?
— HappyNomad

2

Moja wersja

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

Jako metoda blokowałby się w ten sposób

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

i tak się nazywa

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

to nie oznacza sekund
— Alex Norcliffe,

1

Elegancki?

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

1
Bardziej poprawną wersją byłoby: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), który zajmuje się warunek „pozostaje”.
— Olaf,

1

Uwaga: powyższy wzór jest niepoprawny, czyli:

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

należy przepisać jako:

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

1
Nie zgadzam się. Ponieważ dzielenie liczb całkowitych jest / d.Tickszaokrąglane w dół do najbliższego 15-minutowego przedziału (nazwijmy te „bloki”), dodanie tylko połowy bloku nie gwarantuje zaokrąglenia w górę. Zastanów się, kiedy masz 4,25 bloków. Jeśli dodasz 0,5 bloku, a następnie sprawdź, ile masz bloków całkowitych, nadal masz tylko 4. Dodanie jednego ticka mniej niż pełny blok jest działaniem poprawnym. Zapewnia, że ​​zawsze przechodzisz w górę do następnego zakresu bloków (przed zaokrągleniem w dół), ale zapobiega przechodzeniu między dokładnymi blokami. (IE, jeśli dodałeś pełny blok do 4.0 bloków, 5.0 zaokrągliłoby do 5, kiedy chcesz 4. 4.99 będzie 4.)
— Brendan Moore

1

Bardziej rozwlekłe rozwiązanie, które wykorzystuje modulo i pozwala uniknąć niepotrzebnych obliczeń.

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

Jest to proste rozwiązanie pozwalające zaokrąglić w górę do najbliższej 1 minuty. Zachowuje informacje TimeZone i Kind z DateTime. Można go dodatkowo zmodyfikować, aby dopasować go do własnych potrzeb (jeśli chcesz zaokrąglić do najbliższych 5 minut itp.).

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

Możesz użyć tej metody, używa ona określonej daty, aby upewnić się, że zachowuje wszystkie rodzaje globalizacji i daty / godziny określone wcześniej w obiekcie 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

Jeśli chcesz użyć TimeSpan do zaokrąglenia, możesz użyć tego.

/// <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


Co się stanie, jeśli chcesz zaokrąglić do najbliższej 7 minuty var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// powinna wynosić 9:42, ale żadna z tych metod nie działa w ten sposób?
— DotnetShadow

Edycja wygląda na to, że odpowiedź @soulflyman dałaby właściwy wynik
— DotnetShadow
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.