Перевод статьи, опубликованной в 2011 г.

DateTime.Now — одно из наиболее часто используемых свойств в .NET Framework. Несмотря на то, что это свойство предназначено для определенных целей, из-за недостатка понимания и сноровки многие .NET-разработчики используют его при неправильных обстоятельствах, когда следует использовать другие доступные (и рекомендованные) варианты, такие как свойство DateTime.UtcNow и класс Stopwatch. В этой статье мы обсудим эти три варианта, область применения каждого из них и проведем количественное сравнение между ними, чтобы показать, почему DateTime.Now во многих случаях обходится нам слишком дорого и не должно быть использовано.

Введение

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

Одной из самых распространенных ошибок у разработчиков является злоупотребление свойством DateTime.Now, предназначенного для определенных целей, но слишком часто используемого в случаях, когда рекомендовано использовать свойство DateTime.UtcNow или класс Stopwatch.

В этой статье мы рассмотрим три варианта работы с DateTime значениями в .NET Framework, изучим их внутреннюю работу, где и когда их следует использовать, а после проведем количественное сравнение между ними, чтобы понять, почему использование DateTime.Now следует ограничить.

DateTime.Now

Now — это хорошо известное и часто используемое свойство структуры DateTime из .NET, которое просто возвращает текущую дату и время на компьютере, выполняющем код. Такое свойство предоставляется почти в всеми языками программирования в качестве встроенной фичи и имеет множество применений. К сожалению, большинство .NET-разработчиков годами неправильно используют это свойство, не подозревая о его изначальном предназначении. Я могу предположить две возможные причины этой проблемы:

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

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

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

Скажем так, свойство Now структуры DateTime НЕ ПРЕДНАЗНАЧЕНО для использования в случаях, когда вы хотите получить время для внутренних вычислений в вашей программе, сохранить DateTime значение в базе данных, или рассчитать производительность фрагмента кода во время выполнения. Напротив, оно ПРЕДНАЗНАЧЕНО для тех ситуаций, когда вы хотите отобразить текущую дату и время машины для ваших пользователей или сохранить какое-либо значение в локальных логах, где вы хотели бы использовать локальное время.

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

Листинг 1: Внутренняя реализация DateTime.Now

public static DateTime Now
{
      get
      {
            DateTime utcNow = DateTime.UtcNow;
            bool isAmbiguousDst = false;
            long ticks = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utcNow, 
                  out isAmbiguousDst).Ticks;
            long num = utcNow.Ticks + ticks;
            if (num > 3155378975999999999L)
            {
                  return new DateTime(3155378975999999999L, DateTimeKind.Local);
            }
            if (num < 0L)
            {
                  return new DateTime(0L, DateTimeKind.Local);
            }
            return new DateTime(num, DateTimeKind.Local, isAmbiguousDst);
      }
}

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

DateTime.UtcNow

Еще одно одно очень популярное свойство DateTime — это UtcNow, хотя оно не используется так часто, как свойство Now. Это свойство возвращает текущую дату и время в формате UTC. Это свойство предназначено для использования во многих местах, где ошибочно используется Now (я перечислил некоторые из основных примеров в предыдущем разделе).

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

Как можно заметить ниже, UtcNow имеет более простую реализацию, чем Now (листинг 2). На самом деле свойство Now было написано на основе UtcNow с некоторыми дополнениями

Листинг 2: Внутренняя реализация DateTime.UtcNow

