2009-04-20 11 views
18

Voglio arrotondare un DateTime ai 5 secondi successivi. Questo è il modo in cui lo sto facendo attualmente, ma mi chiedevo se ci fosse un modo migliore o più conciso?Esiste un modo migliore in C# per arrotondare un DateTime ai 5 secondi successivi?

DateTime now = DateTime.Now; 
int second = 0; 

// round to nearest 5 second mark 
if (now.Second % 5 > 2.5) 
{ 
    // round up 
    second = now.Second + (5 - (now.Second % 5)); 
} 
else 
{ 
    // round down 
    second = now.Second - (now.Second % 5); 
} 

DateTime rounded = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, second); 

Si prega di notare che ho trovato thesetwo domande precedenti, tuttavia essi troncano piuttosto che rotonda il tempo.

risposta

29

Il conteggio zecche di un DateTime rappresenta intervalli di 100 nanosecondi, in modo da poter intorno alla 5 secondi arrotondando all'intervallo 50000000-tick più vicino in questo modo:

DateTime now = DateTime.Now; 
    DateTime rounded = new DateTime(((now.Ticks + 25000000)/50000000) * 50000000); 

Questo è più conciso, ma non necessariamente meglio Dipende se preferisci la brevità e la velocità rispetto alla chiarezza del codice. Il tuo è probabilmente più facile da capire.

+4

Questo funziona bene perché 59 secondi arrotondati al 5 più vicino daranno 60, che non puoi passare come parametro "secondi" al costruttore DateTime. In questo modo eviti questa trappola. –

+0

Sì, questo è un buon punto: ho perso questo problema nel mio codice ... – Damovisa

+1

Un potenziale trabocchetto, per criticare la mia risposta, è che non sono sicuro di come i conti DateTime risalgano a secondi. Il conteggio delle zecche è misurato dalle 12:00:00 a mezzanotte, il 1 ° gennaio 0001. Pertanto, a seconda del numero di secondi intercalari da quel momento e se DateTime ne tiene conto, è possibile che il valore dei secondi risultante non sia un multiplo di 5. – JayMcClellan

2

Come hai detto, è abbastanza facile da troncare. Quindi, basta aggiungere 2,5 secondi, quindi troncare verso il basso.

+0

Se aggiungo 2,5 secondi, troncato ai 5 secondi successivi e sottraggo i 2,5 secondi, finirò con 2,5 secondi, 7,5 secondi, 12,5 secondi ecc ... – Damovisa

+0

Quindi, tralasciando l'ultimo passaggio (aggiungi 2,5 secondi) dovrebbe funzionare ... – Damovisa

1

Non riesco a pensare ad un modo migliore, anche se mi sarebbe probabilmente scomporre il metodo rotonda:

static int Round(int n, int r) 
{ 
    if ((n % r) <= r/2) 
    { 
     return n - (n % r); 
    } 
    return n + (r - (n % r)); 
} 

Inoltre,% restituisce un int, quindi confrontandolo con 2,5 mi sembra un po 'strano, anche se è corretto. Io userei> = 3.

+0

Sì, so cosa intendi con il confronto con 2.5 - mi sentivo un po 'a disagio. Come dici tu, è corretto, e rende più chiaro quale sia l'intenzione. 2,5 è chiaramente la metà di 5 mentre 3 sembra non adattarsi. – Damovisa

+0

Preferisco arrotondare numeri interi come questo: '((n + (r >> 1))/r) * r' (punti medi tondi su) o' ((n + r >> 1 - 1)/r) * r '(punti medi rotondi in basso). Se so che 'r' è dispari, io uso solo il primo perché funzionano allo stesso modo per odd' r'. Questo approccio utilizza solo una divisione (vs 3) e nessuna ramificazione rispetto alla funzione. –

1

Non riuscivo a riconoscere la differenza tra C# e una barra di sapone (beh, non potevo quando ho scritto questa risposta originariamente, le cose sono cambiate un po 'negli anni successivi), ma, se siete alla ricerca di un concisa soluzione più, vorrei solo mettere il tutto in una funzione - c'è poco che sarà più conciso nel codice di una semplice chiamata a tale funzione:

DateTime rounded = roundTo5Secs (DateTime.Now); 

Quindi puoi inserire tutto ciò che vuoi nella funzione e documentare semplicemente come funziona, ad esempio (supponendo che si tratti di tutte le operazioni con numeri interi):

secBase = now.Second/5; 
secExtra = now.Second % 5; 
if (secExtra > 2) { 
    return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 
     secBase + 5); 
} 
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 
    secBase); 

