DateTime vs DateTimeOffset

742

Obecnie mamy standardowy sposób radzenia sobie z platformą .NET DateTimew sposób świadomy DateTimew strefie czasowej : Za każdym razem, gdy produkujemy coś takiego, robimy to w UTC (np. Za pomocą DateTime.UtcNow) i za każdym razem, gdy go wyświetlamy, konwertujemy z powrotem z UTC na czas lokalny użytkownika .

To działa dobrze, ale czytałem o DateTimeOffsettym, jak przechwytuje czas lokalny i czas UTC w samym obiekcie. Pytanie brzmi: jakie byłyby zalety korzystania DateTimeOffsetz tego, co już robiliśmy?

David Reis
źródło
3
Poniżej znajduje się kilka świetnych odpowiedzi. Nadal jednak zastanawiam się, co mogłoby w ogóle przekonać Cię do korzystania z funkcji DateTimeOffset.
HappyNomad,
1
Może również zobaczyć, kiedy-wolisz-wolisz-datetime-nad-datetimeoffset
nawfal
Jeśli chodzi o przechowywanie, interesujące jest także stackoverflow.com/questions/4715620/...
Dejan

Odpowiedzi:

1169

DateTimeOffsetjest reprezentacją czasu chwilowego (znanego również jako czas bezwzględny ). Rozumiem przez to moment w czasie, który jest powszechny dla wszystkich (nie uwzględniając sekund przestępnych lub relatywistycznych efektów dylatacji czasu ). Innym sposobem przedstawienia chwilowego czasu jest użycie opcji „ DateTimegdzie .Kindjest” DateTimeKind.Utc.

Różni się to od czasu kalendarzowego (znanego również jako czas cywilny ), który jest pozycją w czyimś kalendarzu, i istnieje wiele różnych kalendarzy na całym świecie. Te kalendarze nazywamy strefami czasowymi . Czas w kalendarzu jest reprezentowany przez DateTimegdzie .Kindjest DateTimeKind.Unspecifiedlub DateTimeKind.Local. I .Localma sens jedynie w sytuacjach, gdzie trzeba dorozumianą zrozumienia gdzie komputer, który wykorzystuje efekt jest umieszczony. (Na przykład stacja robocza użytkownika)

Dlaczego więc DateTimeOffsetzamiast UTC DateTime? Chodzi o perspektywę. Wykorzystajmy analogię - będziemy udawać fotografów.

Wyobraź sobie, że stoisz na osi czasu kalendarza, wskazując aparat na osobę na natychmiastowej linii czasu ustawionej przed tobą. Ustaw aparat zgodnie z zasadami obowiązującymi w strefie czasowej - które zmieniają się okresowo ze względu na czas letni lub inne zmiany w prawnej definicji strefy czasowej. (Nie masz pewnej ręki, więc aparat drży.)

Osoba stojąca na zdjęciu zobaczy kąt, z którego pochodzi twój aparat. Gdyby inni robili zdjęcia, mogliby być pod różnymi kątami. To właśnie reprezentuje Offsetczęść DateTimeOffset.

Jeśli więc oznaczysz kamerę jako „Czas wschodni”, czasami wskazujesz na -5, a czasem na -4. Istnieją kamery na całym świecie, wszystkie oznaczone różnymi rzeczami i wszystkie wskazujące tę samą natychmiastową oś czasu pod różnymi kątami. Niektóre z nich znajdują się obok siebie (lub na sobie), więc sama znajomość przesunięcia nie wystarczy, aby określić, z którą strefą czasową jest związany czas.

A co z UTC? Cóż, jest to jedyny aparat, który ma pewną rękę. Jest na statywie, mocno zakotwiczonym w ziemi. Nigdzie się nie wybiera. Kąt perspektywy nazywamy przesunięciem zerowym.

Czas chwilowy a wizualizacja czasu kalendarzowego