public static DateTime UtcNow
{
      [TargetedPatchingOptOut("Performance critical to inline across NGen 
            image boundaries"), SecuritySafeCritical]
      get
      {
            long systemTimeAsFileTime = DateTime.GetSystemTimeAsFileTime();
            return new DateTime((ulong)(systemTimeAsFileTime + 
                  504911232000000000L | 4611686018427387904L));
      }
}

Stopwatch

Менее популярный инструмент DateTime вычислений в .NET — это Stopwatch, класс который разработан, чтобы помочь программистам вычислять производительность фрагмента кода во время его выполнения. Большинство программистов, как правило, проставляют DateTime.Now и DateTime.UtcNow до и после фрагмента кода, чтобы определить время, необходимое для его выполнения.

Stopwatch полагается на публичный статический метод GetTimeStamp, который работает в двух режимах. Если он не используется в режиме высокого разрешения, он применяет DateTime.UtcNow, в противном случае он применяет QueryPerformanceCounter из Windows API (листинг 3), который мы обсудим в следующем разделе.

Листинг 3: Реализация GetTimeStamp Stopwatch

public static long GetTimestamp()
{
      if (Stopwatch.IsHighResolution)
      {
            long result = 0L;
            SafeNativeMethods.QueryPerformanceCounter(out result);
            return result;
      }
      return DateTime.UtcNow.Ticks;
}

QueryPerformanceCounter

То, о чем я собираюсь рассказать, вряд ли входит в перечень вещей, которые должен использовать или даже просто учитывать рядовой .NET-разработчик, тем не менее QueryPerformanceCounter — это метод Windows API, который закулисно используется некоторыми встроенными типами .NET. Это считается устаревшим подходом в .NET-разработке, ведь здесь вам необходимо использовать API интероперабельности, чтобы получить доступ к функции, которая обеспечивает доступ к счетчику производительности с высоким разрешением (листинг 4).

Листинг 4. Применение интероперабельности для использования QueryPerformanceCounter

[DllImport("Kernel32.dll")]
public static extern void QueryPerformanceCounter(ref long ticks);

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

Сравнение

Нет ничего лучше, чем видеть конкретные числа, отражающие то, о чем я говорю выше. Для этого нам понадобятся какие-нибудь простенькие программы, которые сравнивают производительность DateTime.Now, DateTime.UtcNow и Stopwatch. Обычно трудно сравнивать свойства структуры DateTime с Stopwatch, поскольку они отличаются по своей природе, однако, проявив немного смекалки при выборе примеров, можно связать их вместе и сравнить их производительность во время выполнения.

Я написал три фрагмента кода, которые достигают одной цели, используя эти три подхода. Я генерирую выборки разного размера (увеличиваясь на 500, получая 10 пакетов выборок данных) и выполняю очень простую (и бессмысленную) задачу. Я использую DateTime.Now, DateTime.UtcNow и Stopwatch, чтобы рассчитать время, необходимое для работы моего кода. Я измеряю затраченное время с помощью QueryPerformanceCounter, чтобы получить более высокую точность.

Листинг 5: Код для тестирования DateTime.Now

private static void TestDateTimeNow()
{
    Console.WriteLine("Testing DateTime.Now ...");
 
    for (int sampleSize = 500; sampleSize <= 5000; sampleSize += 500)
    {
        long start = 0;
        QueryPerformanceCounter(ref start);
 
        for (int counter = 0; counter < sampleSize; counter++)
        {
            DateTime startTime = DateTime.Now;
 
                int dumbSum = 0;
                for (int temp = 0; temp < 5; temp++)
                    dumbSum++;
 
            DateTime endTime = DateTime.Now;
 
            TimeSpan duration = endTime - startTime;
        }
 
        long end = 0;
        QueryPerformanceCounter(ref end);
            
        long time = 0;
        time = end - start;
 
        Console.WriteLine("Sample Size: {0} - Time: {1}", sampleSize, time);
    }
}

Очень похожий код можно использовать с DateTime.UtcNow (листинг 6).

Листинг 6. Код для тестирования DateTime.UtcNow

private static void TestDateTimeUtcNow()
{
    Console.WriteLine("Testing DateTime.UtcNow ...");
 
    for (int sampleSize = 500; sampleSize <= 5000; sampleSize += 500)
    {
        long start = 0;
        QueryPerformanceCounter(ref start);
 
        for (int counter = 0; counter < sampleSize; counter++)
        {
            DateTime startTime = DateTime.UtcNow;
 
                int dumbSum = 0;
                for (int temp = 0; temp < 5; temp++)
                    dumbSum++;
 
            DateTime endTime = DateTime.UtcNow;
 
            TimeSpan duration = endTime - startTime;
        }
 
        long end = 0;
        QueryPerformanceCounter(ref end);
            
        long time = 0;
        time = end - start;
 
        Console.WriteLine("Sample Size: {0} - Time: {1}", sampleSize, time);
    }
}

И, наконец, Stopwatch можно применить для измерения времени выполнения этого кода, с помощью его методов Start, Stop и свойства Elapsed (листинг 7).

Листинг 7: Код для тестирования Stopwatch

private static void TestStopwatch()
{
    Console.WriteLine("Testing Stopwatch ...");
 
    for (int sampleSize = 500; sampleSize <= 5000; sampleSize += 500)
    {
        long start = 0;
        QueryPerformanceCounter(ref start);
 
        for (int counter = 0; counter < sampleSize; counter++)
        {
            Stopwatch watch = new Stopwatch();
 
            watch.Start();
 
            int dumbSum = 0;
            for (int temp = 0; temp < 5; temp++)
                dumbSum++;
 
            watch.Stop();
 
            TimeSpan duration = watch.Elapsed;
        }
 
        long end = 0;
        QueryPerformanceCounter(ref end);
 
        long time = 0;
        time = end - start;
 
        Console.WriteLine("Sample Size: {0} - Time: {1}", sampleSize, time);
    }
}

Иногда картинка стоит тысячи слов, и таблица 1 отражает все то, о чем я говорил.

Рисунок 1: Количественное сравнение между DateTime.Now, DateTime.UtcNow и Stopwatch

Результаты, отображенные в таблице, довольно интересные, и они становятся еще интереснее, если учесть тот факт, что эти фрагменты кода тестируются на реалистичных размерах выборок данных, которые варьируются от 500 до 5000. DateTime.Now (синяя линия) здесь лидирует в топе худшей производительности, за ним следует Stopwatch (зеленая линия) и DateTime.UtcNow (красная линия). Результаты UtcNow намного ниже других подходов, и растут очень медленно (в отличие от двух других подходов) и показали себя лучше, чем Stopwatch. Это, конечно, было ожидаемо, поскольку я показал вам внутреннюю реализацию Stopwatch, которая использует DateTime.UtcNow с некоторой дополнительной обработкой.

Логичным вопросом будет: зачем нам использовать Stopwatch, если DateTime.UtcNow работает лучше. Ответ заключается в том, что возможно так и следует делать, если вы уверены, что получите такую ​​значительную разницу, применив UtcNow, а не Stopwatch. Но все же есть два преимущества Stopwatch перед UtcNow: значительным и незначительным. Значительным преимуществом является то, что Stopwatch может работать с более высоким разрешением, применяя QueryPerformanceCounter, а DateTime.UtcNow — нет. Незначительным преимуществом является то, что Stopwatch обеспечивает более быстрый и понятный для пользователя метод выполнения этой задачи.

Заключение

Многие разработчики .NET неправильно используют свойства структуры DateTime применяя свойство Now для целей, для которых оно не предназначено, что снижает производительность кода. UtcNow — это то, что следует использовать во многих случаях, вместо свойства Now. Для замеров времени существует специальный класс Stopwatch.

Рассмотрев на наглядной диаграмме количественное сравнение этих подходов, вы можете сделать выводы, как рекомендуется использовать каждое свойство или класс в каждом отдельном случае. Я уверен, что неправильное использование этих параметров в .NET коде, написанном за последнее десятилетие, оказало серьезное влияние на многие .NET приложения, и я надеюсь, что повышение осведомленности об этой и аналогичных темах предотвратит появление таких ошибок.


Материал подготовлен в рамках специализации "C# Developer".

Комментарии (33)


  1. Sing
    20.10.2021 17:54
    -2

    То, что для хранения в БД нужно использовать DateTime.UtcNow вместо DateTime.Now — это как минимум сильно спорно. Микрооптимизации не должны влиять на логику работы, ведь это буквально разное время (на какой-то момент показалось, что автор оригинала живёт в часовом поясе UTC+0 и ему всё равно). Да и является ли это вообще оптимизацией, если потом для отображения надо будет каждый раз время переводить в локальное?

    И уж как минимум в данном контексте стоило упомянуть о DateTimeOffset.


    1. Smerig
      20.10.2021 18:16
      +12

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

      Так себе пример, но логику отразить может.


      1. Sing
        20.10.2021 18:25
        +3

        Именно для этого и используется DateTimeOffset. Вы предлагаете сделать соглашение, что всё время — в utc+0 и это будет работать, но ничто никому не помешает нарушить это соглашение и «по старинке» использовать просто Now, и тогда начнётся погружение в удивительный мир отладки распределённых приложений, особенно интересно выглядящий с точки зрения времени/часовых поясов.


        1. pankraty
          20.10.2021 21:38
          +4

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


          1. Sing
            20.10.2021 21:59

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

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

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


            1. pankraty
              20.10.2021 22:07
              +5

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

              Аналог: если система поддерживает различные единицы измерения, вы вряд ли будете хранить в одном столбце "19 м", " 140 дюймов", "4 фута". Скорее всего, всё будет преобразовано к одной единице измерения, а потом оттбражатся в виде, удобном пользователю. Исключение - когда это приводит к недопустимой потере точности; в таком случае хранение в разных единицах оправдано.


              1. propell-ant
                20.10.2021 22:51
                +1

                Исходных данных для этого обсуждения обычно гораздо больше.
                Перед продолжением холивара посмотрите, например, вот эти статьи:
                https://habr.com/ru/post/278527/
                https://habr.com/ru/company/vk/blog/242645/


              1. Sing
                20.10.2021 22:57

                Мне грустно повторяться, но выше я уже написал, что такая нормализация будет хорошо работать. Ровно до момента, пока кто-то, кто не в курсе (а, согласитесь, это рано или поздно наступит) не начнёт записывать просто DateTime.Now. И тогда у нас какой-нибудь список событий превратится в кашу, когда создание товара было после его отправки. Если вас не слопают пользователи на этом или вы готовы к такому повороту, учитывая, что отлаживать это не так-то просто, а взять информацию о том, какое значение корректное неоткуда — хорошо.

                Давайте тогда поймём, зачем нам такая красота нужна. Вы пишете, что мы вдвое экономим место. Тут нужно уточнение, где именно. Например, в postgresql типы timestamp with timezone и timestamp without timezone занимают одинаково — 8 байт. Если речь про память, то DateTimeOffset занимает 12 байт для 32-битных систем и 16 байт для 64-битных, когда DateTime — 8 байт везде. Если такая экономия оправдана, то я полностью согласен — это оптимизация.

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


                1. segment
                  20.10.2021 23:54
                  +2

                  Я может быть не совсем понял аргумента, но ведь таким же образом кто-то может начать записывать и DateTime.UtcNow, случайно перепутав. Почему ошибка может быть совершена в сторону Now, но не наоборот?


                  1. Sing
                    21.10.2021 00:12

                    Разумеется, и в эту сторону тоже может быть ошибка. Поэтому и вариант решения — не использовать соглашения, а использовать явные часовые пояса.


                    1. rroyter
                      21.10.2021 04:46
                      +1

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

                      Поэтому самый корректный способ это хранить дату в UTC.


                      1. Sing
                        21.10.2021 05:31
                        +1

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

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

                        Если у вас всё в UTC+0, то уже час ночи по Москве станет у вас предыдущей датой, и круги ада начнутся сильно раньше. То, что часовые пояса — это сложно всё-таки не повод от них отказываться совсем.


                      1. rroyter
                        21.10.2021 08:06
                        +1

                        Вы наверно не в курсе, но проблема с часовыми поясами настолько серьезна, что во фреймворке даже есть метод с говорящим именем TimeZoneInfo.IsAmbiguousTime. Это как бы намекает что местное время (с часовым поясом) не всегда может однозначно определять момент во времени.

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


                      1. Sing
                        21.10.2021 11:23
                        +1

                        Я с вами разговариваю аргументами — а вы пытаетесь в ответ «задавить авторитетом». Я вам говорю — да, я знаю, часовые пояса — это сложно, но не повод же отказываться от них. Вы мне в ответ — вон есть свойство, это точно повод отказаться от часовых поясов!

                        Это как бы намекает что местное время (с часовым поясом) не всегда может однозначно определять момент во времени.
                        В каких ситуациях в вашей практике такое случалось? Как много записей из миллиона будут такими?
                        Серьезно, следуйте рекомендуемым практикам и не стреляйте себе в ногу.
                        Договорились, буду следовать рекомендациям:
                        DateTimeOffset

                        Use this structure to work with dates and times whose offset (or difference) from UTC is known. The DateTimeOffset structure combines a date and time value with that time's offset from UTC. Because of its relationship to UTC, an individual date and time value unambiguously identifies a single point in time. This makes a DateTimeOffset value more portable from one computer to another than a DateTime value.


                      1. rroyter
                        21.10.2021 19:04

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

                        Я с вами разговариваю аргументами — а вы пытаетесь в ответ «задавить авторитетом».

                        Хаха, ну ладно делайте как считайте нужным. Не мне вам код ревью делать. Удачи.


                1. gwg605
                  21.10.2021 02:01
                  +3

                  Мне кажется вы ставите все с ног на голову. Если кто-то "забыл", то это не проблема архитектуры и подхода. Так можно "забыть" вообще дату писать, да и много еще чего ;-) обычно это называют багом.

                  А для распределенных систем использование UTC для взаимодействия нормальная практика. Хороший пример календарь. Зачем хранить время с привязкой к таймзоне? Сегодня я в Питере, а завтра уже во Фриско. И живу я по локальным часам. Зачем мне знание таймзоны в которой я вводил данные, мне нужны данные в локальной таймзоне.


                  1. Sing
                    21.10.2021 03:21
                    -3

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

                    И то, что большую часть времени всё будет работать хорошо — сделает вам трудности в отладке. По данным у вас всё будет прекрасно. Или нет? Вы будете видеть, что товар пришёл ночью, хотя по факту он пришёл днём. Это кто-то где-то отправил Now? Может, кто-то залез в БД руками? Или всё правильно, но часовой пояс в том месте, где произошло событие такой, что в UTC+0 получилась другая дата? Во время отладки надо ещё не забыть про этот момент, который «не проблема подхода».

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

                    Мысль с календарным днём натолкнула меня на свою неправоту раньше: нет, UtcNow не будет работать, даже в случаях, когда все следуют одному правилу. Если нужны часовые пояса — используйте DateTimeOffset.


                1. DistortNeo
                  21.10.2021 11:27

                  Прелесть DateTime заключается в том, что он ещё и хранит флажок, показывающий, является ли время локальным или в UTC. И грамотно написанный API проверит этот флажок и сделает необходимые преобразования.


                  1. Sing
                    21.10.2021 12:04

                    Вы о каких преобразованиях? Давайте на примере, в БД хранится значение, скажем, DateTime = 10/21/2021 12:04:52 AM, Kind — Local.
                    Расскажете, как грамотно написать API, чтобы получить UTC+0?


                    1. DistortNeo
                      21.10.2021 12:11

                      Давайте на примере, в БД хранится значение, скажем

                      Неверно. В БД хранятся даты только в UTC.


                      Вы о каких преобразованиях?

                      Local -> UTC при работе с API базы. API принимает дату в любом формате, но делает преобразование при необходимости. Или вы предлагаете в базу писать напрямую, минуя API?


                      1. Sing
                        21.10.2021 12:21

                        Local -> UTC при работе с API базы. API принимает дату в любом формате, но делает преобразование при необходимости. Или вы предлагаете в базу писать напрямую, минуя API?

                        1. Как он узнает, что есть необходимость? Пришла вам DateTime в виде строки с фронта. Тот же «10/21/2021 12:04:52 AM». Там нет никакого флажка, показывающего тип даты. Что делаем?

                        2. API будет один-единственный на все даты системы?


                      1. Kanut
                        21.10.2021 12:26

                        del: не туда ответил


                      1. DistortNeo
                        21.10.2021 13:12

                        Мы же о типе DateTime из C# сейчас говорим, не? Если вам дата пришла в виде строки, то очевидно, что она имеет тип "строка", а не DateTime.


                        Ну а если ваше приложение, написанное на C#, сериализовало дату в подобном виде, то ССЗБ. Нормальный API должен принимать дату исключительно в формате ISO и слать нахрен тех, кто шлёт дату чёрти как.


                      1. Sing
                        21.10.2021 13:18

                        Мы же о типе DateTime из C# сейчас говорим, не? Если вам дата пришла в виде строки, то очевидно, что она имеет тип «строка», а не DateTime.
                        А вам на API данные каким образом приходят? Не строками, интерпретируемыми платформой разве из json? Похоже, мы о чём-то разном говорим.
                        Ну а если ваше приложение, написанное на C#, сериализовало дату в подобном виде, то ССЗБ. Нормальный API должен принимать дату исключительно в формате ISO и слать нахрен тех, кто шлёт дату чёрти как.
                        Хорошо, переформулирую по ISO 8601: 2021-10-21T12:04:52. Какое это время в utc+0?


                      1. DistortNeo
                        21.10.2021 14:21

                        А вам на API данные каким образом приходят? Не строками, интерпретируемыми платформой разве из json? Похоже, мы о чём-то разном говорим.

                        Нет, не строками. Я, например, MessagePack использую с сериализацией DateTime.ToBinary().


                        Хорошо, переформулирую по ISO 8601: 2021-10-21T12:04:52. Какое это время в utc+0?

                        Запрещаем передачу времени-даты без явного указания временной зоны (или Z).


                      1. Sing
                        21.10.2021 14:50

                        Запрещаем передачу времени-даты без явного указания временной зоны (или Z).
                        Вот мы и пришли к временным зонам. Также, конечно, вы далеко не всегда диктуете форматы.

                        Но мне интереснее то, как вы работаете с ситуациями, когда изменение на UTC+0 меняет хранимую дату?


                      1. DistortNeo
                        21.10.2021 15:40

                        Ну конкретном в моём случае допустима только передача времени в UTC-формате. При строковой сериализации обязателен суффикс "Z", указывающий на UTC.


                      1. Kanut
                        21.10.2021 12:27

                        Local -> UTC при работе с API базы. API принимает дату в любом формате, но делает преобразование при необходимости.

                        То есть API для работы с базой данных должно понимать любые форматы? И уметь их как-то распознавать и преобразовывать в UTC?


                        На мой взгляд всё-таки логичнее оставить в API только UTC(или какой-то другой формат, но один единственный). И пусть все кто пользуются этим API сами конвертируют свои форматы в нужный и обратно.


          1. nikolayv81
            21.10.2021 10:30

            Данные это ведь не только логи, к примеру в большой компании есть установленный рабочий день от 9 до 18 по местному времени, и нам надо собрать переработки.

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

            p.s. вот с тем что использовать now для замера производительности нет особого смысла, полностью согласен....


      1. ertaquo
        21.10.2021 01:05

        Почему бы в данном случае не использовать на стороне базы данных тип даты-времени с сохранением часового пояса (`timestamp with time zone` / timestamptz в Postgres)?

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

        Правда, не уверен, сохраняет ли тип DateTime в C# часовой пояс.


        1. Evengard
          21.10.2021 01:29

          DateTime не имеет вообще информации о часовом поясе, только суррогат в виде Kind-а. Для timestamptz обычно используют DateTimeOffset либо вообще (что, кстати правильней) NodaTime.


  1. TimeCoder
    21.10.2021 10:29

    Читать тяжеловато (но автор перевода тут, конечно, не виноват), идёт 4 абзаца где разными словами говорится «разработчики не знают, как правильно», и один абзац по делу.

    Удивлён, что кто-то не знает о том, что время в БД должно быть в UTC. Причин полно. Распределенные системы в разных поясах, перевод часов, наличие пользователей в разных часовых поясах. Конечно, речь в первую очередь про системные поля, типа createdAt, updatedAt и пр., которые участвуют в каких-то синках данных, на которые мы смотрим в логах при поиске причин ошибок. Наверное, для десктопной программы utc уже не так критичен, как и в случае поля времени, которое вообще ни на что не влияет - дело выбора.

    Кроме того, в проекте должен быть ровно один вызов DateTime.UtcNow. В неком сервисе. А остальной код берет время у него. Если везде вызывать DateTime напрямую, такой код сложнее покрыть тестами, если время в них важно (проверяем, например, что через 5 минут после события система ведёт себя так-то, и конечно не тест 5 минут висит, а просто сдвигаем его в замоканном/зафейконом сервисе времени). Ну и в целом недетерменипованность для тестов - плохо, использование реального времени системы одна из причин недетерменированности.


  1. Tzimie
    21.10.2021 12:41

    А вот темная сторона KeQuerySystemTimePrecise

    https://habr.com/ru/post/496612/