Datetimeoffset c что это
Перейти к содержимому

Datetimeoffset c что это

  • автор:

Работа с датами и временем

Для работы с датами и временем в .NET предназначена структура DateTime . Она представляет дату и время от 00:00:00 1 января 0001 года до 23:59:59 31 декабря 9999 года.

Для создания нового объекта DateTime также можно использовать конструктор. Пустой конструктор создает начальную дату:

DateTime dateTime = new DateTime(); Console.WriteLine(dateTime); // 01.01.0001 0:00:00

То есть мы получим минимально возможное значение, которое также можно получить следующим образом:

Console.WriteLine(DateTime.MinValue);

Чтобы задать конкретную дату, нужно использовать один из конструкторов, принимающих параметры:

DateTime date1 = new DateTime(2015, 7, 20); // год - месяц - день Console.WriteLine(date1); // 20.07.2015 0:00:00
DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // год - месяц - день - час - минута - секунда Console.WriteLine(date1); // 20.07.2015 18:30:25

Если необходимо получить текущую время и дату, то можно использовать ряд свойств DateTime:

Console.WriteLine(DateTime.Now); Console.WriteLine(DateTime.UtcNow); Console.WriteLine(DateTime.Today);
20.07.2015 11:43:33 20.07.2015 8:43:33 20.07.2015 0:00:00

Свойство DateTime.Now берет текущую дату и время компьютера, DateTime.UtcNow — дата и время относительно времени по Гринвичу (GMT) и DateTime.Today — только текущая дата.

При работе с датами надо учитывать, что по умолчанию для представления дат применяется григорианский календарь. Но что будет, если мы захотим получить день недели для 5 октября 1582 года:

DateTime someDate = new DateTime(1582, 10, 5); Console.WriteLine(someDate.DayOfWeek);

Консоль высветит значение Tuesday, то есть вторник. Однако, как может быть известно из истории, впервые переход с юлианского календаря на григорианский состоялся в октябре 1582 года. Тогда после даты 4 октября (четверг) (еще по юлианскому календарю) сразу перешли к 15 октября (пятница)(уже по григорианскому календарю). Таким образом, фактически выкинули 10 дней. То есть после 4 октября шло 15 октября.

В большинстве случаев данный факт вряд ли как-то повлияет на вычисления, однако при работе с очень давними датами данный аспект следует учитывать.

Операции с DateTime

Основные операции со структурой DateTime связаны со сложением или вычитанием дат. Например, надо к некоторой дате прибавить или, наоборот, отнять несколько дней.

Для добавления дат используется ряд методов:

  • Add(TimeSpan value) : добавляет к дате значение TimeSpan
  • AddDays(double value) : добавляет к текущей дате несколько дней
  • AddHours(double value) : добавляет к текущей дате несколько часов
  • AddMinutes(double value) : добавляет к текущей дате несколько минут
  • AddMonths(int value) : добавляет к текущей дате несколько месяцев
  • AddYears(int value) : добавляет к текущей дате несколько лет

Например, добавим к некоторой дате 3 часа:

DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 Console.WriteLine(date1.AddHours(3)); // 20.07.2015 21:30:25

Для вычитания дат используется метод Subtract(DateTime date) :

DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 DateTime date2 = new DateTime(2015, 7, 20, 15, 30, 25); // 20.07.2015 15:30:25 Console.WriteLine(date1.Subtract(date2)); // 03:00:00

Здесь даты различаются на три часа, поэтому результатом будет дата «03:00:00».

Метод Substract не имеет возможностей для отдельного вычитания дней, часов и так далее. Но это и не надо, так как мы можем передавать в метод AddDays() и другие методы добавления отрицательные значения:

// вычтем три часа DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); // 20.07.2015 18:30:25 Console.WriteLine(date1.AddHours(-3)); // 20.07.2015 15:30:25

Кроме операций сложения и вычитания еще есть ряд методов форматирования дат:

DateTime date1 = new DateTime(2015, 7, 20, 18, 30, 25); Console.WriteLine(date1.ToLocalTime()); // 20.07.2015 21:30:25 Console.WriteLine(date1.ToUniversalTime()); // 20.07.2015 15:30:25 Console.WriteLine(date1.ToLongDateString()); // 20 июля 2015 г. Console.WriteLine(date1.ToShortDateString()); // 20.07.2015 Console.WriteLine(date1.ToLongTimeString()); // 18:30:25 Console.WriteLine(date1.ToShortTimeString()); // 18:30

