Come troncare millisecondi di un DateTime .NET


334

Sto cercando di confrontare un timestamp da una richiesta in arrivo con un valore memorizzato nel database. SQL Server ovviamente mantiene una precisione di millisecondi nel tempo e, quando viene letto in un DateTime .NET, include quei millisecondi. La richiesta in arrivo al sistema, tuttavia, non offre quella precisione, quindi devo semplicemente eliminare i millisecondi.

Sento che mi manca qualcosa di ovvio, ma non ho trovato un modo elegante per farlo (C #).


(3 ° tentativo ...) Poiché il 20% delle risposte ( 1 , 2 , 3 ) descrive come omettere o rimuovere il componente in millisecondi dalla stringrappresentazione formattata di a DateTime, forse è necessaria una modifica per chiarire che "troncare" / "drop" millisecondi significa "produce un DateTimevalore in cui tutti i componenti data / ora sono uguali tranne che lo TimeOfDay.TotalMillisecondsè 0". Le persone non leggono, ovviamente, ma solo per eliminare qualsiasi ambiguità.
BACON,

Risposte:


557

Di seguito funzionerà per un DateTime che ha millisecondi frazionari e conserva anche la proprietà Kind (Local, Utc o Undefined).

DateTime dateTime = ... anything ...
dateTime = new DateTime(
    dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), 
    dateTime.Kind
    );

o l'equivalente e più breve:

dateTime = dateTime.AddTicks( - (dateTime.Ticks % TimeSpan.TicksPerSecond));

Questo potrebbe essere generalizzato in un metodo di estensione:

public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
    if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException
    if (dateTime == DateTime.MinValue || dateTime == DateTime.MaxValue) return dateTime; // do not modify "guard" values
    return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}

che viene utilizzato come segue:

dateTime = dateTime.Truncate(TimeSpan.FromMilliseconds(1)); // Truncate to whole ms
dateTime = dateTime.Truncate(TimeSpan.FromSeconds(1)); // Truncate to whole second
dateTime = dateTime.Truncate(TimeSpan.FromMinutes(1)); // Truncate to whole minute
...

Mentre ti darò questo perché sei tecnicamente corretto, per le persone che leggono i dati da SQL Server per confrontarli con alcuni dati distribuiti (una richiesta basata sul Web, nel mio caso), questa quantità di risoluzione non è necessaria.
Jeff Putz,

1
Bello. Chiaramente qualcuno deve dare alla classe DateTime alcuni metodi di estensione per arrotondare al più vicino qualunque cosa in modo che questo tipo di buona codifica venga riutilizzato.
chris.w.mclean,

Questo è molto improbabile, ma questo approccio non si interrompe quando tick = 0?
adotout,

@adotout, il metodo Truncate sopra genererà una DivideByZeroException se il parametro timeSpan è zero, è questo che intendi per "approccio si interrompe quando ticks = 0"? Sarebbe meglio lanciare un ArgumentException quando timeSpan è zero.
Joe,

145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);

34
Chiaro e semplice, ricorda di aggiungere un ", date.Kind" alla fine del costruttore per assicurarti di non perdere un'informazione importante.
JMcDaniel,

9
Prestare attenzione a questa soluzione nel codice sensibile alle prestazioni. La mia app impiegava il 12% del tempo della CPU in System.DateTime.GetDatePart .
Colonnello Panic,

3
È semplice, ma più lento della domanda contrassegnata come migliore risposta. Non che questo possa essere un collo di bottiglia, ma è circa 7-8 volte più lento.
Jonas,

Le istruzioni "molto più lente" non sono esattamente corrette, la differenza è tra il 50% e circa il 100% a seconda del tempo di esecuzione; netto 4.7.2: 0.35µs contro 0.62 µs e core 3.1: 0.18 µs contro 0.12 µs che è micro-secondi (10 ^ -6 secondi)
juwens

62

Ecco un metodo di estensione basato su una risposta precedente che ti permetterà di troncare a qualsiasi risoluzione ...

Uso:

DateTime myDateSansMilliseconds = myDate.Truncate(TimeSpan.TicksPerSecond);
DateTime myDateSansSeconds = myDate.Truncate(TimeSpan.TicksPerMinute)

Classe:

public static class DateTimeUtils
{
    /// <summary>
    /// <para>Truncates a DateTime to a specified resolution.</para>
    /// <para>A convenient source for resolution is TimeSpan.TicksPerXXXX constants.</para>
    /// </summary>
    /// <param name="date">The DateTime object to truncate</param>
    /// <param name="resolution">e.g. to round to nearest second, TimeSpan.TicksPerSecond</param>
    /// <returns>Truncated DateTime</returns>
    public static DateTime Truncate(this DateTime date, long resolution)
    {
        return new DateTime(date.Ticks - (date.Ticks % resolution), date.Kind);
    }
}

