Jak obciąć milisekundy dla daty .NET Data

334

Próbuję porównać znacznik czasu z przychodzącego żądania z wartością przechowywaną w bazie danych. SQL Server oczywiście zachowuje pewną precyzję w milisekundach czasu, a po wczytaniu do .NET DateTime uwzględnia te milisekundy. Przychodzące żądanie do systemu nie zapewnia jednak takiej precyzji, więc muszę po prostu upuścić milisekundy.

Czuję, że brakuje mi czegoś oczywistego, ale nie znalazłem eleganckiego sposobu na zrobienie tego (C #).

Jeff Putz
źródło
(3. próba ...) Ponieważ 20% odpowiedzi ( 1 , 2 , 3 ) opisuje, jak pominąć lub usunąć komponent milisekund ze sformatowanej stringreprezentacji a DateTime, być może konieczna jest edycja, aby wyjaśnić, że „obciąć” / „drop” milisekund znaczy „stworzyć DateTimewartość gdzie wszystkie elementy data / czas są takie same z wyjątkiem TimeOfDay.TotalMillisecondsjest 0”. Oczywiście ludzie nie czytają, ale po to, by wyeliminować wszelkie niejasności.
BACON

Odpowiedzi:

556

Poniższe będzie działać dla DateTime, który ma ułamkowe milisekundy, a także zachowuje właściwość Kind (lokalna, utc lub niezdefiniowana).

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

lub równoważny i krótszy:

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

Można to uogólnić na metodę rozszerzenia:

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

który jest używany w następujący sposób:

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
...
Joe
źródło
Chociaż dam ci to, ponieważ technicznie masz rację, dla osób odczytujących dane z SQL Servera w celu porównania z niektórymi danymi rozproszonymi (w moim przypadku żądanie oparte na sieci Web), ta rozdzielczość nie jest konieczna.
Jeff Putz,
1
Miły. Najwyraźniej ktoś musi podać klasie DateTime pewne metody rozszerzenia, aby zaokrąglić ją do najbliższej wartości, aby ten rodzaj dobrego kodowania został ponownie wykorzystany.
chris.w.mclean
Jest to bardzo mało prawdopodobne, ale czy to podejście się nie psuje, gdy tyknięcia = 0?
adotout
@adotout, powyższa metoda Truncate zgłosi wyjątek DivideByZeroException, jeśli parametr timeSpan wynosi zero, czy to rozumiesz przez „podejście łamie się, gdy tyknięcia = 0”? Lepiej byłoby zgłosić wyjątek ArgumentException, gdy przedział czasu wynosi zero.
Joe
145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
benPearce
źródło
34
Jasne i proste, pamiętaj tylko o dodaniu „, date.Kind” na końcu konstruktora, aby mieć pewność, że nie stracisz ważnej informacji.
JMcDaniel
9
Zachowaj ostrożność w przypadku tego rozwiązania w kodzie wrażliwym na wydajność. Moja aplikacja spędzała 12% czasu procesora w System.DateTime.GetDatePart .
Pułkownik Panic
3
Jest to proste, ale wolniejsze niż pytanie oznaczone jako najlepsza odpowiedź. To nie może być wąskie gardło, ale jest około 7-8x wolniejsze.
Jonas
„Znacznie wolniejsze” instrukcje nie są dokładnie poprawne, różnica wynosi od 50% do około 100% w zależności od czasu wykonywania; netto 4.7.2: 0.35μs vs 0,62 mikrosekundy i rdzenia 3,1 0,18 uS vs 0,12 mikrosekundy , który jest mikrosekund (10 ^ -6 sekund)
juwens
61

Oto metoda rozszerzenia oparta na poprzedniej odpowiedzi, która pozwala obciąć do dowolnej rozdzielczości ...

Stosowanie:

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

Klasa:

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);
    }
}
Sky Sanders
źródło
1
Jest to naprawdę elastyczne i wielokrotnego użytku rozwiązanie, które jest zwięzłe i wyraziste bez nadmiernej gadatliwości. Mój głos jako najlepsze rozwiązanie.
Jaans
2
Tak naprawdę nie potrzebujesz nawiasów wokół operandów%.
ErikE
8
.. ale moim zdaniem pareny dodają jasności.
orion elenzil
28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);
chris.w.mclean
źródło
70
-1: Działa tylko wtedy, gdy wartość DateTime nie obejmuje ułamków milisekundy.
Joe
7
Zastosowanie tej metody spowodowało niepowodzenie niektórych moich testów jednostkowych: Oczekiwano: 2010-05-05 15: 55: 49.000 Ale było: 2010-05-05 15: 55: 49.000. Zgaduję z powodu tego, co Joe wspomniał o ułamkach milisekundy.
Seth Reno,
6
Nie działa w przypadku serializacji, np. 2010-12-08T11: 20: 03.000099 + 15: 00 jest wyjściem, nie odcina całkowicie milisekund.
joedotnot
5
MillisecondWłaściwość daje liczbę całkowitą w zakresie od 0 do 999 (włącznie). Więc jeśli pora dnia przed operacją była, powiedzmy, 23:48:49.1234567to ta liczba całkowita będzie 123, a pora dnia po operacji to 23:48:49.0004567. Więc nie skrócił się do pełnej liczby sekund.
Jeppe Stig Nielsen
11

