Открыть список
Как стать автором
Обновить

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

Вариант со статическим анализатором мне кажется правильнее, т.к. гибче и заставит разработчиков быть внимательнее. А с новым типом вы правильно написали — неизвестно где это всплывет и есть вероятность накопления костылей.
Мне кажется научить рослин ругаться на неявное преобразование DateTime в DateTimeOffset не так уж и сложно, ибо вполне штатная ситуация.
не совсем понял проблему. тип DateTime имеет информацию о TimeZone и корректно будет преобразован в тип DateTimeOffset что не так?

Нет, тип DateTime не имеет информации о TimeZone. Он может быть трех вариантов: локальный (таймзоны сервера), UTC, unspecified. В нашей задаче встречаются времена со всех таймзон.
Самый простой пример — пусть вы пишете календарь для многонациональной корпорации. В офисе в Хабаровске есть ежедневный стендап команды уборщиков в 8 утра. Это время не таймзоны сервера (сервер в Москве) и не UTC. Его никак не преобразовать в DateTimeOffset без дополнительной инфы о том, что это именно Хабаровск (то есть, собственно Offset). В DateTime этой инфы нет.

без дополнительной инфы о том, что это именно Хабаровск (то есть, собственно Offset)

В этом предложении вы смешали TimeZone (Хабаровск) и TimeZoneOffset (ваше: «собственно Offset», которое выражается как +5:00 например), а это разные вещи.
Time Zone != Offset

Да, с терминами проблемы, не спорю. Вы правы.

DateTime имеет только тип времени utc, местное или неопределённое.
DateTime.Now возвращает местное время по установкам системы.

Боюсь, что закидают тапками, но что мешает в базе хранить и на бэкенде обрабатывать все в UTC, а клиенту отображать (а также запрашивать от него) в его локальной зоне?
IMHO, попахивает изобретением проблемы и попытками её решить…

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

Боюсь, что закидают тапками, но что мешает узнать это? Если офис в Хабаровске, то мы же знаем каким-то образом что он в Хабаровске… Может быть у пользователя или у аккаунта офисса выставленна таймзона?
На крайний склучай давайте указывать время по МСК…

IMHO, Вместо того, что бы узнать таймзону с клиента, автор изобретает велосипед на бэке. И да, попахивает изобретением проблемы и попытками её решить.

Все правильно, ее нужно узнать и после этого хранить. Правильный способ это делать — хранить в базе DateTimeOffset

Ну дак и DateTimeOffset тут не поможет, потому что как уже писали выше, TimeZone и Offset разные вещи.
UTC+3 это может быть и MSK, а может быть и нет, а может MSK это UTC+4, до 2011 года, вдруг MSK станет UTC+5 в 2019?
Все что даст узнать DateTimeOffset, это оффсет в котором произошло событие, но никак и время в какой-либо таймзоне.


Пример выше про календарь опять же не решается с помощью DateTimeOffset, потому как запланированная встреча на 2020 год в 8 утра по MSK может поломаться, т.к. правительство решит опять переходить на летнее/зимнее время, и часовой пояс для MSK изменится, а на момент создания вы полагаетесь просто на +3. Посмотрите диалог создания событий в гугл календаре, там выбирается не оффсет, а именно таймзона.


Таким образом, чтобы узнать локальное время в таймзоне, надо хранить, таймзону, как ни странно!

И то верно, спасибо!

Надо в базе и бекенде хранить DateTimeOffset. В новых местах мы так и делаем. Почему недостаточно UTC — см выше про хабаровск и уборщиков.

Учитывая, что вы работаете с событиями в разных таймзонах, почему не выбрали готовое решене — например NodaTime?

NodaTime интересное решение, но пока нас почти всем устраивал DateTimeOffset. В целом выглядит что NodaTime лучше, но тяжеловеснее. Лишняя зависимость со своими особенностями. Почитаю подробности, подумаю, есть ли смысл в миграции.

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

И вообще я тут проблемы не вижу. Смотрим исходный код:
Преобразование
public static implicit operator DateTimeOffset (DateTime dateTime) {
    return new DateTimeOffset(dateTime);
}