1
Questa è una soluzione davvero flessibile e riutilizzabile, concisa ed espressiva senza essere eccessivamente prolissa. Il mio voto come migliore soluzione.
Jaans,

2
In realtà non hai bisogno delle parentesi attorno agli operandi%.
ErikE

8
.. ma i genitori aggiungono chiarezza, secondo me.
Orion Elenzil,

28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);

70
-1: funzionerà solo se il valore DateTime non include le frazioni di un millisecondo.
Joe,

7
L'uso di questo metodo ha provocato il fallimento di alcuni test della mia unità: Previsto: 2010-05-05 15: 55: 49.000 Ma era: 2010-05-05 15: 55: 49.000. Sto indovinando a causa di ciò che Joe ha menzionato sulle frazioni di millisecondo.
Seth Reno,

6
Non funziona per la serializzazione, ad esempio 2010-12-08T11: 20: 03.000099 + 15: 00 è l'output, non taglia completamente i millisecondi.
joedotnot

5
La Millisecondproprietà fornisce un numero intero compreso tra 0 e 999 (incluso). Quindi, se l'ora del giorno prima dell'operazione fosse, diciamo, 23:48:49.1234567allora quel numero intero sarà 123, e l'ora del giorno dopo l'operazione sarà 23:48:49.0004567. Quindi non si è troncato per un numero intero di secondi.
Jeppe Stig Nielsen,

11

A volte vuoi troncare a qualcosa di basato sul calendario, come l'anno o il mese. Ecco un metodo di estensione che ti consente di scegliere qualsiasi risoluzione.

public enum DateTimeResolution
{
    Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}

public static DateTime Truncate(this DateTime self, DateTimeResolution resolution = DateTimeResolution.Second)
{
    switch (resolution)
    {
        case DateTimeResolution.Year:
            return new DateTime(self.Year, 1, 1, 0, 0, 0, 0, self.Kind);
        case DateTimeResolution.Month:
            return new DateTime(self.Year, self.Month, 1, 0, 0, 0, self.Kind);
        case DateTimeResolution.Day:
            return new DateTime(self.Year, self.Month, self.Day, 0, 0, 0, self.Kind);
        case DateTimeResolution.Hour:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerHour));
        case DateTimeResolution.Minute:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMinute));
        case DateTimeResolution.Second:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerSecond));
        case DateTimeResolution.Millisecond:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMillisecond));
        case DateTimeResolution.Tick:
            return self.AddTicks(0);
        default:
            throw new ArgumentException("unrecognized resolution", "resolution");
    }
}

9

Invece di perdere i millisecondi e confrontarli, perché non confrontare la differenza?

DateTime x; DateTime y;
bool areEqual = (x-y).TotalSeconds == 0;

o

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;

3
la prima opzione non funziona, poiché TotalSeconds è una doppia; restituisce anche i millisecondi.
Jowen,

1
Il confronto della differenza non dà lo stesso risultato del troncamento e del confronto. Ad esempio, 5.900 e 6.100 sono distanti meno di un secondo, quindi si confronterebbero come uguali al proprio metodo. Ma i valori troncati 5 e 6 sono diversi. Quale è appropriato dipende dalle vostre esigenze.
Joe,

7

Meno ovvio ma più di 2 volte più veloce:

// 10000000 runs

DateTime d = DateTime.Now;

// 484,375ms
d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);

3
Si noti che la seconda opzione d.AddMilliseconds(-d.Millisecond)non sposta necessariamente il DateTime esattamente sul secondo, intero precedente. d.Ticks % TimeSpan.TicksPerMillisecondresteranno delle zecche (tra 0 e 9.999) oltre il secondo.
Technetium,

5

Per arrotondare al secondo:

dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerSecond)

Sostituire con TicksPerMinuteper arrotondare al minuto.


Se il tuo codice è sensibile alle prestazioni, fai attenzione

new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second)

La mia app impiegava il 12% del tempo della CPU in System.DateTime.GetDatePart .


3

Un modo per leggere facilmente è ...

//Remove milliseconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss", null);

E altro ...

//Remove seconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm"), "yyyy-MM-dd HH:mm", null);

//Remove minutes
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH"), "yyyy-MM-dd HH", null);

//and go on...

4
La conversione in stringhe e l'analisi è un'idea orribile in termini di prestazioni.
Jeff Putz,

2
@JeffPutz vero, ma è semplice. Adatto per un test automatizzato in cui un valore inserito e estratto da un DB perde le zecche (la mia situazione esatta). Tuttavia, questa risposta potrebbe essere ancora più semplice di quanto non sia, poiché var now = DateTime.Parse(DateTime.Now.ToString())funziona bene.
Grimm The Opiner,

