DateTime vs DateTimeOffset


742

В даний час у нас є стандартний спосіб поводження з .NET DateTimeв обізнаності з TimeZone: Щоразу, коли ми виробляємо це, DateTimeми робимо це в UTC (наприклад, використовуючи DateTime.UtcNow), і коли ми показуємо його, ми перетворюємо назад з UTC в місцевий час користувача .

Це добре працює, але я читав про DateTimeOffsetте, як він фіксує локальний та UTC час у самому об’єкті. Отже, питання полягає в тому, які б переваги використання було DateTimeOffsetпорівняно з тим, що ми вже робили?


3
Нижче наведено кілька чудових відповідей. Але мені все ще залишається цікаво, що, якщо що, може переконати вас почати використовувати DateTimeOffset.
HappyNomad


Що стосується зберігання, цікаво також stackoverflow.com/questions/4715620/… .
Деян

Відповіді:


1169

DateTimeOffsetявляє собою подання миттєвого часу (також відомого як абсолютний час ). Маючи на увазі, я маю на увазі момент, який є універсальним для всіх (не враховуючи високосних секунд або релятивістські ефекти розширення часу ). Ще один спосіб представити миттєвий час - це " DateTimeде .Kindє" DateTimeKind.Utc.

Це відрізняється від календарного часу (також відомого як громадянський час ), який є позицією в чиємусь календарі, і є багато різних календарів по всьому світу. Ці календарі ми називаємо часовими поясами . Час календаря представлено символом, DateTimeде .Kindє DateTimeKind.Unspecified, або DateTimeKind.Local. І .Localмає сенс лише в сценаріях, де ви розумієте, де розташований комп'ютер, який використовує результат. (Наприклад, робоча станція користувача)

Тож чому DateTimeOffsetзамість UTC DateTime? Вся справа в перспективі. Давайте скористаємось аналогією - будемо робити вигляд, що фотографи.

Уявіть, що ви стоїте на календарній шкалі часу, вказуючи камеру на людину на миттєвій часовій шкалі, розкладеній перед вами. Ви розміщуєте камеру за правилами свого часового поясу, які періодично змінюються через літній час або через інші зміни в законодавчому визначенні вашого часового поясу. (У вас немає стійкої руки, тому ваша камера хитка.)

Людина, що стоїть на фотографії, побачила б кут, під яким вийшла ваша камера. Якби інші фотографували, вони могли бути з різних куточків. Це те, що представляє Offsetчастина DateTimeOffsetпредставників.

Тож якщо ви позначаєте свою камеру "Східним часом", іноді ви вказуєте від -5, а іноді - від -4. У всьому світі є камери, на яких позначено різні речі, і всі вказують на одну і ту ж миттєву шкалу часу з різних ракурсів. Деякі з них знаходяться поруч (або зверху), тому просто знаючи зміщення недостатньо, щоб визначити, з яким часовим поясом пов’язаний час.

А як щодо UTC? Ну, це одна камера, яка гарантовано матиме стійку руку. Це на штативі, міцно закріпленому в землі. Нікуди не дінеться. Ми називаємо його кут точки зору нульовим зміщенням.

Моментальна візуалізація часу проти календаря

