Переход от DateTime к DateTimeOffset

    Представим, что вы хотите преобразовать свою систему из одного состояния в другое. Начальное состояние — это когда DateTime используется везде, в C# коде и в БД. Конечное состояние — когда везде используется DateTimeOffset. Вы хотите сделать переход плавно и внести как можно меньше изменений. Это описание может быть началом очень интересной задачи с тупиком в конце.

    Тип DateTime был типом .NET по умолчанию для работы с датой и временем некоторое время назад, и обычно построенная вокруг него логика была сделана так, как будто она никогда не поменяется. Если попытаться менять тип в один этап, это приведёт к каскадным изменениям практически во всех частях системы. В крайних случаях может потребоваться изменить около 200 хранимых процедур только для одного поля. Это первая проблема. И вторая проблема заключается в том, что последствия таких изменений трудно найти во время тестирования. Регрессионное тестирование не гарантирует, что вы ничего не упустите, или система будет функционировать в любых случаях. Необходимые усилия по обеспечению качества будут увеличиваться по мере работы, и у вас не будет четкого понимания, когда оно закончится.

    Во время моего исследования я нашел возможный подход к таким преобразованиям. Этот подход имеет три этапа и основан на предположении, что в настоящее время система не поддерживает временные зоны, а все подсистемы расположены в одном часовом поясе.

    1. Добавить парное вычисляемое поле для чтения значений DateTimeOffset из БД.
    2. Сделать преобразование операций чтения.
    3. Сделать преобразование операций записи.

    Такой подход поможет локализовать изменения и ограничить усилия QA. Он также обеспечит хорошую предсказуемость для оценки будущей работы. Ниже я описал этапы более подробно.

    Неудачный Подход


    Представьте, что существует около 150 полей, связанных со значениями даты/времени. Вы можете использовать следующий сценарий SQL, чтобы узнать полный список в вашей БД.

    select
        tbl.name as 'table',
        col.name as 'column',
        tp.name as 'type',
        def.name as 'default'
    from sys.columns col
        inner join sys.tables tbl on tbl.[object_id] = col.[object_id]
        inner join sys.types tp on tp.system_type_id = col.system_type_id
            and tp.name in ('datetime', 'date', 'time', 'datetime2', 'datetimeoffset', 'smalldatetime')
        left join sys.default_constraints def on def.parent_object_id = col.[object_id]
            and def.parent_column_id = col.column_id
    order by tbl.name, col.name
    

    В то время как в БД преобразование из DateTime в DateTimeOffset и обратно поддерживается на очень хорошем уровне, в C# коде это сложно из-за типизации. Вы не можете прочитать значение DateTime, если БД возвращает значение DateTimeOffset. При изменении возвращаемого типа для одного поля необходимо изменить все места, где он используется во всей системе. В некоторых случаях это просто невозможно, потому что вы можете не знать о некоторых местах, если система очень большая. Именно по этой причине подход с простым изменением типа поля не будет работать. Вы можете попробовать найти все использования определенного поля таблицы в БД, используя следующий скрипт.

    SELECT
        ROUTINE_NAME,
        ROUTINE_DEFINITION
    FROM INFORMATION_SCHEMA.ROUTINES
    WHERE ROUTINE_DEFINITION LIKE '%table%'
        OR ROUTINE_DEFINITION LIKE '%field%'
        AND ROUTINE_TYPE='PROCEDURE'
    

    Для того, чтобы сделать трансформацию, важно заранее предсказать, какие части системы будут затронуты. У вас должен быть подход к локализации изменений в конкретном модуле системы без нарушения остальных частей.

    Подход «Получше»


    Это подход просто «получше», а не лучший. Я все еще ожидаю, что некоторые проблемы могут появиться в будущем, но он выглядит более безопасным, чем предыдущий. Главное отличие состоит в том, что вы не выполняете преобразование за один шаг. Существует последовательность зависимых изменений, которая даст вам контроль над ситуацией.

    Создание Вычисляемого Поля


    Когда вы добавляете вычисляемое дублирующее поле в БД, вы вводите новое поле с требуемым типом. Это позволит вам отделить чтение, запись и отделить обновленный код от старого. Эта операция легко может быть выполнена с помощью скрипта, и никаких усилий по обеспечению качества не требуется.

    declare @table sysname, @column sysname, @type sysname, @default sysname
    
    declare cols cursor for
    select
        tbl.name as 'table',
        col.name as 'column',
        tp.name as 'type',
        def.name as 'default'
    from sys.columns col
        inner join sys.tables tbl on tbl.[object_id] = col.[object_id]
        inner join sys.types tp on tp.system_type_id = col.system_type_id
            and tp.name in ('datetime', 'date', 'time', 'datetime2', 'smalldatetime')
        left join sys.default_constraints def on def.parent_object_id = col.[object_id]
            and def.parent_column_id = col.column_id
    order by tbl.name, col.name
    
    open cols
    fetch from cols into @table, @column, @type, @default
    while @@FETCH_STATUS = 0
    begin
        declare @cmd nvarchar(max)
        set @cmd = 'alter table ['+@table+'] add ['+@column+'_dto] as todatetimeoffset(['+@column+'], ''+00:00'')'
        exec (@cmd)
        fetch from cols into @table, @column, @type, @default
    end
    close cols
    deallocate cols
    

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

    Трансформация Чтения


    Операции чтения оказались наиболее сложными для преобразования из-за подхода, который используется для интеграции между клиентским кодом и БД. Значения даты/времени передаются посредством сериализации строк. DateTimeOffset имеет другой формат и не может быть прочитан по умолчанию для переменных DateTime на стороне клиента. В тоже время, операции записи просто работают. Если вы передадите значение DateTime аргументу или полю DateTimeOffset, это значение будет принято с предположением, что оно является скорректированным к UTC. Смещение времени после преобразования будет «+00:00».

    Теперь можно взять какой-то раздел системы и определить точное количество хранимок, возвращающих DateTime в код клиента. Тут надо будет изменить операции чтения в коде C# для чтения значений DateTimeOffset. Также потребуется изменить сами хранимки в БД, чтобы они возвращали значения из новых вычисляемых полей. Ожидаемый результат этого шага выглядит следующим образом:

    • C# код считывает DateTimeOffset и использует этот тип везде, где возможно, чтобы возвращать значения из системы.
    • Хранимки БД используют DateTimeOffset в аргументах, и C# код передает им значение DateTimeOffset.
    • Новый тип используется внутри хранимок БД.
    • Хранимки БД возвращают значения из новых добавленных полей.

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

    Трансформация Записи


    Теперь надо фиксировать смещение времени в системе, отправлять его в БД и сохранять в полях. Надо взять старое поле и изменить его на вычисляемое из нового, а новое должно теперь содержать значения. Вы уже читаете их них, теперь еще и записываете значения, а старые — наоборот, только чтение. Этот подход поможет вам изолировать изменения только для определенного раздела. Ожидаемый результат выглядит следующим образом:

    • Код C# создает значения DateTimeOffset и передает их в БД
    • Новые поля теперь являются реальными полями со значениями
    • Cтарыt поля теперь вычисляемые и используются для чтения
    • Хранимки БД сохраняют значения в новые поля

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

    Дальнейшие Шаги


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

    • Изменение типа столбца из DateTime в DateTimeOffset работает с неявным преобразованием. Смещение времени будет +00:00. Если нужно указать другой часовой пояс, необходимо использовать временный столбец.
    • Форматирование значений в строку поддержано.
    • Сравнение всеми операторами поддержано.
    • SYSDATETIMEOFFSET() без риска может заменить GETDATE()
    • Любое присвоение между DateTime и DateTimeOffset работает с неявным преобразованием.

    Операция T-SQL Комментарий
    Конвертация DateTime в DateTimeOffset TODATETIMEOFFSET(datetime_field, '+00:00') Получите значение с добавленным смещением +00:00
    Конвертация DateTimeOffset в DateTime CONVERT(DATETIME, datetimeoffset_field)
    — or — SET @datetime = @datetimeoffset
    Информация о смещении будет потеряна. При преобразовании смещение будет просто проигнорировано. Например, для '2017-04-05 10:02:00 +01:00' получите '2017-04-05 10:02:00'.
    Текущее дата/время SWITCHOFFSET(SYSDATETIMEOFFSET(), '+00:00') Это две команды. Результатом будет точка в UTC зоне
    Встроенные операции DATEPART, DATEDIFF, BETWEEN, <, >, =, etc. DATEDIFF, BETWEEN и операции сравнения учитывают временное смещение, при этом DateTime значение представляется, как значение со смещением UTC
    Форматирование CONVERT(NVARCHAR, datetimeoffset_field, 103) Получите тот же результат что и для DateTime.

    Очень интересно услышать истории о подобной трансформации от тех, кто уже проделывал такой в своих системах. А так же, кто и как поддерживает таймзоны в своих системах.
    Share post

    Comments 39

      0
      Вечная проблема DateTime в связке js (angular) <-> asp.net mvc <-> mssql (UTC в datetime), это невозможность точно сказать, какое время будет отображено у пользователя на странице. Чего только не перепробовали и сами форматировали, и использовали сторонние библиотеки, в итоге остановились на DateTimeOffset — и все заработало.
        +4
        Для нас сработал следующий подход:
        С фронтенда отдавали объект date в UTC формате (использовали momentJs). В Mongo все даты хранили в UTC. Когда нужно было вернуть дату с сервера на клиент, отдавали ее в UTC, а на клиенте приводили к локальной таймзоне.
          0
          У нас так не получалось. Дата писалась в БД (своя зона) как GETUTCDATE(), вроде бы, что может случится? Но когда мы брали дату из БД как DateTime в бэкэнде (другая зона), и передавали во angularJS фронтэнде (другая зона), то получалось, что мы теряли точное время при преобразовании. А уж когда эта дата возвращалась как параметр обратно в БД (дали клиенту выбор из дат загрузки данных, как пример), то мы точно не попадали в ту дату, которая была в БД.
          momentJs не спасал, точнее он спасал, но только до фронтэнда, для отображения, дальше DateTime по мере путешествия БД > бэкэнд > фронтэнд > бэкэнд > БД менялся и не соответствовал тому-же значению в БД.
          Сделали так: из БД брали DateTimeOffset, отдавали его во фронт, там он превращался в объект с двумя полями, одно поле тот же offset (например "/Date(1487688845183)/") которое улетало обратно как параметр, а другое уже отображаемая дата.
          DateTimeOffset после всех сериализаций и десериализаций не менялся, и точно соответствовал тому, что хранилось в БД.
            +1
            Рекомендованный подход — принимайте UTC, храните UTC.
            Если форматировать не удобно, кидайте в миллисекундах (если в 1 секунду может произойти несколько действий, и нужно выяснить что было раньше).

            Отличный ресурс https://currentmillis.com/
              +1
              Еще, 50 минута а далее.
              Fundamental problems that you all know about now - and how to explain them to junior engineers - Jon skeet

              0
              В общем случае UTC — не панацея. К примеру, сотрудник, находящийся в командировке, открывая бэкенд своей компании, должен видеть события в локальном времении компании, а не в локальном своем. «Заседание начинается в 15:00, явка обязательна».
                0
                Если сотрудник находится в другом часовом поясе, то он в любом случае не сможет лично присутствовать на собрании. А вот смотреть трансляцию по Skype вполне может. И логично отображать ему локальное время трансляции, а не серверное время UTC.
                  0
                  А через неделю? В моем проекте так и происходит, что привилегированные юзеры колесят по стране и сами создают и корректируют удаленные события — естественно, в локальном времени места события.
                    0
                    Вот вы и сохраняете время в UTC, со смещением относительно часового пояса в котором событие было создано. Когда отдадите время на фронтенд, приводите его к локальному часовому поясу. Если локальный часовой пояс совпадает с тем, в котором событие было создано — Вы компенсируете сдвиг дат. Если нет, то клиент получит актуальное время (со смещением) в своем часовом поясе.
                      0

                      Вы не поняли. Клиент — админ, он сам создает событие, скажем, в 18:00 локального времени места события, т.к. это публичное время (проще говоря, расклеено на афишах). Он оперирует только этим временем вне зависимости от его текущего местоположения.

            +1

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


            Все остальное, включая хранение UTC, это полумера. UTC решает проблему сравнения дат, но не всегда помогает правильно их отображать.


            Вообще, работа с датами — тема довольно неочевидная и с множеством подводных камней, особенно если в некоторых случаях нужны только даты без времени. Нужно всегда (как отметили уже выше) понимать, что именно нужно показать: дату события в часовом поясе пользователя, дату, как ее ввел пользователь или еще что-то.

              0
              UTC норм, если придерживаться его на входе и на выходе. Задача правильного отображения передается клиенту, он сам должен знать где он находится, в Канаде, Африке или Австралии, и какое там смещение.

              Выбор между типами DateTime, DateTimeOffset, TimeSpan и TimeZoneInfo
              Структура DateTime подходит для приложений, которые:

              работают только с датами и временем в формате UTC;


              Тип DateTimeOffset включает все функциональные возможности типа DateTime, а также сведения о часовом поясе.


              На правах личных предпочтений.
                0

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


                Но иногда возникают задачи, когда требуется узнать дату и время в системе координат пользователя.


                Например, иногда важна именно выбранная дата (без времени), и она может отличатся из-за разницы в часовых поясах. Здесь UTC не поможет, из него нельзя восстановить реально выбранное пользователем значение (без дополнительной информации о часовом поясе, скажем, из профиля).

                  0
                  Есть несколько случаев, в которых UTC не поможет, даже если все четко с его сохранением и выводом.

                  1) Если вы сохраняете информацию о будущем событии, которое должно произойти именно в 10 утра в Апреле через год во Владивостоке. Сервера все в Лондоне. В течение года правительство решает не переходить на летнее время. В этом случае поможет сохранение в локальном времени.

                  2) При переходах от зимнего к летнему и обратно у вас может теряться или дублироваться час в UTC. В этом случае помогает наличие информации о смещении времени.

                  Вообще, чтобы описать конкретный момент во времени необходимо три величины: дата/время + оффсет + название тайм зоны. Если меньше информации, то время приблизительное, как ни крути. Но для бизнес задач может быть достаточным только UTC, бесспортно. Поэтому необходимо смотреть конкретный случай.
                    +1
                    у вас может теряться или дублироваться час в UTC
                    Такие скачки происходят как раз для локального времени, так как оно определено как смещение от времени UTC. Реальная проблема заключается в том, что не все правила перевода сохраняются в базах времени. Бывают ситуации, когда перевод из UTC в локальное время может быть выполнен неправильно для достаточно старых дат (несколько лет назад), что может искажать, например, отчёты.

                    Вообще, чтобы описать конкретный момент во времени необходимо три величины: дата/время + оффсет + название тайм зоны.
                    По первым двум параметрам вопросов нет, а зачем название тайм зоны?

                    Если углубится, то при высоких требованиях к расчётам разницы между двух дат, в этот список можно добавить и количество добавленных високосных секунд — на данный момент 27.

                      0
                      Да, относительно UTC вы правы. Я запутался.

                      На счет названия таймзоны. По моим расчетам она нужна именно, чтобы получить эти карты. Офсет в большинстве случаев не нужен, т.к. можно получать локальное из UTC + TimeZoneInfo. Но без офсета будет проблема с переводом в локальное время тех моментов, которые произошли в течение этого DST перехода. Как то так.
                      0
                      информацию о будущем событии, которое должно произойти именно в 10 утра в Апреле через год во Владивостоке.

                      Эта информация — не дата, и не интервал, поэтому ни DateTime, ни DateTimeOffset не являются правильными типами для планируемых событий.

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

                      Во-вторых, «через год» — это плохо определённое понятие. Например, если сегодня 28 февраля, то что такое «через год» — 27 февраля или 1 марта? То же самое с понятием «через месяц».

                      Так что, в вашем сценарии нужно хранить локацию (координаты или код), смещение времени от начала локальных суток, а та часть, что касается будущей даты, должна храниться в виде параметров для алгоритма вычисления этой даты, и их множество не сводится к абсолютной дате или к смещению. И это ещё не вспомнили про рекуррентные события.
                        0
                        Не понял из вашего объяснения, как хранение локальной даты/времени может не работать для определения точного локального времени, безотносительно всяких переводов и политических решений.

                        В своем примере я имел ввиду, что можно хранить только название таймзоны и date/time (локальный).
                          0
                          Населённый пункт может переехать из одной таймзоны в другую, если границы зоны изменятся.
                  0
                  Рекомендую всегда брать datetimeoffset. В дотнете много неожиданностей можно отловить с datetime. Например приведение при десериализации. Причина в работе форматеров- если клиент пришлёт datetimeoffset, а параметр типа datetime, время преобразуется с учётом серверной таймзоны. В принципе не понимаю в какой ситуации это может быть ожидаемым поведением.
                    +1
                    При сериализации DateTime неожиданностей не будет, если следовать требованию выставлять Kind в DateTimeKind.Utc.
                      0
                      Расскажите как это сделать для следующего кода

                      var res1 = DateTime.Parse(«2016-03-19T12:17:33Z»)?
                      var res2 = DateTime.Parse(«2016-03-19T12:17:33-05:00»);

                      Что бы на выходе у меня было DateTime значение 2016-03-19T12:17:33 в обоих случаях.
                        0

                        В том-то и дело, что если всегда приводить время к UTC при сериализации, указанной вами неконсистентности не будет.

                          0
                          у меня js клиент и publlic Web Api — вот приведенное время к UTC по ISO «2016-03-19T12:17:33Z»- если я его десериализирую DateTime.Parse он всегда будет учитывать серверное время и выдавать ожидаемый datetime только если клиент и сервер в одной таймзоне.
                          Более того так работаю из коробки стандартный Asp.net Web Api Json форматер.
                            0
                            У меня есть контролер вида
                            IEnumerable Get(DateTime startDate)
                            {

                            }
                            я передаю в него
                            ?startDate=2016-03-19T12:17:33Z

                            и получаю дату смещеную с учетом серверного времени 15:17.

                            Единственный путь получить правильную дату не передать таймзону вообще — и в этом странность работы дотнета, которая делает небезопасным использование DateTime в качестве параметров API практически всегда.
                              0
                              получаю дату смещеную с учетом серверного времени

                              Это логично, время приводится к локальному с учетом таймзоны. Тем не менее, оно по-прежнему указывает на ту же самую точку в мировых координатах, которую вы передаете. Вот пример:
                              var d = DateTime.Parse("2016-03-19T12:17:33Z");
                              Console.WriteLine($"Value='{d}', Kind={d.Kind}, UTC='{d.ToUniversalTime()}'");
                              

                              Вывод:
                              Value='3/19/2016 5:17:33 PM', Kind=Local, UTC='3/19/2016 12:17:33 PM'

                              Локальное время учитывает +5 сервера, но Kind при этом Local, так что UTC по-прежнему то же. Как вы видите, здесь нет каких-либо манипуляций с оригинальным значением, добавлений часов и т.п.

                              Ладно, может, сериализация в MVC/API работает по-другому?
                              public ActionResult Index(DateTime time)
                              {
                              	return Json(new
                              	{
                              		value = time.ToString(),
                              		kind = time.Kind.ToString(),
                              		utc = time.ToUniversalTime().ToString()
                              	}, JsonRequestBehavior.AllowGet);
                              }
                              

                              Ответ:
                              {
                              	"value": "3/19/2016 5:17:33 PM",
                              	"kind": "Local",
                              	"utc": "3/19/2016 12:17:33 PM"
                              }
                              
                                0
                                Тем не менее, оно по-прежнему указывает на ту же самую точку в мировых координатах, которую вы передаете.

                                Нашему приложению вроде бы не нужны были таймзоны, просто локальное время — но они где-то там неявно уже были учтены и скоректировали время во время конвертации фреймворком.

                                Как мне куда-то это сохранить в базу, а потом востановить правильное время без приведения в UTC — мне же не нужны манипуляции с таймзонами, у нас же простой DateTime?
                                  0
                                  + это же DateTime тип, какая та же самая точка в мировых координатах? Это тип, который расчитан на работу с локальным временем только, можете расскажите тогда зачем в принципе DateTimeOffset?
                                    0
                                    Нет, несмотря на то, что в DateTime на самом деле нет TimeZone, установка DateTimeKind.Utc делает его мировым (как признак, без вычислений). Отличие от DateTimeOffiset в том, что последний может вычисляться с любой зоной, в то время как DateTime может быть только локальным или UTC.
                                      0
                                      Тогда бы я ожидал, что сериализатор бросит ошибку если получит не локальную и не utc таймзону — не поддерживаю таймзону, вместо молча проглотит ее сместит время на пару часов. Универсальное время в мировых координатах надо только в DateTimeOffset. если мне прислали на сервер DateTime в той модели, о которой вы говорите надо всегда быть уверенным, что и клиент и сервер всегда работают в одной таймзоне и не отправят случайно по ошибке неправильно время и всегда вовремя переключаться на DST и обратно. Иначе такая модель приведения времени просто проглотит ошибку, поломает систему и ничего не сообщит о неисправности.
                      0
                      Как определять часовой пояс пользователя? По заголовкам браузера, или просить пользователя указывать его в профиле? Что лучше? Если передавать UTC клиенту и там преобразовывать к локальному времени, то как: javascript или есть другие варианты? А если на сервере формировать, то без знания часового пояса не обойтись…
                        0
                        Хранить в локальном времени можно лишь если выполняются всё нижеперечисленное:
                        * единственным и исключительным потребителем информации о времени является тот, кто эту информацию произвёл
                        * потребителю не нужна однозначность времени.

                        Т.е., фактически, единственный приемлемый сценарий для хранения локального времени — это хранение времени как строки, в точности как она была введена, без обработки, без использования в расчётах, без передачи этой информации другим клиентам. Только чтобы приватно посмотреть на экране или распечатать. Как только начинаются расчёты, появляются несколько пользователей (сервер, другие клиенты) — немедленно переходить к однозначному представлению в хранилище (т.е. UTC) и к конвертации в локальное время на клиентах (они свои часовые пояса знают лучше, чем сервер).
                          0
                          При расчете будущих событий UTC не поможет, т.к. события, обычно, должны происходить в какое-то точное локальное время.
                          Скажем, как бы вы решали такую проблему наименьшими усилиями: надо расчитать даты запуска джоба в 10 утра по каждой из нескольких локаций, которые находятся в разных часовых поясах?
                          По моим расчетам, можно и через UTC: расчитываем последовательность, переводим в локальную, чтобы проверить 10 часов это или нет, сохраняем обратно в UTC.
                          А пожно просто забить как 10 утра локально и скедулер уже будет вызывать, без конвертаций и расчетов.

                          В чем видите тут проблему?
                            0
                            Задание, я так понимаю, выполняется на лондонском сервере, и должно отработать в 10 часов владивостокского времени, даже если Владик сейчас оффлайн и результатов не получит? Потому что если Владик онлайн, то у него может работать свой планировщик, который ровно в 10 местного времени будет делать запрос в Лондон и инициировать задачу на центральном сервере. Если Владик оффлайн, то зачем вообще нагружать сервер задачей, результатов которой тот в ближайшее время, возможно, не увидит?

                            Но, в принципе, если у вас ограниченное число хорошо известных локаций и нет проблем обновлять таймзоны по ним, то да, ваше решение тоже работает. Мои замечания больше относятся к системам, которая должны обслуживать неопределённое число клиентов в любой мыслимой и немыслимой зоне.
                          0
                          Как определять таймзону не является большой проблемой. Проблема — сохранение этой информации в цикле разных расчетов и чтений-записи данных. Если эта информация всегда будет с date/time, то проблем почти не будет.
                          +1
                          Только DateTimeOffset! Без вариантов. Никаких UTC, — это костыль.
                            0

                            дубль.

                            0

                            Нужно хранить именно DateTimeOffset, т.к. при приведении к UTC теряется информация, какое локальное время и смещение были в момент регистрации события.


                            А эта информация может быть важна — выше привели много примеров.
                            И кстати, если на машине, где регистрировалось событие, часовой пояс был выставлен неверно, то полная исходная информация поможет выявить ошибку.


                            Кроме того, зачем вообще терять часть информации на входе, если к UTC всегда можно привести позже, если, к примеру, в отчете нужно показывать время в UTC.


                            И также верно выше заметили, что если совсем по-хорошему, то кроме смещения нужно хранить и название (или код) тайм-зоны.

                              0
                              Даже представить себе не мог, что в России, с её 11-ю часовыми поясами, возможен иной подход, кроме как DateTimeOffset. Когда, несколько лет назад, переводил старые и не очень мудрые проекты на правильный формат хранения даты, всегда приводил в пример Россию — мол, выйдем на её рынок, а там офисы одной компании в разных часовых поясах. А оказывается, от оно чё!

                              Что до подхода с конвертацией времени в UTC, то как пояснил sand14, этот подход неверен, и порождает проблемы в марте и октябре, когда многие страны переходят на другое время. Или не переходят. Или переходят, но только один раз, но мы этого не знаем. В нашем ПО мы начисляем зарплату за отработанное время, бывают и ночные смены — если человек отработал 4-часовую смену, которая началась в полночь, а закончилась в 3 часа ночи… Ну, вы поняли.

                              Only users with full accounts can post comments. Log in, please.