Co więc mówi nam ta analogia? Zawiera kilka intuicyjnych wskazówek

  • Jeśli reprezentujesz czas w stosunku do konkretnego miejsca, reprezentuj go w czasie kalendarzowym za pomocą DateTime. Tylko upewnij się, że nigdy nie pomylisz jednego kalendarza z drugim. Unspecifiedpowinno być twoim założeniem. Localjest użyteczne tylko z DateTime.Now. Na przykład mogę go pobrać DateTime.Nowi zapisać w bazie danych - ale kiedy go odzyskam , muszę założyć, że tak jest Unspecified. Nie mogę polegać na tym, że mój kalendarz lokalny jest tym samym kalendarzem, z którego został pierwotnie pobrany.

  • Jeśli musisz zawsze mieć pewność co do chwili, upewnij się, że reprezentujesz natychmiastowy czas. Użyj, DateTimeOffsetaby go wymusić, lub użyj UTC DateTimezgodnie z konwencją.

  • Jeśli chcesz śledzić chwilę chwilową, ale chcesz także wiedzieć „O której godzinie użytkownik sądził, że jest to w lokalnym kalendarzu?” - musisz użyć DateTimeOffset. Jest to bardzo ważne na przykład w przypadku systemów pomiaru czasu - zarówno ze względów technicznych, jak i prawnych.

  • Jeśli kiedykolwiek będziesz musiał zmodyfikować wcześniej zarejestrowany zapis DateTimeOffset- nie masz wystarczającej ilości informacji w samym odsunięciu, aby upewnić się, że nowe przesunięcie jest nadal odpowiednie dla użytkownika. Musisz także zapisać identyfikator strefy czasowej (pomyśl - potrzebuję nazwy tego aparatu, aby móc zrobić nowe zdjęcie, nawet jeśli pozycja uległa zmianie).

    Należy również zauważyć, że Noda Time ma reprezentację wymaganą ZonedDateTimedo tego, podczas gdy biblioteka klas podstawowych .Net nie ma niczego podobnego. Musisz przechowywać zarówno wartość DateTimeOffseta, jak i TimeZoneInfo.Idwartość.

  • Czasami będziesz chciał przedstawić czas kalendarzowy lokalny dla „każdego, kto na niego patrzy”. Na przykład przy określaniu, co dzisiaj oznacza. Dzisiaj jest zawsze północ do północy, ale reprezentują one prawie nieskończoną liczbę nakładających się zakresów na natychmiastowej linii czasu. (W praktyce mamy skończoną liczbę stref czasowych, ale można wyrazić przesunięcia aż do tyknięcia). W takich sytuacjach upewnij się, że rozumiesz, jak ograniczyć „kto pyta?” przesłuchać w jednej strefie czasowej lub odpowiednio przełożyć ją na czas natychmiastowy.

Oto kilka innych drobiazgów na DateTimeOffsetten temat, które potwierdzają tę analogię, oraz kilka wskazówek, jak zachować prostotę:

  • Jeśli porównasz dwie DateTimeOffsetwartości, przed porównaniem są one normalizowane do zerowego przesunięcia. Innymi słowy, 2012-01-01T00:00:00+00:00i 2012-01-01T02:00:00+02:00odnoszą się do tej samej chwilowej chwili, a zatem są równoważne.

  • Jeśli robisz żadnych testów jednostkowych i trzeba mieć pewność, offsetowej, test zarówno na DateTimeOffsetwartości, a .Offsetnieruchomość osobno.

  • Istnieje wbudowana jednokierunkowa konwersja wbudowana w platformę .Net, która umożliwia przekazanie DateTimedowolnego DateTimeOffsetparametru lub zmiennej. Dokonując tego, liczy . Jeśli zdasz typ UTC, zostanie on przeniesiony z zerowym przesunięciem, ale jeśli zdasz jeden z nich lub , przyjmie on, że jest lokalny . Ramy w zasadzie mówią: „Poprosiłeś mnie o konwersję czasu kalendarzowego na czas natychmiastowy, ale nie mam pojęcia, skąd on pochodzi, więc po prostu użyję lokalnego kalendarza”. To ogromna gotcha, jeśli załadujesz nieokreślony na komputerze z inną strefą czasową. (IMHO - to powinno wyrzucić wyjątek - ale tak nie jest.).Kind.Local.UnspecifiedDateTime