Метод ToLocalTime() преобразует время UTC в локальное время, добавляя смещение относительно времени по Гринвичу. Метод ToUniversalTime() , наоборот, преобразует локальное время во время UTC, то есть вычитает смещение относительно времени по Гринвичу. Остальные методы преобразуют дату к определенному формату.

Маленькие чудеса C#/.NET – структура DateTimeOffset

Рассмотрим некоторые части .Net Framework’a, выглядящие тривиальными, но вполне способными сделать ваш код более простым как в написании, так и в сопровождении.

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

Однако, бывают случаи, когда вам необходимо сохранить время в виде смещения, а не конвертировать его в локальное время. И вот здесь вам на помощь придёт структура, впервые появившаяся в .NET 3.5 — DateTimeOffset.

Проблема: парсинг DateTime может привести к конвертации в локальное время

Представим себе, что вы используете файл, веб-сервис и т.п. некой сторонней фирмы, чьи сервера находятся в другой временной зоне. Более того, у них есть несколько полей, в возвращаемых данных, которые должны содержать даты, но на самом деле содержат сериализованные экземпляры структуры DateTime, время в которых установлено в полночь. Например, дату рождения пациента они передают вот в таком виде:

2012-03-01 00:00:00-05:00

Такая запись говорит о том, что человек родился 1 марта 2012 года в неуказанное время (в конце концов, большая часть форм не требует от вас заполнения времени вашего рождения). Но поскольку экземпляр структуры DateTime был сериализован «в лоб», то он и содержит время, установленное в полночь, согласно своей временной зоне.

Итак, зная, что эта дата совместима с Восточной временной зоной (Eastern Time Zone), а мы находимся в Центральной временной зоне (Central Time Zone) мы парсим её так:

// ясно что здесь выполняется чтение файла/потока/и т.п.
var dateString = «2012-03-01 00:00:00-05:00»;
// парсим в DateTime
var birthDay = DateTime.Parse(dateString);

Выглядит идеально, не так ли? Но тут кроется проблемка. Если мы проверим содержимое объекта DateTime на нашей локальной машине, где выставлена Центральная временная зона, то увидим вот что:

2012-02-29 11:00:00 PM

Что случилось? (Или как говорил один персонаж — Кто это сделал?) Да, метод DateTime.Parse() конвертировал дату в локальную временную зону поскольку оригинальная дата рождения хранилась с указанным смещением. Вам просто оказали услугу — конвертировали указанную дату и время в ваши локальные дату и время. Это не так и плохо, если бы речь не шла о дне рождения, которое с 1 марта переместилось на 29 февраля.

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

Однако бывает, что у нас нет возможности таким образом изменить ситуацию.

Бывают случаи, когда вы хотите считывать дату и время со смещением, но не конвертировать его в локальную временную зону. И вот тут вам пригодится DateTimeOffset.

DateTimeOffset – хранит DateTime и Offset

Так что там про DateTimeOffset? Структура так же проста, как и её имя, DateTimeOffset это дата+время+смещение. Именно поэтому она представляет намного более точную точку во времени, поскольку включает информацию о смещении, по которому были установлены текущие дата и время.

По правде говоря, функциональность DateTime и DateTimeOffset во многом перекрывается, а поскольку у Microsoft есть руководство по выбору того или другого, то я рекомендую ознакомиться с ней в MSDN. Статья называется «Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo».

В целом, вы можете использовать DateTime, если вы «прикреплены» к одной временной зоне или используете только универсальное время в формате UTC. Но если вы хотите использовать даты и время из разных временных зон, а также хотите сохранить информацию о смещении без конвертации в локальное время, то лучше использовать DateTimeOffset.

В структуре DateTimeOffset есть много таких же свойств, как и в структуре DateTime (Day, Month, Year, Hour, Minute, Second, и т.п.), потому здесь их я описывать не стану. Главное отличие состоит в нескольких новых свойствах:

Возвращает DateTime без учёта смещения.

LocalDateTime

Возвращает конвертированный DateTime, с учётом смещения (т.е. в локальной временной зоне).

Возвращает смещение относительно UTC.

UtcDateTime

Возвращает DateTime как время UTC.

Свойство DateTime возвращает вам DateTime (не приведенное к локальной временной зоне), а свойство Offset имеет формат TimeSpan, представляющее смещение от времени UTC. Также есть свойства LocalDateTime и UtcDateTime, конвертирующие данный DateTimeOffset в DateTime для локальной временной зоны или UTC.

