DateTime против DateTimeOffset

533

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

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

  • 3
    Ниже приведены отличные ответы. Но я все еще задаюсь вопросом, что могло убедить вас начать использовать DateTimeOffset.
  • 1
    Можно также увидеть, когда вы бы предпочли дату-время-дата-смещение
Показать ещё 2 комментария
Теги:
datetime
timezone
datetimeoffset

8 ответов

851
Лучший ответ

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

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

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

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

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

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

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

Изображение 1046

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

  • Если вы представляете время относительно некоторого места в частности, представляйте его в календарном времени с помощью 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, Основы даты и времени. Вы найдете пошаговое руководство по аналогии с камерой во втором модуле "Контекстные вопросы" в клипе "Время календаря против мгновенного времени".

  • 0
    @Matt Johnson: Согласен. Очень хороший ответ Вы бы порекомендовали хранить эти значения в SQL как DATETIMEOFFSET (#) или DATETIME2 (#)? Для создания большинства временных меток в UTC я бы подумал, что если время щелчка не требуется, вы можете сделать это в SPROC на стороне базы данных с помощью SYSUTCDATETIME () и избежать головной боли при входе. Тогда вам нужно только знать TimeZoneInfo.Id для декодирования для локального пользователя, правильно? Если мы имеем дело с браузерным приложением, есть ли какие-либо рекомендации по работе с переходом на летнее время? Я предполагаю, что лучший способ состоит в том, чтобы пользователь установил свой часовой пояс и продолжал сеанс.
  • 4
    @ZackJannsen Если у вас есть DateTimeOffset в C #, то вы должны сохранить это в DATETIMEOFFSET в SQL Server. DATETIME2 или просто DATETIME (в зависимости от требуемого диапазона) подходят для обычных значений DateTime . Да - вы можете определить местное время из любой пары часовых поясов + dto или utc. Разница в том, что вы всегда хотите вычислять правила для каждого решения или же хотите их пересчитать? Во многих случаях (иногда по юридическим соображениям) DTO - лучший выбор.
Показать ещё 26 комментариев
191

От 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 игнорирует смещение часового пояса при сравнении.

  • 98
    Принятый ответ слишком длинный, и аналогия натянута, это гораздо лучший и более краткий ответ IMO.
  • 7
    Я просто скажу, что мне тоже нравится этот ответ, и я проголосовал. Хотя в последней части - даже если убедиться, что Kind одинаковы, сравнение может быть ошибочным. Если обе стороны имеют DateTimeKind.Unspecified вы не знаете, что они пришли из одного часового пояса. Если обе стороны имеют DateTimeKind.Local , большинство сравнений будут хорошими, но вы все равно можете иметь ошибки, если одна сторона неоднозначна в местном часовом поясе. Действительно, только сравнения DateTimeKind.Utc являются надежными, и да, DateTimeOffset обычно предпочтительнее. (Ура!)
51

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

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

31

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

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

  • 10
    DateTimeOffset не решает проблему DST
  • 2
    Использование класса TimeZoneInfo несет правила для DST. если вы используете .net 3.5 или новее, используйте классы TimeZone или TimeZoneInfo для работы с датами, которые должны обрабатывать переход на летнее время в сочетании со смещением часового пояса.
Показать ещё 3 комментария
13

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

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

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

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

  • 8
    со всеми приведенными выше ответами, я удивляюсь, почему никто не удосужился написать ваше единственное предложение, которое суммирует все это The most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
  • 5
    DateTimeOffset НЕ хранит информацию о часовом поясе. В документе MS под названием «Выбор между DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo» указывается следующее: «Значение DateTimeOffset не привязано к конкретному часовому поясу, но может происходить из любого из множества часовых поясов». Тем не менее, DateTimeOffset является часовым поясом AWARE, содержащим смещение от UTC, которое имеет все значение и поэтому MS рекомендует использовать класс по умолчанию при работе с приложениями, который имеет дело с информацией о дате. Если вы действительно заботитесь о том, из какого конкретного часового пояса поступили данные, вы должны сохранить его отдельно
Показать ещё 1 комментарий
9

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

7

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

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

  • 8
    TimeZoneInfo работает также с DateTime
  • 3
    @ Buugeo Bugeo - правда, но есть риск. Вы можете сравнить два DateTimes, сначала вызвав ToUniversalTime для каждого. Если в сравнении вы имеете ровно одно значение DateTimeKind = Unspecified, ваша стратегия потерпит неудачу. Этот потенциал для сбоя является причиной для рассмотрения DateTimeOffset над DateTime, когда требуется преобразование в местное время.
Показать ещё 1 комментарий
2

Единственная отрицательная сторона DateTimeOffset, которую я вижу, - это то, что Microsoft "забыла" (по дизайну), чтобы поддерживать ее в своем классе XmlSerializer. Нет проблем с DataContractSerializer (и, возможно, другими, но нужно проверить это). Во всяком случае, есть обходное решение, которое я объяснил в официальной теме MS connect:

https://connect.microsoft.com/VisualStudio/feedback/details/288349/datetimeoffset-is-not-serialized-by-a-xmlserializer

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

Сообщество Overcoder
Наверх
Меню