Bezwstydna wtyczka:

Wiele osób podzieliło się ze mną, że uważają tę analogię za niezwykle cenną, dlatego umieściłem ją w moim kursie Pluralsight, Data i godzina Podstawy . Szczegółowy opis analogii kamery znajduje się w drugim module „Kontekst ma znaczenie” w klipie zatytułowanym „Czas kalendarzowy a czas natychmiastowy”.

Matt Johnson-Pint
źródło
4
@ZackJannsen Jeśli masz DateTimeOffsetw C #, powinieneś zachować to DATETIMEOFFSETna SQL Server. DATETIME2lub po prostu DATETIME(w zależności od wymaganego zakresu) są odpowiednie dla regularnych DateTimewartości. Tak - czas lokalny można rozwiązać z dowolnej pary stref czasowych + dto lub utc. Różnica polega na tym, czy zawsze chcesz obliczać reguły przy każdym rozwiązaniu, czy też chcesz je wstępnie obliczyć? W wielu przypadkach (czasem z powodów prawnych) lepszym wyborem jest DTO.
Matt Johnson-Pint
3
@ZackJannsen W drugiej części twojego pytania zaleciłbym zrobienie jak najwięcej po stronie serwera. JavaScript nie jest świetny do obliczania stref czasowych. Jeśli musisz to zrobić, użyj jednej z tych bibliotek . Ale po stronie serwera jest najlepsza. Jeśli masz inne, bardziej szczegółowe pytania, zacznij od nich nowe pytanie SO, a ja odpowiem, jeśli będę mógł. Dzięki.
Matt Johnson-Pint
4
@JaoaoLeme - To zależy od tego, skąd je otrzymałeś. Masz rację, że jeśli powiesz DateTimeOffset.Nowna serwerze, rzeczywiście otrzymasz przesunięcie serwera. Chodzi o to, że DateTimeOffsettyp może zachować to przesunięcie. Możesz równie łatwo zrobić to na kliencie, wysłać go na serwer, a wtedy twój serwer będzie wiedział przesunięcie klienta.
Matt Johnson-Pint,
8
Naprawdę uwielbiam analogię aparatu.
Sachin Kainth,
2
Tak, to jest poprawne. Tyle że DTO jest przechowywane jako para (czas lokalny, przesunięcie), a nie para (czas utc, przesunięcie). Innymi słowy, przesunięcie względem UTC jest już odzwierciedlone w czasie lokalnym. Aby przekonwertować z powrotem na utc, odwróć znak przesunięcia i zastosuj go do czasu lokalnego.
Matt Johnson-Pint,
328

Od Microsoft:

Te zastosowania dla wartości DateTimeOffset są znacznie bardziej powszechne niż dla wartości DateTime. W związku z tym DateTimeOffset należy uznać za domyślny typ daty i godziny dla rozwoju aplikacji.

źródło: „Wybór pomiędzy DateTime, DateTimeOffset, TimeSpan i TimeZoneInfo” , MSDN

Używamy DateTimeOffsetprawie wszystkiego, ponieważ nasza aplikacja zajmuje się konkretnymi momentami w czasie (np. Kiedy rekord został utworzony / zaktualizowany). Na marginesie, używamy również DATETIMEOFFSETw SQL Server 2008.

