DateTime vs DateTimeOffset

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

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

Ответ 1

DateTimeOffset представляет собой представление мгновенного времени (также известного как абсолютное время). Под этим я подразумеваю момент времени, универсальный для всех (не учитывающий прыжок секунд или релятивистские эффекты временная дилатация). Другим способом представления мгновенного времени является DateTime, где .Kind - DateTimeKind.Utc.

Это отличается от календарного времени (также известного как гражданское время), которое является позицией для календаря кого-то, и существует множество разных календарей по всему миру. Мы называем эти временные зоны календарей. Время календаря представлено DateTime, где .Kind - DateTimeKind.Unspecified, или DateTimeKind.Local. И .Local имеет смысл только в сценариях, где у вас подразумевается понимание того, где находится компьютер, который использует результат. (Например, рабочая станция пользователя)

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

Представьте, что вы стоите на временной шкале календаря, указывая на камеру у человека на мгновенной временной шкале, изложенной перед вами. Вы выстраиваете свою камеру в соответствии с правилами вашего часового пояса, которые периодически меняются из-за летнего времени или из-за других изменений в юридическом определении вашего часового пояса. (У вас нет устойчивой руки, поэтому ваша камера дрожит.)

Человек, стоящий на фотографии, увидит угол, из которого появилась ваша камера. Если другие фотографируют, они могут быть под разными углами. Это то, что представляет собой Offset часть DateTimeOffset.

Итак, если вы помечаете свою камеру "Восточное время", иногда вы указываете от -5, а иногда вы указываете от -4. Есть камеры по всему миру, все обозначенные разными вещами, и все они указывают на одну и ту же мгновенную временную шкалу под разными углами. Некоторые из них находятся рядом с (или сверху) друг с другом, поэтому просто знать, что смещение недостаточно, чтобы определить, в каком часовом поясе время связано.

А как насчет UTC? Ну, это единственная камера, которая гарантирована устойчивой рукой. Он на штативе, крепко привязан к земле. Это никуда не денется. Мы называем его угол зрения нулевым смещением.

Instantaneous Time vs Calendar Time Visualization

Итак - что говорит эта аналогия? Он содержит некоторые интуитивные рекомендации.

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

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

  • Если вам нужно отслеживать мгновение мгновенного времени, но вы также хотите знать: "В какое время пользователь подумал, что это было в их локальном календаре?" - тогда вы должны использовать 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. При этом .Kind имеет значение. Если вы передадите тип UTC, он будет переноситься с нулевым смещением, но если вы пройдете либо .Local, либо .Unspecified, он будет считаться локальным. Рамки в основном говорят: "Ну, вы попросили меня преобразовать время календаря в мгновенное время, но я понятия не имею, откуда это взялось, поэтому я просто собираюсь использовать локальный календарь". Это огромная проблема, если вы загружаете неуказанный DateTime на компьютер с другим часовым поясом. (IMHO - это должно вызывать исключение - но это не так.)

Бесстыдный плагин:

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

Ответ 2

От Microsoft:

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

source: Выбор между DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo, MSDN

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

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

Самое важное отличие заключается в том, что DateTime не сохраняет информацию о часовом поясе, а DateTimeOffset делает.

Хотя DateTime различает UTC и Local, абсолютно никакого явного смещения часового пояса, связанного с ним. Если вы выполняете сериализацию или преобразование, будет использоваться часовой пояс сервера. Даже если вы вручную создаете локальное время, добавив минуты для смещения времени UTC, вы все равно можете получить бит на этапе сериализации, потому что (из-за отсутствия какого-либо явного смещения в DateTime) он будет использовать смещение часового пояса сервера.

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

Этот кусок кода от 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

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

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

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