Также замечу, что свойства Now и UtcNow структуры DateTimeOffset возвращают не тип DateTime, а DateTimeOffsets с соответствующим смещением от UTC. Конечно, как и DateTime, DateTimeOffset обладает методами, оперирующими с датой/временем, возвращая тип DateTimeOffset вместо DateTime.

Так как нам всё это поможет в выше приведенном примере? Теперь мы знаем, что третья сторона высылает нам дату и время своей временной зоны, которое не нужно конвертировать в локальные дату/время. Поэтому можно использовать DateTimeOffset.Parse() (или TryParse()) и выбрать только дату:

// ясно что здесь выполняется чтение файла/потока/и т.п.
var dateString = «2012-03-01 00:00:00-05:00»;
// парсим день рождения как смещение даты/времени (без конвертирования в локальные даты/время)
var dtOffset = DateTimeOffset.Parse(dateString);
// теперь если нам надо сравнить результат с другими объектами типа локального DateTime
// мы просто используем свойство Date для получения даты
// без времени или смещения
var theDay = dtOffset.Date;

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

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

Вольный перевод (с) В.Ф.Чужа ака hDrummer, оригинал здесь.

Структура DateTimeOffset

Представляет момент времени, который обычно выражается в виде даты и времени суток, относительно времени в формате UTC.

Пространство имен: System

Сборка: mscorlib (в mscorlib.dll)

Синтаксис

public struct DateTimeOffset : IComparable, IFormattable, ISerializable, IDeserializationCallback, IComparableDateTimeOffset>, IEquatableDateTimeOffset>

Конструкторы

Тип Имя Описание
Конструктор DateTimeOffset(Int64, TimeSpan) Инициализирует новый экземпляр структуры DateTimeOffset с использованием заданного количества тактов и смещения.
Конструктор DateTimeOffset(DateTime) Инициализирует новый экземпляр структуры DateTimeOffset с использованием заданного значения DateTime .
Конструктор DateTimeOffset(DateTime, TimeSpan) Инициализирует новый экземпляр структуры DateTimeOffset с использованием заданного значения DateTime и смещения.
Конструктор DateTimeOffset(Int32, Int32, Int32, Int32, Int32, Int32, TimeSpan) Инициализирует новый экземпляр структуры DateTimeOffset , используя год, месяц, день, час, минуту, секунду и смещение.
Конструктор DateTimeOffset(Int32, Int32, Int32, Int32, Int32, Int32, Int32, TimeSpan) Инициализирует новый экземпляр структуры DateTimeOffset , используя год, месяц, день, час, минуту, секунду, миллисекунду и смещение.
Конструктор DateTimeOffset(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Calendar, TimeSpan) Инициализирует новый экземпляр структуры DateTimeOffset , используя год, месяц, день, час, минуту, секунду, миллисекунду и смещение для заданного календаря.

Свойства

Тип Имя Описание
Свойство Date Получает значение DateTime , представляющее компонент даты в текущем объекте DateTimeOffset .
Свойство DateTime Получает значение DateTime , представляющее дату и время в текущем объекте DateTimeOffset .
Свойство Day Возвращает день месяца, представленный текущим объектом DateTimeOffset .
Свойство DayOfWeek Возвращает день недели, представленный текущим объектом DateTimeOffset .
Свойство DayOfYear Возвращает день года, представленный текущим объектом DateTimeOffset .
Свойство Hour Возвращает часовой компонент, представленный текущим объектом DateTimeOffset .
Свойство LocalDateTime Получает значение DateTime , представляющее местную дату и время в текущем объекте DateTimeOffset .
Свойство Millisecond Возвращает временной компонент миллисекунд, представленный текущим объектом DateTimeOffset .
Свойство Minute Возвращает компонент минут, представленный текущим объектом DateTimeOffset .
Свойство Month Возвращает компонент месяца даты, представленный текущим объектом DateTimeOffset .
Свойство Now Получает объект DateTimeOffset , для которого в качестве значения установлена текущая дата и время на текущем компьютере, а в качестве смещения — смещение местного времени от времени в формате UTC.
Свойство Offset Возвращает смещение по времени от времени в формате UTC.
Свойство Second Возвращает компонент секунд по показаниям часов, представленный текущим объектом DateTimeOffset .
Свойство Ticks Возвращает количество тактов, представляющее местную дату и время в текущем объекте DateTimeOffset по показаниям часов.
Свойство TimeOfDay Получает время суток текущего объекта DateTimeOffset .
Свойство UtcDateTime Получает значение DateTime , представляющее дату и время текущего объекта DateTimeOffset в формате UTC.
Свойство UtcNow Возвращает объект DateTimeOffset , в качестве даты и времени которого установлены текущие дата и время в формате UTC, а в качестве смещения — значение Zero .
Свойство UtcTicks Возвращает количество тактов, которое представляет дату и время текущего объекта DateTimeOffset в формате UTC.
Свойство Year Возвращает компонент года даты, представленный текущим объектом DateTimeOffset .

