Come posso arrotondare il tempo ai X minuti più vicini?


160

C'è una semplice funzione per arrotondare UP una DateTimecon l'approssimazione di 15 minuti?

Per esempio

2011-08-11 16:59 diventa 2011-08-11 17:00

2011-08-11 17:00 resta come 2011-08-11 17:00

2011-08-11 17:01 diventa 2011-08-11 17:15

Risposte:


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

Esempio:

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
Questa soluzione è appena entrata nella mia libreria di utility come metodo di estensione.
JYelton,

1
Fai attenzione ai tempi di arrotondamento vicini all'estremo superiore. Ciò può provocare un'eccezione se i tick calcolati sono maggiori di DateTime.MaxValue.Ticks. Sii sicuro e prendi il minimo del tuo valore calcolato e DateTime.MaxValue.Ticks.
Paul Raff,

4
Non stai perdendo informazioni dall'oggetto DateTime con questo metodo? Come il tipo e il fuso orario, se ci sono impostati?
Evren Kuzucuoglu,

11
@ user14 .. Il (+ d.Ticks - 1) si assicura che si arrotonda se necessario. I simboli / e * sono arrotondati. Esempio da 12 a 5 successivi: (12 + 5 - 1) = 16, 16/5 = 3 (perché è un tipo di dati intero), 3 * 5 = 15. tada :)
Diego Frehner

12
@dtb una piccola aggiunta, altrimenti è probabilmente un po 'infastidito: è necessario mantenere il tipo di datetime ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy

107

È venuto con una soluzione che non comporta moltiplicare e dividere i long numeri.

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

Uso:

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
Ho pensato con certezza che questo sarebbe stato più veloce rispetto all'utilizzo della moltiplicazione e della divisione, ma i miei test dimostrano che non lo è. Oltre 10000000 iterazioni, il metodo del modulo ha richiesto ~ 610ms sulla mia macchina, mentre il metodo mult / div ha richiesto ~ 500ms. Immagino che le FPU rendano le vecchie preoccupazioni un problema. Ecco il mio codice di prova: pastie.org/8610460
vigore

1
Ottimo uso delle estensioni. Grazie!
TravisWidden

1
@Alovchin Grazie. Ho aggiornato la risposta. Ho creato questo ideone con il tuo codice per mostrare la differenza: ideone.com/EVKFp5
redent84

1
Questo è piuttosto vecchio, ma è l'ultimo %d.Ticksin RoundUpnecessaria? d.Ticks - (dt.Ticks % d.Ticks))sarà necessariamente inferiore a d.Ticks, quindi la risposta dovrebbe essere la stessa corretta?
Nate Diamond

1
Solo sottolineando, il modulo è un richiede un'operazione di divisione sulla CPU. Ma concordo sul fatto che sia più elegante che usare la proprietà rouding down delle divisioni intere.
Alex,

19

se è necessario arrotondare all'intervallo di tempo più vicino (non in alto), suggerisco di utilizzare quanto segue

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

Questa risposta non è corretta. user1978424 ha l'unico post che mostra correttamente come arrotondare all'intervallo più vicino di seguito: (ironicamente
sottovalutato

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

risultati:

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:01viene troncato a2011-08-11 17:00:00
JYelton l'

1
@JYelton: Grazie per aver sottolineato +1. Ho modificato il mio codice per adattarlo.
Vlad Bezden,

Fornire il codice Linqpad per una facile verifica è un grande risparmio di tempo. Molto facile da usare
Adam Garner,

6

Dato che odio reinventare la ruota, probabilmente seguirò questo algoritmo per arrotondare un valore DateTime a un determinato incremento di tempo (Timespan):

  • Convertire il DateTimevalore da arrotondare a un valore decimale in virgola mobile che rappresenta il numero intero e frazionario di TimeSpanunità.
  • Arrotondalo a un numero intero, usando Math.Round().
  • Ridimensionare ai tick moltiplicando il numero intero arrotondato per il numero di tick TimeSpannell'unità.
  • Crea un'istanza di un nuovo DateTimevalore dal numero arrotondato di tick e restituiscilo al chiamante.

Ecco il codice:

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

}

Questo è bello il codice per l'arrotondamento al più vicino DateTime , ma voglio anche la capacità di turno fino a un multiplo di unit . Il passaggio MidpointRounding.AwayFromZeroa Roundnon ha l'effetto desiderato. Hai in mente qualcos'altro accettando una MidpointRoundingdiscussione?
HappyNomad,

2

La mia versione

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

Come metodo si bloccherebbe in questo modo

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

e si chiama così

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

questo non tiene conto dei secondi
Alex Norcliffe del

1

Elegante?

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

1
Una versione più corretta sarebbe: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), che si occupa di la condizione "rimane".
Olaf,

1

Attenzione: la formula sopra è errata, ovvero la seguente:

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

dovrebbe essere riscritto come:

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

1
Non sono d'accordo. Poiché la divisione intera / d.Ticksviene arrotondata per difetto all'intervallo di 15 minuti più vicino (chiamiamo questi "blocchi"), l'aggiunta di un solo mezzo blocco non garantisce l'arrotondamento. Considera quando hai 4,25 blocchi. Se aggiungi 0,5 blocchi, quindi testa quanti blocchi interi hai, ne hai ancora 4. L'aggiunta di un segno di spunta in meno di un blocco completo è l'azione corretta. Ti assicura di spostarti sempre verso l'intervallo di blocchi successivo (prima di arrotondare per difetto), ma ti impedisce di spostarti tra i blocchi esatti. (IE, se hai aggiunto un blocco completo a 4.0 blocchi, 5.0 dovrebbe arrotondare a 5, quando vuoi 4. 4.99 saranno 4.)
Brendan Moore,

1

Una soluzione più dettagliata, che utilizza il modulo ed evita calcoli inutili.

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

Questa è una soluzione semplice per arrotondare al minuto più vicino. Conserva le informazioni TimeZone e Kind del DateTime. Può essere modificato per soddisfare ulteriormente le proprie esigenze (se è necessario arrotondare ai 5 minuti più vicini, ecc.).

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

È possibile utilizzare questo metodo, utilizza la data specificata per assicurarsi che mantenga qualsiasi tipo di globalizzazione e datetime precedentemente specificato nell'oggetto 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

Se vuoi usare TimeSpan per arrotondare, puoi usare questo.

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

Fiddle TimeSpan


Cosa succede se si desidera arrotondare al settimo minuto var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// più vicino dovrebbe essere 9:42 ma nessuno di questi metodi funziona in questo modo?
DotnetShadow

Modifica sembra che la risposta di @soulflyman produrrebbe il risultato giusto
DotnetShadow il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.