Еще про внедрение таймзон в долгоживущий проект

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

    TL;DR


    • Необходимо различать термины:
      • UTC — локальное время в зоне +00:00, без эффекта DST
      • DateTimeOffset — локальное время со смещением от UTC ±NN:NN, где смещением является базовое смещение от UTC без эффекта DST (в C# TimeZoneInfo.BaseUtcOffset)
      • DateTime — локальное время без информации о таймзоне (мы игнорируем признак Kind)
    • Разделение использования на внешнее и внутренее:
      • Входящие и исходящие данные через API, сообщения, файловые экспорты/импорты должны быть строго в UTC (тип DateTime)
      • Внутри системы данные храняться вместе со смещением (тип DateTimeOffset)
    • Разделение использования в старом коде на не-БД код (C#, JS) и БД:
      • не-БД код оперирует только с локальными значениями (тип DateTime)
      • БД работает с локальными значениями + смещение (тип DateTimeOffset)
    • Новые проекты (компоненты) используют DateTimeOffset.
    • В БД тип DateTime просто меняется на DateTimeOffset:
      • в типах полей таблиц
      • в параметрах хранимок
      • в коде фиксятся несовместимые конструкции
      • к пришедшему значению присоединяется информация о смещении (простая конкатенация)
      • перед отдачей в не-БД код значение приводится к локальному
    • Никаких изменений в не-БД коде
    • DST решается использованием CLR Stored Procedures (для SQL Server 2016 можно использовать AT TIME ZONE).


    Теперь детальнее о преодоленных сложностях.

    «Вшитые» стандартны IT индустрии


    Потребовалось довольно много времени, чтобы избавить людей от страха хранить даты в локальном времени со смещением. Некоторое время назад, если спросить программиста с опытом: «Как поддержать таймзоны?» — единственным вариантом был: «Используй UTC, а конвертируй в локальное время только перед показом». Тот факт, что для нормальной работы все равно необходима дополнительная информация, такая, как смещение и названия таймзон, был спрятан под капотом реализации. С появлением DateTimeOffset такие детали вылезли наружу, но инертность «программистского опыта» не позволяет быстро согласиться с другим фактом: «Хранение локальной даты с базовым смещением от UTC» — это тоже самое, что хранение UTC. Еще один плюс использования DateTimeOffset повсеместно позволяет делегировать контроль за соблюдением таймзон .NET Framework и SQL Server, оставив для человеческого контроля только моменты ввода и вывода данных из системы. Под человеческим контролем я имею ввиду написанный программистом код для работы с date/time значениями.

    Чтобы преодолеть подобный страх пришлось провести не одну сессию с разъяснениями, представляя примеры и Proof Of Concept. Чем проще и ближе примеры к тем задачам, которые решаются в проекте, тем лучше. Если пускаться в рассуждения «вообще», то это приводит к усложнению понимания и трате времени впустую. Коротко: меньше теории — больше практики. Аргументы за UTC и против DateTimeOffset можно отнести к двум категориям:

    • «UTC all the time» является стандартом и остальное не работет
    • UTC решает проблему с DST

    Следует отметить, что ни UTC, ни DateTimeOffset не решают проблему с DST без использования информации о правилах конвертации между зонами, которая доступна через класс TimeZoneInfo в C#.

    Упрощенная Модель


    Как выше отметил, в старом коде изменения происходят только в БД. Как именно это работает можно оценить на простом примере.

    Пример модели в T-SQL
    -- 1) сохранение данных
    -- входящие данные в локали пользователя, как он их видит
    declare @input_user1 datetime = '2017-10-27 10:00:00'
    -- в конфигурации пользователя есть информация о зоне
    declare @timezoneOffset_user1 varchar(10) = '+03:00'
     
    declare @storedValue datetimeoffset
    -- при получении значений присоединяем смещение пользователя
    set @storedValue = TODATETIMEOFFSET(@input_user1, @timezoneOffset_user1)
    -- это значение будет сохранено
    select @storedValue 'stored'
     
    -- 2) отображение информации
    -- в конфигурации 2го пользователя указана другая таймзона
    declare @timezoneOffset_user2 varchar(10) = '-05:00'
    -- перед выдачей в клиентский код значения приводятся к локальным
    -- так будут выглядеть данные в базе и на дисплеях пользователей
    select
    @storedValue 'stored value',
    CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow',
    CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY'
     
    -- 3) теперь 2й пользователь сохраняет данные
    declare @input_user2 datetime
    -- на вход приходят локальные значения, как он их видит в Нью-Йорке
    set @input_user2 = '2017-10-27 02:00:00.000'
     -- соединяем с информацией о смещении
    set @storedValue = TODATETIMEOFFSET(@input_user2, @timezoneOffset_user2)
    select @storedValue 'stored'
     
    -- 4) отображение информации
    select
    @storedValue 'stored value',
    CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user1)) 'user1 Moscow',
    CONVERT(DATETIME, SWITCHOFFSET(@storedValue, @timezoneOffset_user2)) 'user2 NY'
    


    Результат выполнения скрипта будет следующим.



    По примеру видно, что данная модель позволяет делать изменения только в БД, что значительно уменьшает риск возникновения дефектов.

    Примеры функций для обработки date/time значений
    -- При получении значений из не-БД кода в DateTimeOffset они будут локальными, но со смещением +00:00, поэтому необходимо присоединить смещение юзера, но конвертировать между поясами нельзя. Для этого переведем значение в DateTime и потом обратно уже с указанием смещения
    -- DateTime без проблем конвертируется в DateTimeOffset, поэтому изменять вызов хранимок в клиентском коде не надо
    
    create function fn_ConcatinateWithTimeOffset(@dto datetimeoffset, @userId int)
    returns DateTimeOffset as begin
        declare @user_time_zone varchar(10)
        set @user_time_zone = '-05:00' -- из настроек юзера @userId
        return todatetimeoffset(convert(datetime, @dto), @user_time_zone)
    end
    
    -- Клиентский код не может читать DateTimeOffset в переменные типа DateTime, поэтому необходимо не только сконвертировать в в нужную таймзону, но и привести к DateTime, иначе произойдет ошибка 
    
    create function fn_GetUserDateTime(@dto datetimeoffset, @userId int)
    returns DateTime as begin
        declare @user_time_zone varchar(10)
        set @user_time_zone = '-05:00' -- из настроек юзера @userId
        return convert(datetime, switchoffset(@dto, @user_time_zone))
    end
    


    Маленькие Артифакты


    В ходе адаптации SQL кода были обнаружены некоторые вещи, которые работают для DateTime, но несовместимы с DateTimeOffset:

    • GETDATE()+1 надо заменить на DATEADD(day, 1, SYSDATETIMEOFFSET())
    • ключевое слово DEFAULT несовместимо с DateTimeOffset, надо использовать SYSDATETIMEOFFSET()
    • конструкция ISNULL(date_field, NULL) > 0" работает с DateTime, но для DateTimeOffset должна быть заменена «date_field IS NOT NULL»

    Заключение или UTC vs DateTimeOffset


    Кто-то может заметить, что как и в подходе с UTC мы занимаемся конвертацией при получении и при отдаче данных. Тогда зачем это все, если есть проверенное и работающее решение? Есть несколько причин этому:

    • DateTimeOffset позволяет забыть где находится SQL Server.
    • Это позволяет переложить часть работы на систему.
    • Конвертации можно свести к минимуму, если DateTimeOffset используется везде, делая их только перед отображением данных или выдачи их во внешние системы.

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

    Комментарии 62

      0
      «Хранение локальной даты с базовым смещением от UTC» — это тоже самое, что хранение UTC
      Нет, это больше, чем просто UTC. В России часто происходит дурдом с изменением зон в разных городах, типа давайте с нового года жить в UTC+03 вместо UTC+04, или давайте отменим летнее время, потом снова его введём, потом снова отменим.

      Поэтому, например, для программы кадрового учёта, которая хранит время сотрудников на работу, UTC — лишние проблемы (если посмотреть на 2 года в прошлое, 5:00/UTC — это опоздание на час или приход вовремя?)

      Конвертации можно свести к минимуму, если DateTimeOffset используется везде, делая их только перед отображением данных
      Тут всё равно нужен анализ ситуаций. Иногда время 15:00 MSK показывать в Нью-Йорке как есть (с припиской «по Москве»), иногда конвертить в местное.
      • НЛО прилетело и опубликовало эту надпись здесь
          –1
          Так и есть. Просто адепты UTC пропускают этот факт: все равно надо будет обеспечить хранение смещения. Толька в случае с UTC all the time самостоятельно с вытекающими из этого «напрягами».
          • НЛО прилетело и опубликовало эту надпись здесь
              +1
              Имеет смысл или нет зависит от проекта. Если у вас не было поддержки таймзон, то переписывать в любом случае придется. И если сравнивать два подхода с UTC-all-the-time и DateTimeOffset, то при равных затратах на переписывание, второй проще технически.
        +1
        Таймзона это безусловно дополнительная информация ко времени события. Но описанный случай со временем прихода на работу является практически исключительным, и в этом случае я бы даже хранил таймзону как отдельное поле ) чтобы всем было понятно что эта информация которую нельзя потерять при всяких конвертациях ) Но если вам необходимо просто абсолютное время произошедшего события то таймзона лишняя информация которой просто не должно быть на уровне БД.
        Используя принцип Оккама не умножать сущности без необходимости )
        Вывод времени события в таймзоне пользователя относится к презентационной логике и конвертация в локальное время пользователя по моему скромному времени должна быть только там а не в хранимых процедурах и не в каком либо t-sql.

        То же самое относится к загрузкам данных, у разных источников данных могут быть разные соглашения о формате времени и таймзоне, но задачу конвертации в один общий формат и одну таймзону БД лучше решать именно там в каждом конкретном сервисе загрузки данных.
        Кстати таймзона БД не обязательно должна быть UTC, иногда удобнее хранить данные в таймзоне локальной для основной команды разработки. Но главное чтобы к этой таймзоне не применялись правила DST. То есть если EST так уж EST никаких там ESD.
          +3
          Что то мне подсказывает то все эти проблемы и изыскания от непонимания того, что такое «UTC» и что такое «Часовой пояс» (Timezone). А самое главное, как правильно это использовать.
          единственным вариантом был: «Используй UTC, а конвертируй в локальное время только перед показом»

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

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

          Чтобы не было никаких проблем достаточно 3 вещи:
          1. Время везде хранись в UTC
          2. Знать список правил, как устанавливать смещение относительно UTC (В зависимости от часового пояса, времени года, страны)
          3. Знать Часовой пояс пользователя, а так же страну его местонахождения.

          Если я не прав, рад бы был услышать возможные проблемы в данном подходе.
            0
            Проблему я описал в первом комментарии. Она возникает, когда для события важно знать локальное время и показывать даты в часовой зоне события, а не в часовой зоне клиента, отображающего данные.
              +1
              Этот конкретный юзкейс решается хранением данных о том, где и в каком часовом поясе это событие произошло.
              Причем для любого кол-ва событий произошедших географически в одном месте достаточно хранить всего 1 значение с информацией о том, где и в каком часовом поясе событие произошло.

              В физическом же смысле не важно где произошло событие, если оно произошло, то произошло одновременно во всех существующих часовых поясах.
                –1
                для любого кол-ва событий произошедших географически в одном месте достаточно хранить всего 1 значение с информацией о том, где и в каком часовом поясе событие произошло
                Это неудобно, т.к. придётся к этому месту привязывать историю изменений его часового пояса. Гораздо прозрачнее текущую таймзону сохранять вместе с событием.
                  –1
                  > В физическом же смысле не важно где произошло событие, если оно произошло, то произошло одновременно во всех существующих часовых поясах.

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

                  А по топику — ваш подход к UTC, как «единственному правильному» способу хранения дат — пример упрощённого и шапкозакидательского подхода к работам с датами. Благодаря таким же чувакам мне сейчас приходится на смартфоне использовать временную зону Кувейта, из-за долговременной стабильности оной (и невозможности часто просто указать фиксированную временную зону как смещение). То тут, то там оказывается, что кто-то использовал то или иное умолчание, которое, почему-то, оказалось некорректным.

                  По факту, термины: 1) время в UTC, 2) время в локальной временной зоне (политически установленной), 3) время в временной зоне ±IJKL, где IJKL — цифры, 4) время в т.н. «floating» временной зоне — описывают разные явления нашей жизни. И подход «используй везде UTC», как минимум, вследствие этого факта, неверен.

                  P.S. Также неверно путать системное время в компьютере (в т.ч. в СУБД и в других серверах) и время как физическую абстракцию/модель. В частности, в компьютере время не монотонно возрастающее. :)
                    0
                    В частности, в компьютере время не монотонно возрастающее. :)
                    Я подзабыл значение монотонный, вы имеете в виду что в компьютере дискретно?
                      0

                      Монотонность — отсутствие движения значения функции/элементов последовательности в другом направлении. Дискретность — это другая беда, общая для программирования вообще.

                        0
                        Я удивлен. Запросто можно получить одно и то же время на том же компьютере, но кажется я не встречал что бы таймер шел назад. Связан ли обратный отсчет с несколькими процессорами? Может связан с NUMA…
                          0

                          Он связан с возможностью крутить системные часы в любую сторону. Этим занимается либо пользователь — либо служба синхронизации времени (на линуксе — ntpd). А еще иногда на материнке дохнет батарейка.

                      0
                      Благодаря таким же чувакам мне сейчас приходится на смартфоне использовать временную зону Кувейта, из-за долговременной стабильности оной

                      А для меня такие чуваки, на моем смартфоне запилили галочку «Устанавливать время автоматически»
                      И вот уже 10 лет как со временем у меня нет проблем.
                      А по топику — ваш подход к UTC, как «единственному правильному» способу хранения дат — пример упрощённого и шапкозакидательского подхода к работам с датами

                      Этот поход придумал не я, его годами выверяли и стандартизировали. Но в вашем мировоззрении конечно все дурачки, напридумывали всякой фигни. Лучше уж свои велосипеды строить. К слову даже в автоматике в системах реального времени хренят и используют время в UTC, потому что намного меньше проблем с тем чтобы преобразовать время перед отправкой клиенту, и не преобразовывать его больше вообще нигде.
                        0
                        А для меня такие чуваки, на моем смартфоне запилили галочку «Устанавливать время автоматически»
                        И вот уже 10 лет как со временем у меня нет проблем.

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

                        Этот поход придумал не я, его годами выверяли и стандартизировали. Но в вашем мировоззрении конечно все дурачки, напридумывали всякой фигни

                        Понимаете, в чем дело — у подобных стандартов есть область применения, и не всегда стоит их слепо применять. Всегда нужно думать о своей задаче. Например, для задачи «контролировать опоздания работников» локальное время важно, а UTC и таймзоны нет. Решать задачу через несвязанные с ней абстрактные (хоть и стандартные) инструменты — несколько неправильно, не находите?
                +2
                Главная проблема datatype'ов с поддержкой таймзон — отсутствие единого железного стандарта о том, как они должны работать. Как видим, даже внутри SQL Server поведение типа DateTimeOffset немного отличается от DateTime.

                Что будет, если понадобится выгрузить эти данные для обработки в Hive, в BigQuery, в системы аналитики, в визуализаторы (Tableau etc.)? Что будет, если захотим поменять СУБД целиком?
                  +1
                  Спасибо за статью. Но все на самом деле еще сложнее и интереснее (немного больше подробностей тут Подводные камни Date & Time — Илья Фофанов).
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      Это артифакты доставшиеся из далекого прошлого. Было любопытно их обнаружить.
                      0

                      Полностью согласен с коллегой DexterHD. Если все данные в системе хранятся и обрабатываются в UTC, то остается всего одна проблема — определить желаемую временнУю зону при представлении этих данных конечному пользователю. Другие варианты не обладают универсальностью и хороши лишь в определенных условиях и до тех пор, пока эти условия не изменятся.

                        –2
                        Оба варианта решают задачу. DateTimeOffset делает решение проще, на мой взгляд. Описал тут в ответе. Что думаете?
                        +1
                        Прямо «неделя проповедничества DateTimeOffset» на Хабре…
                          0

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


                          Для абстрактного проекта действительно даты надо хранить либо в UTC, либо в формате с указанием смещения от UTC. Это позволит в крайнем случае скачать базу данных временных зон и получить местное время — почти всегда, так как местное время иногда устанавливается волевым решением руководства лодочной станции. Это позволяет синхронизировать события. Это устраняет некоторые странные проблемы. Но пример выше показывает, что в некоторых случаях это недостаточно. Просто надо помнить что время — самая сложная концепция в программировании (после выбора названия для объектов).

                            +2
                            Действительно инертность есть. Не хочется перелопачивать код в DateTimeOffset. Просмотрел статью, не убедила в использовании этого другого подхода. Может быть кто-нибудь кратко объяснит, в чем преимущество этого способа перед «везде внутри используем UTC»?
                              0
                              Например, следующие вещи меня убедили в том, что DateTimeOffset лучше чем UTC all the time.

                              Меньше волнений из-за:
                              • меньше волнений из-за сервера БД, т.к. он может работать в любой зоне (если кто-то случайно поменял ее — пофиг)
                              • меньше кода проверок таймзоны входящих значений (например, например, проверка всяких Kind и действительно ли пришедшая дата в зоне N, а не UTC?)
                              • проще соглашения по использованию дат внутри системы (не надо постоянно заботиться о передаче UTC)
                              • проще обработка данных из разных таймзон, т.к. многие рутинные вещи выполняет фреймворк
                              • новые C# проекты могут вовсе заботиться о конвертации только в момент, когда выводится пользователю. А сохранять в БД значения со смещением, которое предоставляется система. Т.е. разрабу вообще не надо будет думать о всяких конвертациях и UTC пока он не возвращает данные наружу (экран или внешние системы)

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

                              НО:
                              При чтении SQL DateTimeOffset в C# DateTime возникает исключение. Затраты на компенсацию соизмеримы с затратами по поддержке UTC. При этом смещение вам, скорее всего, все равно понадобится хранить рано или поздно. Только с подходом UTC вы обрекаете себя на дополнительные муки. Так же не стоит забывать о проблемах, которые связаны с DateTime.
                                0

                                Простите, но какие проблемы могут в принципе возникнуть при смене временной зоны сервера в подходе "all UTC"? Зачем проверять Kind если известно, что все даты — в UTC?

                                  0
                                  Потому что DateTime приводится к UTC в зависимости от Kind. Если проскочит неверный, то возникнет ошибка, которую вы просто так не отловите. Например, кто-то забыл выставить Kind, или наоборот выставил, когда не надо было. Вручную конвертацию производить? Без четкого обозначения смещения, вы обрекаете девелопера помнить об этом постоянно. А потом еще и тикеты из саппорта разгребать.
                                    –1
                                    Про сервер. Потому что значения, сохраненные, как бы, в UTC после переезда сервера в другую зону, тоже переедут, т.к. рассчитывались относительно таймзоны смещения сервера. Задачу можно решить, но опять же, за счет больших напрягов для девелоперов: необходимо использовать только правильные методы работы с датами.
                                      0

                                      Да с чего им переезжать-то?!

                                        0
                                        Не понял вопрос. Кому «им»?
                                          0

                                          Датам, кому же еще. Вот лежит у меня в базе дата в UTC. Я меняю у сервера тайм-зону. Что, по-вашему, случится с датой?

                                            –2
                                            Давайте рассмотрим случай:
                                            — из базы достается ДТ значение без оффсета (DateTime)
                                            — в коде C# вы конвертируете это в зону юзера, предполагая, что пришли UTC

                                            1) Проблема с чтением из БД
                                            var utc = new DateTime(2017, 4, 4, 10, 0, 0); // from DB expecting UTC
                                            var targetTz = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
                                            var local = TimeZoneInfo.ConvertTime(utc, targetTz);
                                            
                                            Console.WriteLine("utc: {0}", utc);
                                            Console.WriteLine("local: {0}", local);
                                            


                                            Результат:
                                            utc: 04/04/2017 10:00:00
                                            local: 04/04/2017 10:00:00


                                            Решение: Надо поставить Kind в Utс, или использовать TimeZoneInfo.ConvertTimeFromUtc().

                                            2) Проблема с датами при сохранении
                                            Суть примера: юзер вводит два раза одну и туже дату, но первую он вводит пока сервер в одной таймзоне, а вторую — когда в другой. Затем сервер пытается привести все к дате юзера и терпит неудачу, т.к. они не равны. А должны быть одинаковые.

                                            var server1Timezone = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
                                            var server2Timezone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
                                            
                                            var userInput1 = new DateTime(2017, 4, 4, 13, 0, 0);
                                            var utc1 = TimeZoneInfo.ConvertTimeToUtc(userInput1, server1Timezone);
                                            
                                            var userInput2 = new DateTime(2017, 4, 4, 13, 0, 0);
                                            var utc2 = TimeZoneInfo.ConvertTimeToUtc(userInput2, server2Timezone);
                                            
                                            var targetTz = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time");
                                            var local1 = TimeZoneInfo.ConvertTimeFromUtc(utc1, targetTz);
                                            var local2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, targetTz);
                                            
                                            Console.WriteLine("display: {0}", local1);
                                            Console.WriteLine("display: {0}", local2);
                                            

                                            Результат:
                                            display1: 04/04/2017 13:00:00
                                            display2: 04/04/2017 20:00:00

                                            Следует учесть, что такие строчки, как TimeZoneInfo.ConvertTimeToUtc(userInput1, server1Timezone); будут выполняться самой системой, т.е. это придется как-то решать. И если пропустите, то никто не упадет и не скажет об ошибке. Например, в SQL и в C# у вас могут использоваться такие вещи, которые оперируют с локальной таймзоной сервра. Например, GETDATE() или TimeZoneInfo.ConvertTime(). Вам надо будет постоянно следить за правильностью кода.

                                            DateTimeOffset убирает «предположения» из работы с датами.
                                              0

                                              С какого перепугу у вас дата, введенная пользователем, интерпретируется исходя из тайм-зоны сервера?

                                                –1
                                                Это же только пример, показывающий природу неоднозначности DateTime.
                                                  0

                                                  В таком случае, вот это — пример, показывающий неоднозначность типа данных int:


                                                  var x = 42;
                                                  x = x+1;
                                                  Console.WriteLine($"{x} = 42");
                                                    –1
                                                    Речь о том, что для правильной работы с UTC-all-the-time вам надо постоянно помнить о том, что:
                                                    — какая зона у юзера
                                                    — правильно ли я произвел конвертацию
                                                    — правильно ли передался Kind в момент, когда это важно

                                                    C DateTimeOffset вам придется об этом думать только в момент перед выдачей результата (на экран или во внешнюю систему).
                                                      0

                                                      Не нужно это все помнить. Подход UTC-all-the-time подразумевает, что конвертация производится ровно 1 раз, на границе подхода. Надо просто сделать ее и забыть.

                                                        –1
                                                        Такой оптимизм рождает потом баги.
                                                          –1

                                                          Баги рождает не оптимизм, а отсутствие соглашений.

                                                0
                                                Решение: Надо поставить Kind в Utс, или использовать TimeZoneInfo.ConvertTimeFromUtc().

                                                Так и используйте, в чем проблема?

                                                первую он вводит пока сервер в одной таймзоне, а вторую — когда в другой

                                                Таймзона сервера тут ни при чем. Должно быть примерно так:

                                                var userTimezone = getUserTimeZoneFromSomeStorage();
                                                
                                                var userInput1 = new DateTime([params from user input]);
                                                var utc1 = TimeZoneInfo.ConvertTimeToUtc(userInput1, userTimezone);
                                                
                                                var userInput2 = new DateTime([params from user input]);
                                                var utc2 = TimeZoneInfo.ConvertTimeToUtc(userInput2, userTimezone);
                                                
                                                // save to DB
                                                
                                                ...
                                                
                                                // show data from DB
                                                
                                                var utc1 = new DateTime([params from DB]);
                                                var utc2 = new DateTime([params from DB]);
                                                
                                                var userTimezone = getUserTimeZoneFromSomeStorage();
                                                
                                                var local1 = TimeZoneInfo.ConvertTimeFromUtc(utc1, userTimezone);
                                                var local2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, userTimezone);
                                                
                                                Console.WriteLine("display: {0}", local1);
                                                Console.WriteLine("display: {0}", local2);
                                                


                                                В БД положили, и оно там лежит. Изменение таймзоны БД на сохраненные данные не влияет. Изменение таймзоны сервера на выполняющийся код не влияет. Таймзона пользователя в пределах запроса одна и та же. Все выборки с фильтрами или вычисления в бизнес-логике можно делать в UTC, конвертирование только при выводе.
                                                  0
                                                  Никто не спорит, что с UTC это можно. Но зачем руками, когда DateTimeOffset позволяет все тоже самое с меньшими усилиями?
                                                    0
                                                    А какой код будет с DateTimeOffset?

                                                    А какой код будет с DateTimeOffset, если одни и те же данные десятиминутной давности могут смотреть 2 пользователя в разных таймзонах?
                                                      0
                                                      Я к тому, что в статье у вас вроде то же самое, только на SQL. Зачем использовать связку SQL + C#, если можно использовать просто C#?
                                                        –1
                                                        Потому что при использовании C# вы будете делать компенсирующие вещи, такие как конвертация в UTC и обратно, учет Kind и прочее. При «связке» вам ничего ненадо делать, т.к. работаете так, словно зон нет ровно до момента вывода данных. Я обобщаю, конечно, т.к. работать надо будет с DateTimeOffset, что чуть-чуть иначе, чем с DateTime.
                                                +2
                                                Я меняю у сервера тайм-зону. Что, по-вашему, случится с датой?
                                                Когда есть куча legacy-кода, в котором есть вызовы DateTime.Now и GETDATE(), причём ещё как-то неявно (например, из сторонних компонент), то смена зоны сервера приложений или сервера БД станет проблемой.

                                                Если сразу всё писалось и тестилось с DateTime.UtcNow и GETUTCDATE(), то проблемы не будет.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                          0
                                          Выше ответил.
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Как я понял, Вы говорите только о случае, когда вы десериализуете значение, но упускаете из виду ситуацию, когда десериализованная дата передается в другой компонент, который тоже имеет логику вокруг таймзон.
                                      0
                                      Мы использовали очень похожий подход. Спасибо за статью
                                        0

                                        Жесткий vendor lock, обратная несовместимость, неполнота локальной информации, а реальных преимуществ нет.
                                        Пример с опозданием на работу в любом случае требует привязки к конкретному офису и его расположению — в эту информацию таймзона обязательно входит.
                                        Пока полученный результат больше всего похож на велосипед с квадратными колесами.
                                        Что только люди ни придумают лишь бы UTC не хранить.

                                          0
                                          Пример с опозданием на работу в любом случае требует привязки к конкретному офису и его расположению — в эту информацию таймзона обязательно входит.
                                          Однако, таймзона в Усть-Урюпинске сейчас и тайм-зона там же 2 года назад — совершенно разные вещи ))) И нормального надёжного способа получить её нет, разве что парсить местные газеты с местными указами.
                                            +1

                                            Распорядок дня с привязкой к конкретной организации, должности, смене тоже сам собой не нарисуется. Зону за прошлое получить не проще и не сложнее. А вот что в смещении после указов Медведева были ошибки и много — 99%. Таймзону уточнить и исправить достаточно однажды, а вот со смещениями придется повозиться.
                                            Даже в идеальном для авторов идеи варианте получается не айс.

                                              +1

                                              Есть велосипеды со специальными колесами для езды по ступенькам и на них действительно удобно ездить по ступенькам.


                                              image


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

                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                  +1

                                                  Реальные велосипеды реального мира, если что:
                                                  image

                                              0
                                              Что только люди ни придумают лишь бы UTC не хранить.

                                              DateTimeOffset эквивалентно хранению UTC. При условии, что используется базовое смещение от UTC.
                                                0

                                                Только зачем он нужен?
                                                Усложнение, ломающее изменение, избыточная информация, отсутствие поддержки от большинства СУБД.
                                                И все это ради вариантов использования, которые все равно требуют дополнительной информации о месте события, в которое все равно входит таймзона.
                                                Про время же события необходимо и достаточно хранить UTC.
                                                Получается простое, чистое и элегантное сочетание преждевременной оптимизации с денормализацией.

                                                  +1
                                                  Если сразу делать в UTC, это можно.
                                                  А вот для миграции (причём на лету, без остановки) системы с длинной историей развития несовместимые типы очень кстати — видно, где уже поправили, а где ещё нет.
                                                    0

                                                    Если специально нужен несовместимый тип то в нем надо хранить UTC без всякой дополнительной мишуры.
                                                    DateTimeOffset — удобная вещь для расчетов, но не для хранения.

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

                                            Самое читаемое