Czasami chcesz obciąć coś na podstawie kalendarza, na przykład rok lub miesiąc. Oto metoda rozszerzenia, która pozwala wybrać dowolną rozdzielczość.

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");
    }
}
KingPong
źródło
9

Zamiast upuszczać milisekundy niż porównywać, dlaczego nie porównać różnicy?

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

lub

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;
Kok
źródło
3
pierwsza opcja nie działa, ponieważ TotalSeconds jest podwójny; zwraca również milisekundy.
Jowen
1
Porównanie różnicy nie daje takiego samego rezultatu jak obcięcie, a następnie porównanie. Np. 5.900 i 6.100 dzieli je mniej niż sekunda, więc porównałbyś się z tą samą metodą. Ale obcięte wartości 5 i 6 są różne. To, co jest właściwe, zależy od wymagań.
Joe
7

Mniej oczywiste, ale ponad 2 razy szybsze:

// 10000000 runs

DateTime d = DateTime.Now;

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

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);
Diadistis
źródło
3
Zauważ, że druga opcja d.AddMilliseconds(-d.Millisecond)niekoniecznie przenosi DateTime dokładnie do poprzedniej pełnej sekundy. d.Ticks % TimeSpan.TicksPerMillisecondtiki (gdzieś pomiędzy 0 a 9 999) poza sekundą pozostaną.
Technetium
5

Aby zaokrąglić w dół do drugiej:

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

Zamień na, TicksPerMinuteaby zaokrąglić w dół do minuty.


Jeśli twój kod jest wrażliwy na wydajność, zachowaj ostrożność

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

Moja aplikacja spędzała 12% czasu procesora w System.DateTime.GetDatePart .

Pułkownik Panika
źródło
3

Sposobem na łatwe czytanie jest ...

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

I więcej...

//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...
Sergio Cabral
źródło
4
Konwersja na ciągi i parsowanie to okropny pomysł pod względem wydajności.
Jeff Putz,
2
@JeffPutz to prawda, ale jest to proste. Nadaje się do automatycznego testu, w którym wartość wstawiona i wyciągnięta z DB traci tiki (moja dokładna sytuacja). Jednak ta odpowiedź może być jeszcze prostsza niż jest, ponieważ var now = DateTime.Parse(DateTime.Now.ToString())działa dobrze.
Grimm The Opiner,
1
@GrimmTheOpiner - „... działa dobrze”, głównie, ale nie ma gwarancji. To, co robi, to: „Zaokrągla datę i godzinę z dowolną precyzją„ Długi czas ”jest skonfigurowany zgodnie z preferencjami Panelu sterowania bieżącego użytkownika”. Co zwykle jest sekundą, ale niekoniecznie.
Joe
1
podobnie jak jego prostota, wydajność nie stanowi problemu w sytuacji zautomatyzowanego testowania.
liang
1

