DateTime против DateTimeOffset - PullRequest
       59

DateTime против DateTimeOffset

627 голосов
/ 02 декабря 2010

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

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

Ответы [ 9 ]

1027 голосов
/ 11 января 2013

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, он будет считаться local . Фреймворк в основном говорит: «Ну, вы попросили меня перевести календарное время в мгновенное, но я понятия не имею, откуда это пришло, поэтому я просто собираюсь использовать местный календарь». Это огромная ошибка, если вы загрузите неуказанный DateTime на компьютер с другим часовым поясом. (ИМХО - это должно вызвать исключение - но это не так.)

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

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

262 голосов
/ 09 января 2013

От Microsoft:

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

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

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

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

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

62 голосов
/ 02 декабря 2010

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

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

32 голосов
/ 02 декабря 2010

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

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

21 голосов
/ 06 августа 2015

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

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

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

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

9 голосов
/ 29 апреля 2013

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

7 голосов
/ 02 декабря 2010

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

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

5 голосов
/ 19 октября 2018

Этот фрагмент кода от 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
2 голосов
/ 09 апреля 2014

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

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

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

...