Potrebbe anche essere necessario qualche controllo extra se secBase va a 60 (a meno che gli oggetti C# DateTime siano abbastanza intelligenti da far salire il minuto (e l'ora se il minuto va a 60, e così via).

+0

Sì, giusto punto. Lo farò - semplicemente non ho chiarito la domanda. – Damovisa

+0

Il tempo del bagno per te deve essere difficile ;-) –

+0

Bello, @Gordon, conosco un bel po 'di più su C# al giorno d'oggi, ma il bagno è molto difficile. Anche se questo ha più a che fare con i bambini piccoli piuttosto che con la mancanza di coordinamento da parte mia :-) – paxdiablo

1

Che ne dici di questo (mescolando insieme alcune risposte)? Penso che trasmetta bene il significato e debba gestire i casi limite (arrotondando al minuto successivo) elegantemente a causa di AddSeconds.

// truncate to multiple of 5 
int second = 5 * (int) (now.Second/5); 
DateTime dt = new DateTime(..., second); 

// round-up if necessary 
if (now.Second % 5 > 2.5) 
{ 
    dt = dt.AddSeconds(5); 
} 

Il Ticks approccio come dimostrato da Jay è più conciso, ma può essere un po 'meno leggibile. Se si utilizza tale approccio, almeno il riferimento TimeSpan.TicksPerSecond.

+0

-1: non funziona se l'ora originale contiene frazioni di secondo. – Joe

+0

Hai ragione. Ho eseguito il rollback a una modifica precedente che era più chiara nella gestione di tale caso –

+0

Rimosso -1 quindi! – Joe

46

(Ci scusiamo per la risurrezione, riconosco che è un vecchio e rispose alla domanda - solo l'aggiunta di un po 'di codice aggiuntivo per l'amor di Google.)

ho iniziato con JayMcClellan's answer, ma poi ho voluto che fosse più generico, arrotondando al intervalli arbitrari (non solo 5 secondi). Così ho finito col lasciare il metodo di Jay per uno che usa Math.Round su tick e lo metto in un metodo di estensione che può prendere intervalli arbitrari e offre anche l'opzione di cambiare la logica di arrotondamento (arrotondamento del banchiere contro lo zero).Sto postando qui nel caso in cui questo è utile a qualcun altro così:

public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval, MidpointRounding roundingType) { 
     return new TimeSpan(
      Convert.ToInt64(Math.Round(
       time.Ticks/(decimal)roundingInterval.Ticks, 
       roundingType 
      )) * roundingInterval.Ticks 
     ); 
    } 

    public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval) { 
     return Round(time, roundingInterval, MidpointRounding.ToEven); 
    } 

    public static DateTime Round(this DateTime datetime, TimeSpan roundingInterval) { 
     return new DateTime((datetime - DateTime.MinValue).Round(roundingInterval).Ticks); 
    } 

non vincerà alcun premio per l'efficienza a nudo, ma trovo facile da leggere e intuitivo da usare. Esempio di utilizzo:

new DateTime(2010, 11, 4, 10, 28, 27).Round(TimeSpan.FromMinutes(1)); // rounds to 2010.11.04 10:28:00 
new DateTime(2010, 11, 4, 13, 28, 27).Round(TimeSpan.FromDays(1)); // rounds to 2010.11.05 00:00 
new TimeSpan(0, 2, 26).Round(TimeSpan.FromSeconds(5)); // rounds to 00:02:25 
new TimeSpan(3, 34, 0).Round(TimeSpan.FromMinutes(37); // rounds to 03:42:00...for all your round-to-37-minute needs 
+0

Eccellente, grazie - proprio quello che stavo cercando! – chris

+0

Questo è un bel codice per arrotondare al * più vicino * 'DateTime', ma voglio anche la possibilità di arrotondare * su * a un multiplo di' roundingInterval'. Ho provato a modificare l'overload 'DateTime' per prendere anche un' MidpointRounding' e poi passarlo a l'altro sovraccarico, ma questo non ha aggiunto l'abilità desiderata – HappyNomad

+0

@HappyNomad 'MidpointRounding' entra in gioco solo quando il valore si trova nel punto medio attuale.Se si desidera sempre arrotondare, è necessario aggiungere un sovraccarico o modificare la prima funzione per usare 'Math.Ceiling' invece di' Math.Round', e ignorare del tutto il 'roundingType'. –

0

Tecnicamente, non è mai possibile arrotondare correttamente a un intervallo dispari dati solo secondi.

2, 4, 6, 8, 10 < - non sono un problema

Se si sta distribuendo '' volte ad intervalli e se il jitter è basso, il troncamento è molto più trattabile.

Se è possibile passare millisecondi e arrotondare a un segno di 500 mS, sarà possibile dispari di secondi e anche tagliare l'effetto del jitter verso il basso o eliminarlo del tutto.

Problemi correlati