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