DateTime против DateTimeOffset

743

В настоящее время у нас есть стандартный способ работы с .NET DateTimeс учетом TimeZone: всякий раз, когда мы производим его, DateTimeмы делаем это в UTC (например, используя DateTime.UtcNow), и всякий раз, когда мы его отображаем, мы конвертируем обратно из UTC в местное время пользователя. ,

Это прекрасно работает, но я читал о DateTimeOffsetтом, как он фиксирует местное и UTC время в самом объекте. Итак, вопрос в том, какие преимущества использования DateTimeOffsetпо сравнению с тем, что мы уже делали?

Дэвид Рейс
источник
3
Ниже приведены отличные ответы. Но я все еще задаюсь вопросом, что могло бы убедить вас начать использовать DateTimeOffset.
HappyNomad
1
Также можно посмотреть, когда вы бы предпочли дату-время-
дату-смещение
Когда дело доходит до хранилища, интересен stackoverflow.com/questions/4715620/…
Деян

Ответы:

1168

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по соглашению.

  • Если вам нужно отследить мгновенное время, но вы также хотите знать, «во сколько время пользователь думал, что это было в его локальном календаре?» - тогда вы должны использовать DateTimeOffset. Это очень важно, например, для систем хронометража - как для технических, так и для юридических вопросов.

  • Если вам когда-либо понадобится изменить ранее записанный файл DateTimeOffset- у вас недостаточно информации только для одного смещения, чтобы гарантировать, что новое смещение по-прежнему актуально для пользователя. Вы также должны сохранить идентификатор часового пояса (подумайте - мне нужно имя этой камеры, чтобы я мог сделать новый снимок, даже если положение изменилось).

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

  • Иногда вам может потребоваться указать календарное время, которое является локальным для «того, кто смотрит на него». Например, при определении того, что сегодня означает. Сегодня всегда полночь - полночь, но они представляют собой почти бесконечное количество перекрывающихся диапазонов на мгновенной временной шкале. (На практике у нас есть ограниченное количество часовых поясов, но вы можете выразить смещения вплоть до отметки). Поэтому в этих ситуациях убедитесь, что вы понимаете, как ограничить «кто спрашивает?» вопрос до одного часового пояса, или сделайте перевод их обратно в мгновенное время по мере необходимости.

Вот еще несколько маленьких подробностей об DateTimeOffsetэтой аналогии, а также несколько советов, как сохранить ее прямо:

  • Если вы сравниваете два DateTimeOffsetзначения, они сначала нормализуются к нулевому смещению перед сравнением. Другими словами, 2012-01-01T00:00:00+00:00и 2012-01-01T02:00:00+02:00относятся к одному и тому же мгновенному моменту и, следовательно, эквивалентны.

  • Если вы делаете какие - либо модульного тестирования и должны быть уверены в офсетной, испытания как в DateTimeOffsetстоимости, и .Offsetсобственности отдельно.

  • В платформу .Net встроено одностороннее неявное преобразование, которое позволяет передавать a DateTimeв любой DateTimeOffsetпараметр или переменную. При этом, вопросы . Если вы передаете тип UTC, он будет иметь нулевое смещение, но если вы передадите либо, либо , он будет локальным . Фреймворк в основном говорит: «Ну, вы попросили меня перевести календарное время в мгновенное, но я понятия не имею, откуда это пришло, поэтому я просто собираюсь использовать местный календарь». Это огромная ошибка, если вы загружаете неопределенное на компьютер с другим часовым поясом. (ИМХО - это должно вызвать исключение - но это не так.).Kind.Local.UnspecifiedDateTime

Бесстыдная вилка:

Многие люди поделились со мной, что они находят эту аналогию чрезвычайно полезной, поэтому я включил ее в свой курс по Pluralsight, «Основы даты и времени» . Вы найдете пошаговое руководство по аналогии с камерой во втором модуле «Вопросы контекста» в клипе под названием «Время календаря и мгновенное время».