Uważam, DateTimeże jest przydatny, gdy chcesz poradzić sobie tylko z datami, tylko czasami lub z ogólnymi. Na przykład, jeśli masz alarm, który chcesz zgaśnie codziennie o 7 rano, można zapisać, że w sposób DateTimewykorzystujący DateTimeKindod Unspecifiedbo chcesz go zgaśnie o 7 rano, niezależnie od DST. Ale jeśli chcesz przedstawić historię zdarzeń alarmowych, skorzystasz DateTimeOffset.

Zachowaj ostrożność, używając kombinacji, DateTimeOffseta DateTimezwłaszcza podczas przypisywania i porównywania typów. Ponadto porównuj tylko DateTimetakie same wystąpienia, DateTimeKindponieważ DateTimepodczas porównywania ignorowane jest przesunięcie strefy czasowej.

Glina
źródło
146
Przyjęta odpowiedź jest zbyt długa, a analogia jest napięta, jest to znacznie lepsza i bardziej zwięzła odpowiedź IMO.
Nexus mówi
10
Powiem tylko, że podoba mi się ta odpowiedź, i głosowałem. Chociaż w ostatniej części - nawet upewnienie Kindsię, że są takie same, porównanie może być błędne. Jeśli obie strony DateTimeKind.Unspecifiedtak naprawdę nie wiedzą, że pochodzą z tej samej strefy czasowej. Jeśli obie strony są DateTimeKind.Local, większość porównań będzie w porządku, ale nadal możesz mieć błędy, ponieważ jedna strona jest niejednoznaczna w lokalnej strefie czasowej. Naprawdę tylko DateTimeKind.Utcporównania są niezawodne i tak, DateTimeOffsetzwykle jest preferowane. (Na zdrowie!)
Matt Johnson-Pint
1
+1 Dodałbym do tego: Wybrany typ danych powinien odzwierciedlać twoje zamiary. Nie używaj DateTimeOffset wszędzie, tylko dlatego. Jeśli przesunięcie ma znaczenie dla twoich obliczeń i odczytu z / utrwalania do bazy danych, użyj DateTimeOffset. Jeśli to nie ma znaczenia, użyj DateTime, aby zrozumieć (po prostu patrząc na DataType), że przesunięcie nie powinno mieć żadnego wpływu, a czasy powinny pozostać względne w stosunku do lokalizacji serwera / maszyny, na której działa kod C #.
MikeTeeVee
„Ale jeśli chcesz przedstawić historię zdarzeń alarmowych, skorzystaj z funkcji DateTimeOffset”. Czy chciałbyś wyjaśnić, dlaczego tak jest? Mógłbym to wypełnić, czytając wszystkie informacje na tej stronie, ale być może mógłbyś również podać takie informacje w sposób łatwy do odczytania. To bardzo czytelna odpowiedź.
Barrosy,
77

DateTime może przechowywać tylko dwa różne czasy, czas lokalny i UTC. Właściwość Kind wskazuje, która.

DateTimeOffset rozwija się dzięki temu, że może przechowywać czasy lokalne z dowolnego miejsca na świecie. Przechowuje również przesunięcie między czasem lokalnym a czasem UTC. Zwróć uwagę, że DateTime nie może tego zrobić, chyba że dodasz dodatkowego członka do swojej klasy, aby zapisać to przesunięcie UTC. Lub tylko kiedykolwiek współpracuje z UTC. Co samo w sobie jest świetnym pomysłem.

Hans Passant
źródło
33

Jest kilka miejsc, w których DateTimeOffsetma to sens. Jednym z nich jest sytuacja powtarzających się zdarzeń i czasu letniego. Powiedzmy, że chcę ustawić alarm, aby uruchamiał się codziennie o 9 rano. Jeśli użyję reguły „zapisz jako UTC, wyświetlaj jako czas lokalny”, to alarm wyłączy się w innym czasie, gdy obowiązuje czas letni.