Отже - що нам говорить ця аналогія? Він пропонує інтуїтивні вказівки,

  • Якщо ви представляєте час відносно якогось місця, зокрема, представляйте його в календарному часі за допомогою а DateTime. Просто будьте впевнені, що ви ніколи не плутаєте один календар з іншим. Unspecifiedмає бути вашим припущенням. Localкорисний лише звідки DateTime.Now. Наприклад, я можу отримати DateTime.Nowі зберегти його в базі даних, але коли я його завантажую, я повинен припустити, що він є Unspecified. Я не можу розраховувати, що мій місцевий календар - це той самий календар, з якого він був взятий.

  • Якщо ви завжди повинні бути впевнені в моменті, переконайтесь, що ви представляєте миттєвий час. Використовуйте DateTimeOffsetдля його виконання або використовуйте UTC DateTimeза умовами.

  • Якщо вам потрібно відстежити момент миттєвого часу, але ви хочете також знати "Який час користувач думав, що це в їхньому місцевому календарі?" - тоді ви повинні використовувати a DateTimeOffset. Це дуже важливо, наприклад, для систем хронометражу - як для технічних, так і для юридичних питань.

  • Якщо вам коли-небудь потрібно змінити записаний раніше DateTimeOffset- вам не вистачає інформації лише в зміщенні, щоб переконатися, що новий зсув все ще актуальний для користувача. Ви також повинні зберегти ідентифікатор часового поясу (подумайте - мені потрібна назва цієї камери, щоб я міг сфотографуватись, навіть якщо позиція змінилася).

    Слід також зазначити, що для цього Noda Time має представництво ZonedDateTime, тоді як бібліотека базового класу .Net не має нічого подібного. Вам потрібно буде зберігати DateTimeOffsetі TimeZoneInfo.Idзначення, і значення.

  • Іноді вам потрібно представити календарний час, який є локальним для того, щоб "хто дивиться на це". Наприклад, при визначенні того, що сьогодні означає. Сьогодні завжди опівночі до півночі, але вони являють собою майже нескінченну кількість діапазонів, що перетинаються на миттєвій шкалі часу. (На практиці у нас є обмежена кількість часових поясів, але ви можете виразити зміщення до кліща). Тому в цих ситуаціях переконайтеся, що ви розумієте, як обмежити "хто просить?" запитання до єдиного часового поясу або вирішити їх переклад назад у миттєвий час, якщо це доречно.

Ось кілька інших невеликих шматочків щодо DateTimeOffsetрезервної копії цієї аналогії, а також кілька порад щодо її правильності:

  • Якщо порівнювати два DateTimeOffsetзначення, вони спочатку нормалізуються до нульового зміщення перед порівнянням. Іншими словами, 2012-01-01T00:00:00+00:00і 2012-01-01T02:00:00+02:00посилаються на той самий миттєвий момент, і тому є рівнозначними.

  • Якщо ви робите якісь - або модульного тестування і повинні бути впевнені в офсетного, випробування як в DateTimeOffsetвартості, і .Offsetвласності окремо.

  • Існує одностороння неявна конверсія, вбудована в .Net фреймворк, що дозволяє переходити DateTimeдо будь-якого DateTimeOffsetпараметра або змінної. При цьому, питання . Якщо ви передаєте тип UTC, він здійснюватиметься з нульовим зміщенням, але якщо ви передасте або, або він вважатиметься локальним . В основному рамки говорять: "Ну, ви попросили мене перетворити календарний час на миттєвий час, але я не маю уявлення, звідки це взялося, тому я просто збираюся використовувати місцевий календар". Це величезна проблема, якщо ви завантажуєте не визначене на комп'ютер з іншим часовим поясом. (IMHO - це повинно кинути виняток, але це не так.).Kind.Local.UnspecifiedDateTime

Безсоромний штекер:

Багато людей поділилися зі мною, що вважають цю аналогію надзвичайно цінною, тому я включив її у свій курс з питань плюралізму, Основи дати та часу . Покрокове ознайомлення з аналогією камери ви знайдете у другому модулі "Контекстні питання" у кліпі під назвою "Час календаря проти миттєвого часу".


4
@ZackJannsen Якщо у вас є DateTimeOffsetC #, то вам слід наполегливо використовувати це DATETIMEOFFSETв SQL Server. DATETIME2або просто DATETIME(залежно від необхідного діапазону) є нормальними для звичайних DateTimeзначень. Так - ви можете вирішити місцевий час з будь-якого парування часового поясу + dto або utc. Різниця полягає в тому, що ти завжди хочеш обчислювати правила з кожним дозволом, чи ти хочеш їх попередньо обчислити? У багатьох випадках (іноді з юридичних питань) кращий вибір - довідковий документ.
Метт Джонсон-Пінт