DateTimeOffset.Now
public static DateTimeOffset Now {
   get {
      return new DateTimeOffset(DateTime.Now);
   }
}     



То есть вызов DateTimeOffset.Now аналогичен DateTimeOffset(DateTime.Now) и DateTimeOffset Event = DateTime.Now.
так учитывает локальную зону компьютера, где идёт преобразование.

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

Вы же сами в Попытке 4 пишете, что DateTimeOffsetStrict EventTime = DateTimeOffset.Now вам подходит, а это абсолютно то же самое, что и DateTimeOffset EventTime = DateTime.Now. Да и сломать это всё равно можно: DateTimeOffsetStrict EventTime = new DateTimeOffset(new DateTime()).

Я вам и не говорю, что нужно использовать DateTime. Использование DateTimeOffset полностью оправдано и вы храните в базах именно этот тип, но как возможность локального неявного преобразования может подпортить вам данные? При добавлении в базу новых данных всё равно будет учитываться временная зона локального компьютера, откуда добавляется.

Я понял, я запутал вас своими DateTimeOffset.Now.
Там имелась ввиду просто возможность присвоить туда DateTime/DateTimeOffset. На самом деле он поднимается из базы и высчитывается

Как человек с опытом работы с временем и зонами, могу высказать имхо. Храните всё в utc. Всегда. Никогда не храните локальное время ни в какой базе. Utc отражает момент времени, единый для всей Земли (минус релятивизм).
Если нужно отобразить время пользователю, используйте перевод в локальное время на клиенте.
В исключительно редком случае, когда нужно хранить ощущаемое пользователем время (показания на часах на стене пользователя; в стиле календаря в конкретной таймзоне, что само по себе немного странно), храните локальное время и таймзону, в совершенно отдельном типе. Эта конструкция не задаёт момент времени, потому что её смысл может меняться вместе с оффсетом таймзоны.


P.S. Опыт с двух сторон: разработка мобильного приложения для продажи авиабилетов — народ пару раз приезжал к вылету на час раньше/позже из-за забавных особенностей таймзон и Российского законодательства; и работы в международной компании, где я скорее назову текущее время utc, чем локальное, в ответ на вопрос "который час?".

Да, работа с таймзонами не самое тривиальное занятие, если меня про это спрашивают, я всегда даю эту ссылку: stackoverflow.com/questions/2532729/daylight-saving-time-and-time-zone-best-practices.
Там как раз есть объяснение почему «Don't tell people to „always use UTC everywhere“.
Просто у вас первый абзац комментария слишком категоричен.
По той же ссылке: «Whenever you are referring to an exact moment in time, persist the time according to a unified standard that is not affected by daylight savings». Так сказать «use» и «persist» — вещи таки разные, согласитесь.
Лично я на 100% согласен с gurux13 — в базе только в UTC.

Выше тоже писали про локальное время компьютера (так понимаю, сервера), где происходит преобразование. Хорошо, если у вас один сервер, стоящий в соседней кладовке, а если система распределённая? Вы не можете предсказать, где и когда будет обработан данный запрос пользователя: может в Штатах, может в Европе, может в Японии…

Другой пример: в одной конторе я был в команде, которая занималась представлением демо и PoC потенциальным клиентам по всему миру. Приходилось летать с системой, установленной на ноут, чтобы всё показывать (а порой и допиливать или даже реализовывать совершенно новый функционал, который только что стукнул в голову потенциальному клиенту). Ну прилетел я, положим, в Сингапур — локальное время «перевелось»… а потом возвращаюсь домой и нужно проанализировать логи или «проиграть» то, что было показано ранее, на месте. А время-то опять «переехало». И начинается ад.

Так что не — в базе хранить всё только в UTC и на бэкенде оперировать тоже только им.

DateTimeOffset — это не локальное время. Это абсолютное плюс оффсет (указывающий локальное время источника).

DateTimeOffset… прекрасно поддерживается MS SQL и Entity Framework

Как решена проблема хранения DateTimeOffsetStrict в БД?

Тут есть дальше варианты. Если сделать его class/[ComplexType], то EF будет его сохранять.


Ещё вариант, иметь его на уровне Domain/Dto, а в базу сохранять DateTimeOffset

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