Методы

Тип Имя Описание
Метод Add(TimeSpan) Добавляет указанный интервал времени к объекту DateTimeOffset .
Метод AddDays(Double) Добавляет указанное количество полных и неполных дней к текущему объекту DateTimeOffset .
Метод AddHours(Double) Добавляет указанное количество полных и неполных часов к текущему объекту DateTimeOffset .
Метод AddMilliseconds(Double) Добавляет указанное количество миллисекунд к текущему объекту DateTimeOffset .
Метод AddMinutes(Double) Добавляет указанное количество полных и неполных минут к текущему объекту DateTimeOffset .
Метод AddMonths(Int32) Добавляет указанное количество месяцев к текущему объекту DateTimeOffset .
Метод AddSeconds(Double) Добавляет указанное количество полных и неполных секунд к текущему объекту DateTimeOffset .
Метод AddTicks(Int64) Добавляет указанное количество тактов к текущему объекту DateTimeOffset .
Метод AddYears(Int32) Добавляет указанное количество лет к объекту DateTimeOffset .
МетодСтатический Compare(DateTimeOffset, DateTimeOffset) Сравнивает два объекта DateTimeOffset , и указывает, как соотносятся между собой указанные в них моменты времени: первый раньше второго, первый и второй равны, или же первый позже второго.
Метод CompareTo(DateTimeOffset) Сравнивает текущий объект DateTimeOffset с заданным объектом DateTimeOffset и указывает, когда наступает момент, заданный в первом объекте: раньше, позже или одновременно с моментом, указанным во втором объекте DateTimeOffset .
Метод Equals(Object) Определяет, представляет ли объект DateTimeOffset тот же момент времени, что и заданный объект. (Переопределяет Object.Equals(Object).)
Метод Equals(DateTimeOffset) Определяет, представляет ли текущий объект DateTimeOffset тот же момент времени, что и заданный объект DateTimeOffset .
МетодСтатический Equals(DateTimeOffset, DateTimeOffset) Определяет, представляют ли два заданных объекта DateTimeOffset один и тот же момент времени.
Метод EqualsExact(DateTimeOffset) Определяет, представляет ли текущий объект DateTimeOffset тот же момент времени, что и заданный объект DateTimeOffset и совпадают ли их смещения.
МетодСтатический FromFileTime(Int64) Преобразует заданную временную характеристику файла Windows в ее эквивалент по местному времени.
МетодСтатический FromUnixTimeMilliseconds(Int64)
МетодСтатический FromUnixTimeSeconds(Int64)
Метод GetHashCode() Возвращает хэш-код для текущего объекта DateTimeOffset . (Переопределяет Object.GetHashCode().)
Метод GetType() Возвращает объект Type для текущего экземпляра. (Наследуется от Object.)
МетодСтатический Parse(String) Преобразует заданное строковое представление даты, времени и смещения в их эквивалент DateTimeOffset .
МетодСтатический Parse(String, IFormatProvider, DateTimeStyles) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя указанную информацию о форматировании, связанную с языком и региональными параметрами, а также заданный стиль форматирования.
МетодСтатический Parse(String, IFormatProvider) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя указанные сведения о форматировании, связанные с языком и региональными параметрами.
МетодСтатический ParseExact(String, String[], IFormatProvider, DateTimeStyles) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя заданные форматы, указанные сведения о форматировании, связанные с языком и региональными параметрами, а также стиль.Формат строкового представления должен полностью соответствовать одному из заданных форматов.
МетодСтатический ParseExact(String, String, IFormatProvider, DateTimeStyles) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя заданный формат, указанные сведения о форматировании, связанные с языком и региональными параметрами, а также стиль.Формат строкового представления должен полностью соответствовать заданному формату.
МетодСтатический ParseExact(String, String, IFormatProvider) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя указанные сведения о форматировании, связанные с языком и региональными параметрами.Формат строкового представления должен полностью соответствовать заданному формату.
Метод Subtract(TimeSpan) Вычитает указанный интервал времени из текущего объекта DateTimeOffset .
Метод Subtract(DateTimeOffset) Вычитает значение DateTimeOffset , представляющее определенную дату и время в текущем объекте DateTimeOffset .
Метод ToFileTime() Преобразует значение текущего объекта DateTimeOffset во временную характеристику файла Windows.
Метод ToLocalTime() Преобразует текущий объект DateTimeOffset в объект DateTimeOffset , представляющий местное время.
Метод ToOffset(TimeSpan) Преобразует значение текущего объекта DateTimeOffset в дату и время, указанные в значении смещения.
Метод ToString(String, IFormatProvider) Преобразует значение текущего объекта DateTimeOffset в эквивалентное ему строковое представление с использованием указанного формата и сведений об особенностях формата для данного языка и региональных параметров.
Метод ToString(IFormatProvider) Преобразует числовое значение текущего объекта DateTimeOffset в эквивалентное ему строковое представление с использованием указанных сведений об особенностях форматирования для данного языка и региональных параметров.
Метод ToString(String) Преобразует значение текущего объекта DateTimeOffset в эквивалентное ему строковое представление с использованием заданного формата.
Метод ToString() Преобразует значение текущего объекта DateTimeOffset в эквивалентное ему строковое представление. (Переопределяет Object.ToString().)
Метод ToUniversalTime() Преобразует текущий объект DateTimeOffset в значение DateTimeOffset , представляющее время в формате UTC.
Метод ToUnixTimeMilliseconds()
Метод ToUnixTimeSeconds()
МетодСтатический TryParse(String, IFormatProvider, DateTimeStyles, DateTimeOffset) Предпринимает попытку преобразования указанного строкового представления даты и времени в его эквивалент DateTimeOffset , и возвращает значение, позволяющее определить успешность преобразования.
МетодСтатический TryParse(String, DateTimeOffset) Предпринимает попытку преобразования указанного строкового представления даты и времени в его эквивалент DateTimeOffset , и возвращает значение, позволяющее определить успешность преобразования.
МетодСтатический TryParseExact(String, String, IFormatProvider, DateTimeStyles, DateTimeOffset) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя заданный формат, указанные сведения о форматировании, связанные с языком и региональными параметрами, а также стиль.Формат строкового представления должен полностью соответствовать заданному формату.
МетодСтатический TryParseExact(String, String[], IFormatProvider, DateTimeStyles, DateTimeOffset) Преобразует заданное строковое представление даты и времени в его эквивалент DateTimeOffset , используя заданный массив форматов, указанные сведения о форматировании, связанные с языком и региональными параметрами, и стиль форматирования.Формат строкового представления должен полностью соответствовать одному из заданных форматов.