3
@ZackJannsen У другій частині вашого питання я рекомендую зробити якомога більше на сервері. Javascript не так підходить для розрахунку часового поясу. Якщо ви повинні це зробити, використовуйте одну з цих бібліотек . Але найкраща сторона сервера. Якщо у вас є інші більш детальні запитання, будь ласка, запустіть нове запитання щодо них, і я відповім, якщо зможу. Дякую.
Метт Джонсон-Пінт

4
@JoaoLeme - Це залежить від того, звідки ви її отримали. Ви праві, що якщо ви скажете DateTimeOffset.Nowна сервері, ви дійсно отримаєте компенсацію сервера. Справа в тому, що DateTimeOffsetтип може зберегти це зміщення. Ви можете так само легко зробити це на клієнті, надіслати його на сервер, і тоді ваш сервер буде знати компенсацію клієнта.
Метт Джонсон-Пінт

8
Дуже любить аналогію камери.
Сахін Карін

2
Так, це правильно. За винятком того, що DTO зберігається як пара (локальний час, зміщення), а не пара (utc time, offset). Іншими словами, зміщення від UTC вже відбивається на місцевому часі. Щоб перетворити назад в utc, переверніть знак зміщення і застосуйте його до місцевого часу.
Метт Джонсон-Пінт

328

Від Microsoft:

Ці використання для DateTimeOffset значення набагато частіше, ніж значення для DateTime. Як результат, DateTimeOffset слід вважати датою та типом часу для розробки програми.

джерело: "Вибір між DateTime, DateTimeOffset, TimeSpan та TimeZoneInfo" , MSDN

Ми використовуємо DateTimeOffsetмайже все, оскільки наша програма стосується конкретних моментів часу (наприклад, коли було створено / оновлено запис). В якості бічної примітки ми використовуємо і DATETIMEOFFSETв SQL Server 2008.

Я вважаю DateTime, що це корисно, коли ви хочете мати справу лише з датами, лише часом, або мати справу з будь-яким загальним. Наприклад, якщо у вас є тривога , що ви хочете , щоб піти кожен день о 7 годині ранку, ви могли б зберігати , що в DateTimeвикористовує DateTimeKindз Unspecifiedтому , що ви хочете , щоб піти в 7 ранку , незалежно від DST. Але якщо ви хочете представити історію виникнення тривожних ситуацій, ви б використали DateTimeOffset.

Слід дотримуватися обережності при використанні поєднання DateTimeOffsetі DateTimeособливо при призначенні і порівняння між типами. Крім того, порівняйте лише DateTimeті самі випадки, DateTimeKindоскільки DateTimeігнорує зміщення часового поясу при порівнянні.


146
Прийнята відповідь занадто довга, а аналогія напружена, це набагато краща і більш лаконічна відповідь ІМО.
Nexus повідомляє

10
Я просто скажу, що мені ця відповідь теж подобається і підтримується. Хоча в останній частині - навіть забезпечення Kindоднакового, порівняння може бути помилковим. Якщо у вас обох сторін, DateTimeKind.Unspecifiedви насправді не знаєте, що вони прийшли з одного часового поясу. Якщо обидві сторони є DateTimeKind.Local, більшість порівнянь буде добре, але ви все одно можете мати помилки - одна сторона неоднозначна в локальному часовому поясі. Дійсно лише DateTimeKind.Utcпорівняння є дурними, і так, DateTimeOffsetяк правило, кращим є. (Ура!)
Метт Джонсон-Пінт

1
+1 Я додам до цього: обраний вами тип даних повинен відображати ваш намір. Не використовуйте DateTimeOffset скрізь, просто викликайте. Якщо зміщення має значення для ваших обчислень та читання-від / зберігання-до бази даних, тоді використовуйте DateTimeOffset. Якщо це не має значення, то використовуйте DateTime, щоб ви зрозуміли (лише подивившись на DataType), що зміщення не повинно мати відношення, а Times повинен залишатися відносно місцевості сервера / машини, на якій працює ваш C # код.
MikeTeeVee

"Але якщо ви хочете представити історію виникнення тривожних сигналів, ви використовуєте DateTimeOffset." Чи хотіли б ви пояснити, чому ви вважаєте, що це так? Я міг би заповнити це, прочитавши всю інформацію на цій сторінці, але, можливо, ви могли б надати таку інформацію легко і для читання. Це дуже читабельна відповідь.
Barrosy

77

DateTime здатний зберігати лише два різні часи - місцевий час та UTC. Вид власності показує , який.

DateTimeOffset розширює цю проблему завдяки можливості зберігати місцеві часи з будь-якої точки світу. Він також зберігає зміщення між місцевим часом та UTC. Зверніть увагу, як DateTime не може цього зробити, якщо ви не додасте додаткового члена до свого класу для зберігання цього зміщення за UTC. Або тільки коли-небудь працювати з UTC. Що саме по собі є чудовою ідеєю btw.


33

Є кілька місць, де DateTimeOffsetє сенс. Перше - це коли ти маєш справу з повторюваними подіями та літним часом. Скажімо, я хочу налаштувати будильник о 9 годині ранку. Якщо я використовую правило "зберігати як UTC, відображати як місцевий час", то сигнал тривоги буде вимикатися в інший час, коли діє літній час.

Напевно, є й інші, але наведений вище приклад насправді той, на який я стикався в минулому (це було до моменту додавання DateTimeOffsetдо BCL - моє рішення на той час полягало в тому, щоб явно зберігати час у локальному часовому поясі та зберігати інформація про часовий пояс поруч із ним: в основному те, що DateTimeOffsetробиться всередині).


12
DateTimeOffset не вирішує проблему DST
JarrettV

2
Використання класу TimeZoneInfo несе правила для DST. якщо ви перебуваєте на .net 3.5 або пізнішої версії, тоді використовуйте класи TimeZone або TimeZoneInfo, щоб мати справу з датами, які повинні обробляти літній час разом із зміщенням часового поясу.
Zack Jannsen

1
Так, хороший приклад винятку (додаток тривожної сигналізації), але коли час важливіший за дату, ви дійсно повинні зберігати це розділене у вашій структурі даних графіку програми, тобто тип події = Щодня та час = 09:00. Сенс у тому, що розробник повинен знати про тип дати, яку вони записують, обчислюють або представляють користувачам. Особливо додатки, як правило, більш глобальні, зараз у нас є Інтернет як стандартні, так і великі магазини додатків для написання програмного забезпечення. Як бічний вузол я також хотів би побачити, як Microsoft додає окрему структуру дати та часу.
Тоні Уолл

3
Підсумовуючи коментарі Джарретта та Зака: Це здається, що DateTimeOffset сам по собі не впорається з проблемою DST, але з використанням DataTimeOffset спільно з TimeZoneInfo це впорається. Це не відрізняється від DateTime, де видом є Utc. В обох випадках я повинен знати часовий пояс (а не лише зміщення) календаря, в який я проектую момент. (Я можу зберігати це у профілі користувача або отримати його від клієнта (наприклад, Windows), якщо це можливо). Звучить правильно?
Джеремі Кук

"Є кілька місць, де DateTimeOffset має сенс." --- Можливо, це частіше має сенс, ніж ні.
Ронні Овербі

23

Найважливіша відмінність полягає в тому, що DateTime не зберігає інформацію про часовий пояс, тоді як DateTimeOffset робить.