Prawdopodobnie są też inne, ale powyższy przykład jest właściwie tym, na który natknąłem się w przeszłości (to było przed dodaniem DateTimeOffsetdo BCL - moim rozwiązaniem było wtedy jawne przechowywanie czasu w lokalnej strefie czasowej i oszczędzanie informacje o strefie czasowej obok: w zasadzie to, co DateTimeOffsetdziała wewnętrznie).

Dean Harding
źródło
12
DateTimeOffset nie naprawia problemu z DST
JarrettV
2
Korzystanie z klasy TimeZoneInfo niesie reguły dla czasu letniego. jeśli korzystasz z .net 3.5 lub nowszej, użyj klas TimeZone lub TimeZoneInfo, aby radzić sobie z datami, które muszą obsługiwać czas letni w połączeniu z przesunięciem strefy czasowej.
Zack Jannsen
1
Tak, dobry przykład wyjątku (aplikacja alarmowa), ale kiedy czas jest ważniejszy niż data, naprawdę powinieneś przechowywać to oddzielne w strukturze danych harmonogramu dla aplikacji, tj. Typ wystąpienia = Codziennie i czas = 09:00. Chodzi o to, że programista musi wiedzieć, jakiego rodzaju datę rejestrują, obliczają lub prezentują użytkownikom. Zwłaszcza aplikacje są bardziej globalne, teraz mamy internet jako standard i duże sklepy z aplikacjami, dla których można pisać oprogramowanie. Jako węzeł boczny chciałbym również, aby Microsoft dodał osobną strukturę daty i godziny.
Tony Wall,
3
Podsumowując komentarze Jarretta i Zacka: Wygląda na to, że sam DateTimeOffset nie poradzi sobie z problemem DST, ale poradzi sobie z tym DateTimeOffset w połączeniu z TimeZoneInfo. Nie różni się to od DateTime, gdzie rodzaj to Utc. W obu przypadkach muszę znać strefę czasową (nie tylko przesunięcie) kalendarza, na który rzutuję moment. (Mogę zapisać to w profilu użytkownika lub uzyskać to od klienta (np. Windows), jeśli to możliwe). Brzmi dobrze?
Jeremy Cook
„Jest kilka miejsc, w których DateTimeOffset ma sens”. --- Prawdopodobnie częściej ma to sens niż nie.
Ronnie Overby
23

Najważniejsze jest to, że DateTime nie przechowuje informacji o strefie czasowej, podczas gdy DateTimeOffset tak.

Mimo że DateTime rozróżnia UTC od lokalnego, nie ma absolutnie żadnego wyraźnego przesunięcia strefy czasowej z tym związane. W przypadku serializacji lub konwersji zostanie użyta strefa czasowa serwera. Nawet jeśli ręcznie utworzysz czas lokalny, dodając minuty w celu przesunięcia czasu UTC, nadal możesz uzyskać bit w kroku serializacji, ponieważ (z powodu braku wyraźnego przesunięcia w DateTime) użyje przesunięcia strefy czasowej serwera.

Na przykład, jeśli szeregujesz wartość DateTime za pomocą Kind = Local przy użyciu Json.Net i formatu daty ISO, otrzymasz ciąg podobny do 2015-08-05T07:00:00-04. Zauważ, że ostatnia część (-04) nie miała nic wspólnego z twoim DateTime ani jakimkolwiek przesunięciem, którego użyłeś do obliczenia ... to po prostu przesunięcie strefy czasowej serwera.

Tymczasem DateTimeOffset wyraźnie zawiera przesunięcie. Może nie zawierać nazwy strefy czasowej, ale przynajmniej zawiera przesunięcie, a jeśli go serializujesz, otrzymasz jawnie uwzględnione przesunięcie w swojej wartości zamiast tego, co dzieje się na lokalnym serwerze.