Поля

Тип Имя Описание
ПолеСтатический MaxValue Представляет максимально допустимое значение типа DateTimeOffset .Это поле доступно только для чтения.
ПолеСтатический MinValue Представляет наиболее раннее из возможных значений DateTimeOffset .Это поле доступно только для чтения.

developers/references/system.datetimeoffset.txt · Последние изменения: 2021/07/22 14:29 (внешнее изменение)

DateTime vs DateTimeOffset

What is the difference between a DateTime and a DateTimeOffset and when should one be used? Currently, we have a standard way of dealing with .NET DateTime s in a TimeZone-aware way: Whenever we produce a DateTime we do it in UTC (e.g. using DateTime.UtcNow ), and whenever we display one, we convert back from UTC to the user’s local time. That works fine, but I’ve been reading about DateTimeOffset and how it captures the local and UTC time in the object itself.

20.8k 67 67 gold badges 76 76 silver badges 102 102 bronze badges
asked Dec 2, 2010 at 2:39
David Reis David Reis
12.8k 7 7 gold badges 36 36 silver badges 43 43 bronze badges
When it comes to storage, stackoverflow.com/questions/4715620/… is interesting too.
Jul 21, 2016 at 18:22
Curious people might also want to read storing utc is not a silver bullet
Feb 23, 2021 at 8:20

10 Answers 10

DateTimeOffset is a representation of instantaneous time (also known as absolute time). By that, I mean a moment in time that is universal for everyone (not accounting for leap seconds, or the relativistic effects of time dilation). Another way to represent instantaneous time is with a DateTime where .Kind is DateTimeKind.Utc .

