DateTime против DateTimeOffset

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

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

11 ответов

Решение

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 не имеет ничего подобного. Вам нужно будет хранить как 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 на компьютере с другим часовым поясом. (ИМХО - это должно вызвать исключение - но это не так.)

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

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

От Microsoft:

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

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

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

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

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

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

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

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

DateTime.Now
Пт 03 Дек 21 18:40:11

DateTimeOffset.Now
Пт 03 Дек 21 18:40:11 +02:00

Так, DateTimeOffsetхранит информацию о том, как время относится к UTC, в основном часовой пояс.

TL; DR, вы не хотите читать все эти замечательные ответы:-)

Явный:

С помощью DateTimeOffset потому что часовой пояс принудительно установлен на UTC+0.

Неявный:

С помощью DateTimeгде, как вы надеетесь, все придерживаются неписаного правила о том, что часовой пояс всегда равен UTC+0.


(Примечание для разработчиков: явное всегда лучше, чем неявное!)

(Боковое примечание для разработчиков Java, C# DateTimeOffset == Java OffsetDateTimeпрочтите это: https://www.baeldung.com/java-zoneddatetime-offsetdatetime)

Наиболее важным отличием является то, что DateTime не хранит информацию о часовом поясе, а DateTimeOffset - хранит.

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

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

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

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

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

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

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

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

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

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

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

Другие вопросы по тегам