Перевод статьи, опубликованной в 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)
TimeCoder
21.10.2021 10:29Читать тяжеловато (но автор перевода тут, конечно, не виноват), идёт 4 абзаца где разными словами говорится «разработчики не знают, как правильно», и один абзац по делу.
Удивлён, что кто-то не знает о том, что время в БД должно быть в UTC. Причин полно. Распределенные системы в разных поясах, перевод часов, наличие пользователей в разных часовых поясах. Конечно, речь в первую очередь про системные поля, типа createdAt, updatedAt и пр., которые участвуют в каких-то синках данных, на которые мы смотрим в логах при поиске причин ошибок. Наверное, для десктопной программы utc уже не так критичен, как и в случае поля времени, которое вообще ни на что не влияет - дело выбора.
Кроме того, в проекте должен быть ровно один вызов DateTime.UtcNow. В неком сервисе. А остальной код берет время у него. Если везде вызывать DateTime напрямую, такой код сложнее покрыть тестами, если время в них важно (проверяем, например, что через 5 минут после события система ведёт себя так-то, и конечно не тест 5 минут висит, а просто сдвигаем его в замоканном/зафейконом сервисе времени). Ну и в целом недетерменипованность для тестов - плохо, использование реального времени системы одна из причин недетерменированности.
Sing
То, что для хранения в БД нужно использовать DateTime.UtcNow вместо DateTime.Now — это как минимум сильно спорно. Микрооптимизации не должны влиять на логику работы, ведь это буквально разное время (на какой-то момент показалось, что автор оригинала живёт в часовом поясе UTC+0 и ему всё равно). Да и является ли это вообще оптимизацией, если потом для отображения надо будет каждый раз время переводить в локальное?
И уж как минимум в данном контексте стоило упомянуть о DateTimeOffset.
Smerig
Допустим у вас распределенное приложение, использующее одну БД. Один сервер хостится в Ирландии, второй в Орегоне. Оба пишут в БД локальное время. Теперь мы хотим пользователю показать на странице время, в которое произошло определенное событие, к примеру, произошла покупка на сайте.
Так себе пример, но логику отразить может.
Sing
Именно для этого и используется DateTimeOffset. Вы предлагаете сделать соглашение, что всё время — в utc+0 и это будет работать, но ничто никому не помешает нарушить это соглашение и «по старинке» использовать просто
Now
, и тогда начнётся погружение в удивительный мир отладки распределённых приложений, особенно интересно выглядящий с точки зрения времени/часовых поясов.pankraty
Когда данные собраны в базу из разных источников, в подавляющем большинстве случаев неважно знать, какой часовой пояс использовал пользователь/система в момент отправки данных. В тех случаях, когда это важно, DateTimeOffset вполне оправдан. В других - скорее избыточен, т.к. занимает вдвое больше места.
Sing
Так или иначе, это очевидно, что хранить ненужные данные — избыточно. Но в этой ветке речь идёт о том, когда часовой пояс важен.
Так что вы хотите сказать, в контексте? Что нужно использовать DateTime.UtcNow и терять то время, что было у пользователя, потому что это неважно знать? Или что нужно конвертировать каждый раз в локальное время? Или что в подавляющем случае время неважно и вообще надо на него забить?
pankraty
Скажем так, я не настаиваю, что это единственно правильное решение, но считаю рациональным "нормализовать" входные данные, приводя их к нулевому часовому поясу (да, UtcNow), а при отображении - приводить к часовому поясу пользователя.
Аналог: если система поддерживает различные единицы измерения, вы вряд ли будете хранить в одном столбце "19 м", " 140 дюймов", "4 фута". Скорее всего, всё будет преобразовано к одной единице измерения, а потом оттбражатся в виде, удобном пользователю. Исключение - когда это приводит к недопустимой потере точности; в таком случае хранение в разных единицах оправдано.
propell-ant
Исходных данных для этого обсуждения обычно гораздо больше.
Перед продолжением холивара посмотрите, например, вот эти статьи:
https://habr.com/ru/post/278527/
https://habr.com/ru/company/vk/blog/242645/
Sing
Мне грустно повторяться, но выше я уже написал, что такая нормализация будет хорошо работать. Ровно до момента, пока кто-то, кто не в курсе (а, согласитесь, это рано или поздно наступит) не начнёт записывать просто DateTime.Now. И тогда у нас какой-нибудь список событий превратится в кашу, когда создание товара было после его отправки. Если вас не слопают пользователи на этом или вы готовы к такому повороту, учитывая, что отлаживать это не так-то просто, а взять информацию о том, какое значение корректное неоткуда — хорошо.
Давайте тогда поймём, зачем нам такая красота нужна. Вы пишете, что мы вдвое экономим место. Тут нужно уточнение, где именно. Например, в postgresql типы timestamp with timezone и timestamp without timezone занимают одинаково — 8 байт. Если речь про память, то DateTimeOffset занимает 12 байт для 32-битных систем и 16 байт для 64-битных, когда DateTime — 8 байт везде. Если такая экономия оправдана, то я полностью согласен — это оптимизация.
Стоит ли она озвученных мной рисков — инженерное решение, но я бы рекомендовал использовать DateTimeOffset по умолчанию. Всё же в реальных промышленных приложениях лучше пожертвовать несколькими байтами памяти, чем долго и мучительно страдать, пытаясь откопать истоки проблем с этими проклятыми датами.
segment
Я может быть не совсем понял аргумента, но ведь таким же образом кто-то может начать записывать и DateTime.UtcNow, случайно перепутав. Почему ошибка может быть совершена в сторону Now, но не наоборот?
Sing
Разумеется, и в эту сторону тоже может быть ошибка. Поэтому и вариант решения — не использовать соглашения, а использовать явные часовые пояса.
rroyter
Проблема с часовыми поясами в том что им нельзя доверять. Часовые пояса, неожиданно, могут меняться и вы уже никогда не разберете эту чехарду. Это особенно нежелательно для исторических данных где вам нужно будет соотнести даты со "старым" часовым поясом и "новым".
Поэтому самый корректный способ это хранить дату в UTC.
Sing
Часовым поясам вполне можно верить, просто нужно понимать, что смещение относительно UTC — это не вся необходимая информация, есть ещё локальное регулирование, тут уже нужно знать именование зоны, что не поддерживает DateTimeOffset, но поддерживает NodaTime.
Да, часовые пояса — это сложно, и если у вас будет подобная задача про соотношение исторических данных на обширных территориях, то вы действительно пройдёте все круги ада и стадии принятия, но у вас есть шанс успешно выполнить задачу.
Если у вас всё в UTC+0, то уже час ночи по Москве станет у вас предыдущей датой, и круги ада начнутся сильно раньше. То, что часовые пояса — это сложно всё-таки не повод от них отказываться совсем.
rroyter
Вы наверно не в курсе, но проблема с часовыми поясами настолько серьезна, что во фреймворке даже есть метод с говорящим именем TimeZoneInfo.IsAmbiguousTime. Это как бы намекает что местное время (с часовым поясом) не всегда может однозначно определять момент во времени.
Серьезно, следуйте рекомендуемым практикам и не стреляйте себе в ногу.
Sing
Я с вами разговариваю аргументами — а вы пытаетесь в ответ «задавить авторитетом». Я вам говорю — да, я знаю, часовые пояса — это сложно, но не повод же отказываться от них. Вы мне в ответ — вон есть свойство, это точно повод отказаться от часовых поясов!
В каких ситуациях в вашей практике такое случалось? Как много записей из миллиона будут такими?Договорились, буду следовать рекомендациям:
rroyter
Хаха, ну ладно делайте как считайте нужным. Не мне вам код ревью делать. Удачи.
gwg605
Мне кажется вы ставите все с ног на голову. Если кто-то "забыл", то это не проблема архитектуры и подхода. Так можно "забыть" вообще дату писать, да и много еще чего ;-) обычно это называют багом.
А для распределенных систем использование UTC для взаимодействия нормальная практика. Хороший пример календарь. Зачем хранить время с привязкой к таймзоне? Сегодня я в Питере, а завтра уже во Фриско. И живу я по локальным часам. Зачем мне знание таймзоны в которой я вводил данные, мне нужны данные в локальной таймзоне.
Sing
Если вы персонально живёте по локальным часам, то распределённая система — нет. От часового пояса будет зависеть, например, в какой день произошло событие в том конкретном месте, где оно произошло. Если у вас есть завязка на дни (типа, доставка в течении 3 дней) — то система, например, может считать доставку просроченной, хотя есть ещё день.
И то, что большую часть времени всё будет работать хорошо — сделает вам трудности в отладке. По данным у вас всё будет прекрасно. Или нет? Вы будете видеть, что товар пришёл ночью, хотя по факту он пришёл днём. Это кто-то где-то отправил Now? Может, кто-то залез в БД руками? Или всё правильно, но часовой пояс в том месте, где произошло событие такой, что в UTC+0 получилась другая дата? Во время отладки надо ещё не забыть про этот момент, который «не проблема подхода».
И тут — эврика, вы понимаете в чём проблема! Только решить вы её уже не сможете, у вас весь массив данных не содержит информации о часовых поясах.
Мысль с календарным днём натолкнула меня на свою неправоту раньше: нет, UtcNow не будет работать, даже в случаях, когда все следуют одному правилу. Если нужны часовые пояса — используйте DateTimeOffset.
DistortNeo
Прелесть
DateTime
заключается в том, что он ещё и хранит флажок, показывающий, является ли время локальным или в UTC. И грамотно написанный API проверит этот флажок и сделает необходимые преобразования.Sing
Вы о каких преобразованиях? Давайте на примере, в БД хранится значение, скажем, DateTime = 10/21/2021 12:04:52 AM, Kind — Local.
Расскажете, как грамотно написать API, чтобы получить UTC+0?
DistortNeo
Неверно. В БД хранятся даты только в UTC.
Local -> UTC при работе с API базы. API принимает дату в любом формате, но делает преобразование при необходимости. Или вы предлагаете в базу писать напрямую, минуя API?
Sing
1. Как он узнает, что есть необходимость? Пришла вам DateTime в виде строки с фронта. Тот же «10/21/2021 12:04:52 AM». Там нет никакого флажка, показывающего тип даты. Что делаем?
2. API будет один-единственный на все даты системы?
Kanut
del: не туда ответил
DistortNeo
Мы же о типе DateTime из C# сейчас говорим, не? Если вам дата пришла в виде строки, то очевидно, что она имеет тип "строка", а не DateTime.
Ну а если ваше приложение, написанное на C#, сериализовало дату в подобном виде, то ССЗБ. Нормальный API должен принимать дату исключительно в формате ISO и слать нахрен тех, кто шлёт дату чёрти как.
Sing
Хорошо, переформулирую по ISO 8601:
2021-10-21T12:04:52
. Какое это время в utc+0?DistortNeo
Нет, не строками. Я, например, MessagePack использую с сериализацией DateTime.ToBinary().
Запрещаем передачу времени-даты без явного указания временной зоны (или Z).
Sing
Но мне интереснее то, как вы работаете с ситуациями, когда изменение на UTC+0 меняет хранимую дату?
DistortNeo
Ну конкретном в моём случае допустима только передача времени в UTC-формате. При строковой сериализации обязателен суффикс "Z", указывающий на UTC.
Kanut
То есть API для работы с базой данных должно понимать любые форматы? И уметь их как-то распознавать и преобразовывать в UTC?
На мой взгляд всё-таки логичнее оставить в API только UTC(или какой-то другой формат, но один единственный). И пусть все кто пользуются этим API сами конвертируют свои форматы в нужный и обратно.
nikolayv81
Данные это ведь не только логи, к примеру в большой компании есть установленный рабочий день от 9 до 18 по местному времени, и нам надо собрать переработки.
В принципе, если речь идёт о работе с БД, то в зависимости от используемуй RDBMS может оказаться что лучше вообще не использовать генерацию текущего времени в приложении, т.к. условно, нет гарантии что разные экземпляры приложения (неважно серверы на сервере или у экземпляры клиента) работают на машинах с синхронизированные и правильно настроенным временем (хотя в последние годы обратная ситуация встречается крайне редко).
p.s. вот с тем что использовать now для замера производительности нет особого смысла, полностью согласен....
ertaquo
Почему бы в данном случае не использовать на стороне базы данных тип даты-времени с сохранением часового пояса (`timestamp with time zone` /
timestamptz
в Postgres)?Получится красиво: и сохранение локального времени независимо от локации, и возможность отображать его в произвольном часовом поясе, и правильная сортировка по данному полю.
Правда, не уверен, сохраняет ли тип
DateTime
в C# часовой пояс.Evengard
DateTime не имеет вообще информации о часовом поясе, только суррогат в виде Kind-а. Для timestamptz обычно используют DateTimeOffset либо вообще (что, кстати правильней) NodaTime.