Незважаючи на те, що DateTime відрізняє між UTC та Local, немає явного зміщення часового поясу, пов'язаного з ним. Якщо ви робите будь-яку серіалізацію або перетворення, буде використаний часовий пояс сервера. Навіть якщо ви вручну створюєте локальний час, додаючи хвилини, щоб компенсувати час UTC, ви все одно можете отримати біт на етапі серіалізації, оскільки (через відсутність явного зміщення в DateTime) він буде використовувати зсув часового поясу сервера.

Наприклад, якщо ви серіалізуєте значення DateTime за допомогою Kind = Local за допомогою Json.Net та формату дати ISO, ви отримаєте рядок типу 2015-08-05T07:00:00-04. Зауважте, що остання частина (-04) не мала нічого спільного з вашим DateTime або будь-яким зміщенням, яке ви використовували для його обчислення ... це лише суто зрушення часового поясу сервера.

Тим часом DateTimeOffset явно включає зміщення. Він не може містити назву часового поясу, але принаймні він включає зміщення, і якщо ви його серіалізуєте, ви отримаєте явно включене зміщення у вашій вартості замість того, який би місцевий час сервера не відбувся.


14
З усіма вищезазначеними відповідями мені цікаво, чому ніхто не потрудився написати ваше єдине речення, яке підсумовує це всеThe most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Кораєм

9
DateTimeOffset НЕ зберігає інформацію про часовий пояс. Документ MS під назвою "Вибір між DateTime, DateTimeOffset, TimeSpan та TimeZoneInfo" вказує це: "Значення DateTimeOffset не прив'язане до певного часового поясу, але може походити з будь-якого з різних часових поясів". З цього приводу, часовий пояс DateTimeOffset IS AWARE, що містить зміщення від UTC, що робить усе різницею, і саме тому MS рекомендує клас за замовчуванням при роботі з розробкою додатків, що стосується інформації про дату. Якщо вам по-справжньому цікаво, з якого конкретного часового поясу надійшли дані, ви повинні зберегти це окремо
stonedauwg

1
Так, але, як це було показано в багатьох місцях, + або - години нічого не говорить про те, в якому часовому поясі ви опинилися і в кінцевому рахунку марний. Залежно від того, що вам потрібно зробити, ви можете так само добре зберігати дату, як Kind.Unspecified, а потім зберігати ідентифікатор свого часового поясу, і я думаю, що вам насправді краще.
Арвін

13

Цей фрагмент коду від Microsoft пояснює все:

// 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

9

Більшість відповідей хороші, але я подумав додати ще кілька посилань MSDN для отримання додаткової інформації



7

Основна відмінність полягає в тому, що DateTimeOffsetїх можна застосовувати спільно з TimeZoneInfoперетворенням на місцеві часи в часових поясах, відмінних від поточного.

Це корисно для серверного додатку (наприклад, ASP.NET), до якого користувачі отримують доступ у різні часові пояси.


3
@ Buugeo Bugeo правда, але ризик є. Ви можете порівняти два DateTimes, попередньо зателефонувавши "ToUniversalTime" на кожному. Якщо у порівнянні є саме одне значення, яке є DateTimeKind = Не визначено, ваша стратегія вийде з ладу. Цей потенціал збою є причиною врахування DateTimeOffset за DateTime, коли потрібні переходи на місцевий час.
Zack Jannsen

Як і вище, я думаю, що в цьому сценарії вам краще, ніж зберігати TimeZoneId, ніж використовувати DateTimeOffset, що в кінцевому рахунку нічого не означає.
Арвін

2

Єдиною негативною стороною DateTimeOffset, яку я бачу, є те, що Microsoft «забув» (за дизайном) підтримати його у своєму класі XmlSerializer. Але з тих пір він був доданий у клас утиліти XmlConvert.

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

Я кажу, що використовуйте DateTimeOffset та TimeZoneInfo через усі переваги, будьте обережні, створюючи об'єкти, які будуть або можуть бути серіалізовані до XML або з нього (усі бізнес-об’єкти тоді).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.