1
@GrimmTheOpiner - "... funziona bene", per lo più, ma non è garantito. Quello che fa è: "Arrotonda un DateTime a qualsiasi precisione" Long time "sia configurata come nelle preferenze del Pannello di controllo dell'utente corrente". Che di solito è, ma non necessariamente, secondi.
Joe,

1
come la sua semplicità, le prestazioni non sono un problema per la situazione dei test automatizzati.
liang,

1

Per quanto riguarda la risposta di Diadistis. Questo ha funzionato per me, tranne per il fatto che ho dovuto usare Floor per rimuovere la parte frazionaria della divisione prima della moltiplicazione. Così,

d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

diventa

d = new DateTime(Math.Floor(d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

Mi sarei aspettato che la divisione di due valori Long risultasse in Long, rimuovendo così la parte decimale, ma la risolve come Double lasciando lo stesso valore esatto dopo la moltiplicazione.

Eppsy


1

2 Metodi di estensione per le soluzioni sopra menzionate

    public static bool LiesAfterIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate > compareDate;
    }


    public static bool LiesAfterOrEqualsIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate >= compareDate;
    }

utilizzo:

bool liesAfter = myObject.DateProperty.LiesAfterOrEqualsIgnoringMilliseconds(startDateTime, DateTimeKind.Utc);

1

Non la soluzione più veloce ma semplice e di facile comprensione:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)

0
DateID.Text = DateTime.Today.ToShortDateString();

Use ToShortDateString() //Date 2-02-2016
Use ToShortDateString() // Time 

E per uso di

ToLongDateString() // its show 19 February 2016.

: P


-1. Posso vedere come la questione potrebbe essere stato male interpretato come chiedere di produrre una stringinvece di una DateTime, ma questo omette componenti tempo l'uscita completamente . (Ciò rende Todaysuperfluo anche l'accesso alla proprietà.)
BACON

0

Nuovo metodo

String Date = DateTime.Today.ToString("dd-MMM-yyyy"); 

// definisce il parametro String pass dd-mmm-yyyy return 24-feb-2016

O mostrato nella casella di testo

txtDate.Text = DateTime.Today.ToString("dd-MMM-yyyy");

// metti su PageonLoad


-1. Posso vedere come la questione potrebbe essere stato male interpretato come chiedere di produrre una stringinvece di una DateTime, ma questo omette componenti tempo l'uscita completamente . (Ciò rende Todaysuperfluo anche l'accesso alla proprietà.)
BACON

0

Nel mio caso, miravo a salvare TimeSpan dallo strumento datetimePicker senza salvare i secondi e i millisecondi, ed ecco la soluzione.

Per prima cosa converti datetimePicker.value nel formato desiderato, il mio è "HH: mm", quindi riconvertilo in TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;

Un modo migliore (intento più chiaro, evitare la formattazione e il parsing da a string) per fare ciò sarebbe DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Oppure potresti usare una variante del metodo di estensione di Joe che opera su TimeSpanvalori e usa TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));per troncare i secondi.
BACON,

0

Questa è la mia versione dei metodi di estensione pubblicati qui e in domande simili. Ciò convalida il valore di tick in un modo facile da leggere e conserva DateTimeKind dell'istanza DateTime originale. (Ciò ha effetti collaterali sottili ma rilevanti quando si archivia in un database come MongoDB.)

Se il vero obiettivo è troncare un DateTime su un valore specificato (ad es. Ore / Minuti / Secondi / MS), consiglio invece di implementare questo metodo di estensione nel codice. Assicura che puoi troncare solo con una precisione valida e conserva gli importanti metadati DateTimeKind della tua istanza originale:

public static DateTime Truncate(this DateTime dateTime, long ticks)
{
    bool isValid = ticks == TimeSpan.TicksPerDay 
        || ticks == TimeSpan.TicksPerHour 
        || ticks == TimeSpan.TicksPerMinute 
        || ticks == TimeSpan.TicksPerSecond 
        || ticks == TimeSpan.TicksPerMillisecond;

    // /programming/21704604/have-datetime-now-return-to-the-nearest-second
    return isValid 
        ? DateTime.SpecifyKind(
            new DateTime(
                dateTime.Ticks - (dateTime.Ticks % ticks)
            ),
            dateTime.Kind
        )
        : throw new ArgumentException("Invalid ticks value given. Only TimeSpan tick values are allowed.");
}

Quindi è possibile utilizzare il metodo in questo modo:

DateTime dateTime = DateTime.UtcNow.Truncate(TimeSpan.TicksPerMillisecond);

dateTime.Kind => DateTimeKind.Utc

-1

So che la risposta è abbastanza tardi, ma il modo migliore per sbarazzarsi di millisecondi è

var currentDateTime = DateTime.Now.ToString("s");

Prova a stampare il valore della variabile, mostrerà la data e l'ora, senza millisecondi.


1
Questo non è l'ideale. Allora hai una stringa e non un DateTime.
Jeff Putz,
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.