Мэтт Джонсон-Пинт
источник
4
@ZackJannsen Если у вас есть DateTimeOffsetв C #, то вы должны сохранить это DATETIMEOFFSETв SQL Server. DATETIME2или просто DATETIME(в зависимости от требуемого диапазона) хорошо для обычных DateTimeзначений. Да - вы можете определить местное время из любой пары timezone + dto или utc. Разница в том, что вы всегда хотите вычислять правила для каждого решения или же хотите их пересчитать? Во многих случаях (иногда по юридическим причинам) DTO - лучший выбор.
Мэтт Джонсон-Пинт
3
@ZackJannsen Для второй части вашего вопроса, я бы порекомендовал сделать как можно больше на стороне сервера. Javascript не очень хорош для расчета часовых поясов. Если вы должны это сделать, используйте одну из этих библиотек . Но на стороне сервера лучше. Если у вас есть другие более подробные вопросы, пожалуйста, начните новый SO вопрос для них, и я отвечу, если смогу. Спасибо.
Мэтт Джонсон-Пинт
4
@JoaoLeme - это зависит от того, откуда вы его получили. Вы правы, что если вы скажете DateTimeOffset.Nowна сервере, вы действительно получите смещение сервера. Дело в том, что DateTimeOffsetтип может сохранить это смещение. Вы также можете легко сделать это на клиенте, отправить его на сервер, и тогда ваш сервер узнает смещение клиента.
Мэтт Джонсон-Пинт
8
Очень люблю аналогию с камерой.
Сачин Каинт
2
Да, это правильно. За исключением того, что DTO хранится в виде пары (местное время, смещение), а не пары (время utc, смещение). Другими словами, смещение от UTC уже отражено по местному времени. Чтобы преобразовать обратно в utc, инвертируйте знак смещения и примените его к местному времени.
Мэтт Джонсон-Пинт
329

От Microsoft:

Эти значения значений DateTimeOffset встречаются гораздо чаще, чем значения DateTime. В результате DateTimeOffset следует считать типом даты и времени по умолчанию для разработки приложений.

Источник: «Выбор между DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo» , MSDN

Мы используем DateTimeOffset почти все, так как наше приложение имеет дело с определенными моментами времени (например, когда запись была создана / обновлена). В качестве примечания мы также используем DATETIMEOFFSETSQL Server 2008.

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

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

глина
источник
146
Принятый ответ слишком длинный, и аналогия натянута, это гораздо лучший и более краткий ответ IMO.
говорит нексус
10
Я просто скажу, что мне тоже нравится этот ответ, и я проголосовал. Хотя в последней части - даже обеспечение Kindтого же, сравнение может быть ошибочным. Если обе стороны DateTimeKind.Unspecifiedне знают, что они пришли из одного и того же часового пояса. Если обе стороны верны DateTimeKind.Local, большинство сравнений будут хорошими, но вы все равно можете иметь ошибки, если одна сторона неоднозначна в местном часовом поясе. Действительно, только DateTimeKind.Utcсравнения являются надежными, и да, DateTimeOffsetкак правило, предпочтительнее. (Ура!)
Мэтт Джонсон-Пинт
1
+1 Я бы добавил к этому: выбранный вами тип данных должен отражать ваши намерения. Не используйте DateTimeOffset везде, просто вызывайте. Если смещение имеет значение для ваших вычислений и чтения из базы данных (Persisting-To), используйте DateTimeOffset. Если это не имеет значения, тогда используйте DateTime, так что вы понимаете (просто взглянув на DataType), что смещение не должно иметь никакого значения, а время должно оставаться относительно местоположения сервера / компьютера, на котором работает код C #.
MikeTeeVee
«Но если вы хотите представить историю возникновения аварий, вы должны использовать DateTimeOffset». Не могли бы вы объяснить, почему вы так думаете? Я мог бы заполнить это, прочитав всю информацию на этой странице, но, возможно, вы могли бы также предоставить такую ​​информацию в удобном для чтения виде. Это очень читаемый ответ.
Баррозы
78

DateTime может хранить только два разных времени, местное время и UTC. Свойство Kind указывает, какой.

DateTimeOffset расширяет это, имея возможность хранить местное время из любой точки мира. Он также хранит смещение между этим местным временем и UTC. Обратите внимание, что DateTime не может этого сделать, если вы не добавите в свой класс дополнительного члена для хранения этого смещения UTC. Или работать только с UTC. Что само по себе является прекрасной идеей, кстати.

