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
для обеспечения его соблюдения или использования UTCDateTime
условно.Если вам нужно отследить момент мгновенного времени, но вы также хотите знать: "Сколько времени пользователь думал, что это было в его локальном календаре?" - тогда вы должны использовать
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 для получения дополнительной информации.
- Краткая история DateTime - Энтони Мур, команда BCL
- Выбор между Datetime и DateTime Offset - по MSDN
- Не забывайте, что в SQL Server 2008 и старше появился новый тип данных DateTimeOffset
- .NET Framework включает типы DateTime, DateTimeOffset иTimeZoneInfo, каждый из которых может использоваться для создания приложений, работающих с датами и временем.
- Выполнение арифметических операций с датами и временем-MSDN
Основное отличие состоит в том, что DateTimeOffset
может использоваться в сочетании с TimeZoneInfo
преобразовать в местное время в часовых поясах, отличных от текущего.
Это полезно для серверного приложения (например, ASP.NET), к которому обращаются пользователи в разных часовых поясах.
Единственная отрицательная сторона DateTimeOffset, которую я вижу, заключается в том, что Microsoft "забыла" (по замыслу) поддерживать ее в своем классе XmlSerializer. Но с тех пор он был добавлен в служебный класс XmlConvert.
Я говорю "вперед" и используйте DateTimeOffset и TimeZoneInfo из-за всех преимуществ, просто будьте осторожны при создании сущностей, которые будут или могут быть сериализованы в или из XML (тогда все бизнес-объекты).