Odnośnie odpowiedzi Diadistisa. To działało dla mnie, tyle że musiałem użyć Floor, aby usunąć ułamkową część podziału przed pomnożeniem. Więc,

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

staje się

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

Spodziewałbym się, że podział dwóch długich wartości da w wyniku Long, usuwając w ten sposób część dziesiętną, ale rozwiązuje to jako Double, pozostawiając dokładnie tę samą wartość po pomnożeniu.

Eppsja


źródło
1

2 Metody rozszerzenia wyżej wymienionych rozwiązań

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

stosowanie:

bool liesAfter = myObject.DateProperty.LiesAfterOrEqualsIgnoringMilliseconds(startDateTime, DateTimeKind.Utc);
HerbalMart
źródło
1

Nie najszybsze rozwiązanie, ale proste i łatwe do zrozumienia:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)
AlliterativeAlice
źródło
0
DateID.Text = DateTime.Today.ToShortDateString();

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

I przy użyciu

ToLongDateString() // its show 19 February 2016.

: P

Dhawal Shukal
źródło
-1. Widzę, jak pytanie mogło zostać błędnie zinterpretowane jako prośba o utworzenie stringzamiast a DateTime, ale całkowicie pomija komponenty czasu w danych wyjściowych . (To sprawia, że ​​dostęp do Todaynieruchomości również nie jest konieczny.)
BACON
0

Nowa metoda

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

// zdefiniuj parametr Przekazanie ciągu dd-mmm-rrrr Zwróć 24-lut-2016

Lub pokazane na polu tekstowym

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

// włóż PageonLoad

Dhawal Shukal
źródło
-1. Widzę, jak pytanie mogło zostać błędnie zinterpretowane jako prośba o utworzenie stringzamiast a DateTime, ale całkowicie pomija komponenty czasu w danych wyjściowych . (To sprawia, że ​​dostęp do Todaynieruchomości również nie jest konieczny.)
BACON
0

W moim przypadku chciałem zaoszczędzić TimeSpan z narzędzia datetimePicker bez zapisywania sekund i milisekund, a oto rozwiązanie.

Najpierw przekonwertuj datetimePicker.value na żądany format, który to mój „HH: mm”, a następnie przekonwertuj go z powrotem na TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;
Dzwoneczek
źródło
Lepszym sposobem (wyraźniejszym celem jest uniknięcie formatowania i analizowania wstecz od a string), aby to zrobić, DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Albo możesz użyć odmiany metody rozszerzenia Joe, która działa na TimeSpanwartościach i użyć, TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));aby skrócić sekundy.
BACON
0

To jest moja wersja metod rozszerzenia opublikowanych tutaj i w podobnych pytaniach. Sprawdza to wartość tyknięć w łatwy do odczytania sposób i zachowuje DateTimeKind oryginalnej instancji DateTime. (Ma to subtelne, ale istotne skutki uboczne podczas przechowywania w bazie danych takiej jak MongoDB.)

Jeśli prawdziwym celem jest obcięcie DateTime do określonej wartości (tj. Godziny / Minuty / Sekundy / MS), zalecam zamiast tego wdrożenie tej metody rozszerzenia w kodzie. Zapewnia, że ​​można obciąć tylko z prawidłową precyzją i zachowuje ważne metadane DateTimeKind oryginalnej instancji:

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

Następnie możesz użyć następującej metody:

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

dateTime.Kind => DateTimeKind.Utc
Kyle L.
źródło
-1

Wiem, że odpowiedź jest dość późna, ale najlepszym sposobem na pozbycie się milisekund jest

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

Spróbuj wydrukować wartość zmiennej, pokaże datę i godzinę, bez milisekund.

Nisarg Shah
źródło
1
To nie jest idealne. Masz wtedy ciąg znaków, a nie DateTime.
Jeff Putz,