Ганс Пассант
источник
33

Есть несколько мест, где DateTimeOffsetесть смысл. Один из них - когда вы имеете дело с повторяющимися событиями и переходом на летнее время. Допустим, я хочу, чтобы будильник включался в 9 утра каждый день. Если я использую правило «хранить как UTC, отображать как местное время», то сигнал тревоги сработает с другим время, когда летнее время.

Возможно, есть и другие, но приведенный выше пример на самом деле тот, с которым я сталкивался в прошлом (это было до добавления DateTimeOffsetв BCL - в то время моим решением было явное сохранение времени в местном часовом поясе и сохранение информация о часовом поясе вдоль нее: в основном то, что DateTimeOffsetпроисходит внутри).

Дин Хардинг
источник
12
DateTimeOffset не решает проблему DST
JarrettV
2
Использование класса TimeZoneInfo содержит правила для DST. если вы используете .net 3.5 или новее, используйте классы TimeZone или TimeZoneInfo для работы с датами, которые должны обрабатывать переход на летнее время в сочетании со смещением часового пояса.
Зак Яннсен
1
Да, хороший пример исключения (приложение тревоги), но когда время более важно, чем дата, вы должны действительно хранить это отдельно в своей структуре данных расписания для приложения, то есть тип вхождения = Ежедневно и время = 09:00. Дело в том, что разработчик должен знать, какую дату он записывает, рассчитывает или представляет пользователям. Особенно приложения, как правило, становятся более глобальными, теперь у нас есть Интернет как стандартные и большие магазины приложений, для которых пишут программное обеспечение. В качестве побочного узла я бы также хотел, чтобы Microsoft добавила отдельную структуру даты и времени.
Тони Уолл
3
Обобщение комментариев Джарретта и Зака: Похоже, что только DateTimeOffset не решит проблему DST, но использование DateTimeOffset в сочетании с 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 явно включает смещение. Оно может не включать имя часового пояса, но, по крайней мере, оно включает смещение, и если вы его сериализуете, вы получите явно включенное смещение в вашем значении вместо того, каким бы ни было местное время сервера.

Triynko
источник
14
со всеми приведенными выше ответами, я удивляюсь, почему никто не удосужился написать ваше единственное предложение, которое подводит итог всего этогоThe most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Korayem
9
DateTimeOffset НЕ хранит информацию о часовом поясе. Документ MS под названием «Выбор между DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo» указывает следующее: «Значение DateTimeOffset не привязано к определенному часовому поясу, но может происходить из любого из множества часовых поясов». Тем не менее, DateTimeOffset является часовым поясом 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
Mojtaba
источник
9

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

dekdev
источник
7

Основным отличием является то, что DateTimeOffsetможет использоваться в сочетании сTimeZoneInfo преобразованием местного времени в часовые пояса, отличные от текущего.

Это полезно для серверного приложения (например, ASP.NET), к которому обращаются пользователи в разных часовых поясах.

Джо
источник
3
@ Buugeo Bugeo это правда, но есть риск. Вы можете сравнить два DateTimes, сначала вызвав ToUniversalTime для каждого. Если в сравнении вы имеете ровно одно значение DateTimeKind = Unspecified, ваша стратегия потерпит неудачу. Этот потенциал для сбоя является причиной для рассмотрения DateTimeOffset над DateTime, когда требуется преобразование в местное время.
Зак Яннсен,
Как и выше, я думаю, что в этом сценарии вам лучше хранить TimeZoneId, чем использовать DateTimeOffset, что в конечном итоге ничего не значит.
Арвин
2

Единственная отрицательная сторона DateTimeOffset, которую я вижу, заключается в том, что Microsoft «забыла» (намеренно) поддерживать ее в своем классе XmlSerializer. Но с тех пор он был добавлен в служебный класс XmlConvert.

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

Я говорю «вперед» и используйте DateTimeOffset и TimeZoneInfo из-за всех преимуществ, просто будьте осторожны при создании сущностей, которые будут или могут быть сериализованы в или из XML (тогда все бизнес-объекты).

Тони Уолл
источник