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

TimS
źródło

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}
dtb
źródło
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
redent84
źródło
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);
    }
DevSal
źródło
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
Vlad Bezden
źródło
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 ;
    }

}
Nicholas Carey
źródło
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);
soulflyman
źródło
to nie oznacza sekund
Alex Norcliffe,
1

Elegancki?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)
Olaf
źródło
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);
}
user1978424
źródło
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);
    }
}
Bo Sunesen
źródło
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;
dodgy_coder
źródło
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

Du D.
źródło
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