Triynko
źródło
14
The most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Biorąc
9
DateTimeOffset NIE przechowuje informacji o strefie czasowej. Dokument MS zatytułowany „Wybieranie między DateTime, DateTimeOffset, TimeSpan i TimeZoneInfo” określa następujące stwierdzenie: „Wartość DateTimeOffset nie jest powiązana z określoną strefą czasową, ale może pochodzić z dowolnej z różnych stref czasowych”. To powiedziawszy, strefa czasowa DateTimeOffset IS AWARE, zawierająca przesunięcie względem UTC, co robi różnicę i dlatego MS zaleca klasę domyślną, gdy zajmuje się tworzeniem aplikacji zajmujących się informacjami o dacie. Jeśli naprawdę zależy ci na tym, z której konkretnej strefy czasowej pochodzą dane, musisz to zachować osobno
stonedauwg
1
Tak, ale jak pokazano w wielu miejscach, + lub - godziny nic nie mówią o strefie czasowej, w której się znajdujesz, i jest ostatecznie bezużyteczne. W zależności od tego, co musisz zrobić, możesz równie dobrze przechowywać datę i godzinę jak Rodzaj. Nieokreślone, a następnie przechowywać identyfikator jej strefy czasowej i myślę, że naprawdę lepiej.
Arwin,
13

Ten fragment kodu firmy Microsoft wyjaśnia wszystko:

// Find difference between Date.Now and Date.UtcNow
  date1 = DateTime.Now;
  date2 = DateTime.UtcNow;
  difference = date1 - date2;
  Console.WriteLine("{0} - {1} = {2}", date1, date2, difference);

  // Find difference between Now and UtcNow using DateTimeOffset
  dateOffset1 = DateTimeOffset.Now;
  dateOffset2 = DateTimeOffset.UtcNow;
  difference = dateOffset1 - dateOffset2;
  Console.WriteLine("{0} - {1} = {2}", 
                    dateOffset1, dateOffset2, difference);
  // If run in the Pacific Standard time zone on 4/2/2007, the example
  // displays the following output to the console:
  //    4/2/2007 7:23:57 PM - 4/3/2007 2:23:57 AM = -07:00:00
  //    4/2/2007 7:23:57 PM -07:00 - 4/3/2007 2:23:57 AM +00:00 = 00:00:00
Mojtaba
źródło
9

Większość odpowiedzi jest dobra, ale pomyślałem o dodaniu kilku linków MSDN, aby uzyskać więcej informacji

dekdev
źródło
7

Główną różnicą jest to, że DateTimeOffsetmożna go używać w połączeniu z TimeZoneInfokonwertowaniem na czasy lokalne w strefach czasowych innych niż bieżąca.

Jest to przydatne w aplikacji serwerowej (np. ASP.NET), do której użytkownicy mają dostęp w różnych strefach czasowych.

Joe
źródło
3
@Bugeo Bugeo jest prawdą, ale istnieje ryzyko. Możesz porównać dwa DateTimes, najpierw wywołując „ToUniversalTime” na każdym. Jeśli masz dokładnie jedną wartość w porównaniu, czyli DateTimeKind = Nieokreślona, ​​strategia zawiedzie. Ten potencjał niepowodzenia jest powodem, aby rozważyć DateTimeOffset w porównaniu z DateTime, gdy wymagane są konwersje na czas lokalny.
Zack Jannsen
Jak wyżej, myślę, że w tym scenariuszu lepiej jest przechowywać TimeZoneId niż przy użyciu DateTimeOffset, co ostatecznie nic nie znaczy.
Arwin,
2

Jedyną negatywną stroną DateTimeOffset, którą widzę, jest to, że Microsoft „zapomniał” (z założenia), aby obsługiwać go w swojej klasie XmlSerializer. Ale od tego czasu został dodany do klasy narzędzia XmlConvert.

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

Mówię, śmiało, i używaj DateTimeOffset i TimeZoneInfo ze względu na wszystkie korzyści, po prostu uważaj podczas tworzenia encji, które będą lub mogą być serializowane do lub z XML (wtedy wszystkie obiekty biznesowe).

Tony Wall
źródło