This is distinct from calendar time (also known as civil time), which is a position on someone’s calendar, and there are many different calendars all over the globe. We call these calendars time zones. Calendar time is represented by a DateTime where .Kind is DateTimeKind.Unspecified , or DateTimeKind.Local . And .Local is only meaningful in scenarios where you have an implied understanding of where the computer that is using the result is positioned. (For example, a user’s workstation)

So then, why DateTimeOffset instead of a UTC DateTime ? It’s all about perspective. Let’s use an analogy — we’ll pretend to be photographers.

Imagine you are standing on a calendar timeline, pointing a camera at a person on the instantaneous timeline laid out in front of you. You line up your camera according to the rules of your timezone — which change periodically due to daylight saving time, or due to other changes to the legal definition of your time zone. (You don’t have a steady hand, so your camera is shaky.)

The person standing in the photo would see the angle at which your camera came from. If others were taking pictures, they could be from different angles. This is what the Offset part of the DateTimeOffset represents.

So if you label your camera «Eastern Time», sometimes you are pointing from -5, and sometimes you are pointing from -4. There are cameras all over the world, all labeled different things, and all pointing at the same instantaneous timeline from different angles. Some of them are right next to (or on top of) each other, so just knowing the offset isn’t enough to determine which timezone the time is related to.

And what about UTC? Well, it’s the one camera out there that is guaranteed to have a steady hand. It’s on a tripod, firmly anchored into the ground. It’s not going anywhere. We call its angle of perspective the zero offset.

Instantaneous Time vs Calendar Time Visualization

So — what does this analogy tell us? It provides some intuitive guidelines-

  • If you are representing time relative to some place in particular, represent it in calendar time with a DateTime . Just be sure you don’t ever confuse one calendar with another. Unspecified should be your assumption. Local is only useful coming from DateTime.Now . For example, I might get DateTime.Now and save it in a database — but when I retrieve it, I have to assume that it is Unspecified . I can’t rely that my local calendar is the same calendar that it was originally taken from.
  • If you must always be certain of the moment, make sure you are representing instantaneous time. Use DateTimeOffset to enforce it, or use UTC DateTime by convention.
  • If you need to track a moment of instantaneous time, but you want to also know «What time did the user think it was on their local calendar?» — then you must use a DateTimeOffset . This is very important for timekeeping systems, for example — both for technical and legal concerns.
  • If you ever need to modify a previously recorded DateTimeOffset — you don’t have enough information in the offset alone to ensure that the new offset is still relevant for the user. You must also store a timezone identifier (think — I need the name of that camera so I can take a new picture even if the position has changed). It should also be pointed out that Noda Time has a representation called ZonedDateTime for this, while the .Net base class library does not have anything similar. You would need to store both a DateTimeOffset and a TimeZoneInfo.Id value.
  • Occasionally, you will want to represent a calendar time that is local to «whomever is looking at it». For example, when defining what today means. Today is always midnight to midnight, but these represent a near-infinite number of overlapping ranges on the instantaneous timeline. (In practice we have a finite number of timezones, but you can express offsets down to the tick) So in these situations, make sure you understand how to either limit the «who’s asking?» question down to a single time zone, or deal with translating them back to instantaneous time as appropriate.

Here are a few other little bits about DateTimeOffset that back up this analogy, and some tips for keeping it straight:

  • If you compare two DateTimeOffset values, they are first normalized to zero offset before comparing. In other words, 2012-01-01T00:00:00+00:00 and 2012-01-01T02:00:00+02:00 refer to the same instantaneous moment, and are therefore equivalent.
  • If you are doing any unit testing and need to be certain of the offset, test both the DateTimeOffset value, and the .Offset property separately.
  • There is a one-way implicit conversion built in to the .Net framework that lets you pass a DateTime into any DateTimeOffset parameter or variable. When doing so, the .Kind matters. If you pass a UTC kind, it will carry in with a zero offset, but if you pass either .Local or .Unspecified , it will assume to be local. The framework is basically saying, «Well, you asked me to convert calendar time to instantaneous time, but I have no idea where this came from, so I’m just going to use the local calendar.» This is a huge gotcha if you load up an unspecified DateTime on a computer with a different timezone. (IMHO — that should throw an exception — but it doesn’t.)

Shameless Plug:

Many people have shared with me that they find this analogy extremely valuable, so I included it in my Pluralsight course, Date and Time Fundamentals. You’ll find a step-by-step walkthrough of the camera analogy in the second module, «Context Matters», in the clip titled «Calendar Time vs. Instantaneous Time».

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *