Комментарии 42
Веб-приложение веб-приложению рознь. У нас тоже была подобная проблема. Один из супругов в командировке в Лондоне, супруга дома в Киеве. Оба одновременно делают запись о расходах в свой общий бюджетный аккаунт. Помимо вопроса «как хранить» есть еще вопрос «как отображать». В нашем случае веб-приложение — полностью на JS а данные приходят в браузер сыром виде. Поэтому хранятся и передаются данные в UTC, а уже в браузере преобразовываются во временную зону ОС. И наоборот: перед отправкой даты временные метки переводятся из локальных в UTC/
Т.е. если супруги запишут одновременно операции (в 1:00 по Киеву и в 23:00 по Лондону) увидят эти операции одновременными, но у супруга они обе будут датированы допустим 17-тым июля, а у супруги — 18-тым.
Т.е. если супруги запишут одновременно операции (в 1:00 по Киеву и в 23:00 по Лондону) увидят эти операции одновременными, но у супруга они обе будут датированы допустим 17-тым июля, а у супруги — 18-тым.
Собственно по-моему самый логичный подход к проблеме — хранить данные даты и времени в UTC в любом формате, а ввод-вывод уже осуществлять через прямые и обратные преобразования. Что касается преобразований, то тут полагаться на какой-то конкретный инструментарий следует достаточно осторожно, так как данные о преобразованиях для временных зон периодически меняются (как в России в этом году например), а отражается это в изменениях функционала инструментов не всегда достаточно оперативно и адекватно. В частности PHP в этом плане сильно хромает.
А можно поподробней про вашу систему преобразования времён в браузере? Как-то это автоматизированно?
Если речь о чем-то вроде
date_default_timezone_set
— то нет. Но, скажем так, при хорошо спроектированной архитектуре это ведь не проблема. Если за обработку полученных с сервера данных, и за подготовку их к отправке отвечает некоторая прослойка — то совершенно не проблема заставить ее, в случае дат — делать прямое/обратное преобразование с учетом Date().getTimezoneOffset()
. А если асинхронность приложения сделана кусочно-гнездовым способом — то тут уж неча на зеркало пенять…Я писал, веб-приложение веб-приложению рознь. Если манипуляции (в том числе отображение) вынесены на клиентскую часть, то как там оно хранится — личное дело сервера. В том смысле, что данные можно (хотя нет — желательно) хранить в некотором унифицированном виде и никак не преобразовывать — только отдавать и записывать. Тогда и пример с супругами в Лондоне и Киеве будет работать логично.
А вот если клиент тонкий, то естественно, часть (или все) подобных манипуляций с датами происходит на сервере. Что вызывает естественное желание спихнуть эту задачу на встроенные возможности того же СУБД.
А вот если клиент тонкий, то естественно, часть (или все) подобных манипуляций с датами происходит на сервере. Что вызывает естественное желание спихнуть эту задачу на встроенные возможности того же СУБД.
Ничего не мешает продолжать хранить в datestamp:
SELECT DATE_ADD(date_field, INTERVAL diff HOUR) AS date_field, где diff — разница часовых поясов между пользовательским (в настройках пользователя, либо всего домена сайта) и серверным.
SELECT DATE_ADD(date_field, INTERVAL diff HOUR) AS date_field, где diff — разница часовых поясов между пользовательским (в настройках пользователя, либо всего домена сайта) и серверным.
Спасибо
С учетом того, что в России сейчас в плане часовых поясов вечное лето, будет ли по осени вызов
корректным?
Или же php эти настройки жестко не вшиты и есть некая системная функция преобразования, которая выдаст корректное значение?
'Europe/Moscow' => +4
date_default_timezone_set('Europe/Moscow')
корректным?
Или же php эти настройки жестко не вшиты и есть некая системная функция преобразования, которая выдаст корректное значение?
'Europe/Moscow' => +4
Насколько мне известно он и сейчас в разных версиях php является не всегда корректным. На php в этом плане надеяться не стоит.
Кхм, ну как бэ… что тут сказать: довольно долго наблюдал некорректные данные зоны для Новосибирска в свежих версиях php.
А почему на MySQL стоит надеяться? У него принцип тот же самый. И у операционки. Смысл тот же везде пока что — закачиваем базы переходом на летнее/зимнее время и расчитываем сдвиг, исходя из этого.
Конечно принцип везде одинаков, только не всё есть возможность обновлять (виртуальный хостинг бывает в этом плане совсем запущен), не всё одинаково хорошо обновляется. Потому часто применяются «грабли» с использованием комбинации временного смещения и флага использования летнего времени.
Так если не обновлять базу, то откуда брать информацию, что время летнее или зимнее в определенный момент времени?
В смысле? Определить к какому времени относится временная отметка — арифметическая задача, а необходимость добавлять смещение для летнего времени определяется флагом. Не разу не видели реализации? Профиль пользователя имеет настройки часового пояса, но не в виде выбора готовых ID поясов, а в виде указания смещения от UTC и флага указывающего на необходимость автоматического перехода на летнее время.
Не совсем понимаю о каком профиле пользователя идет.
В базе хранится дата/время по UTC: 2011-03-13 00:00:00. Сколько это будет для Лос-Анжелеса? Сколько для Киева? Сколько для Москвы?
В базе хранится дата/время по UTC: 2011-03-13 00:00:00. Сколько это будет для Лос-Анжелеса? Сколько для Киева? Сколько для Москвы?
Если для известно смещение по времени относительно UTC, известно используется ли в данных местах переход на летнее время (эти данные задаются вручную), то мы легко можем определить относится ли данное время к летнему и вычислить результат, ведь так?
Если нет базы часовых поясов или нет возможности ей воспользоваться или в ней некорректные данные, то как иначе решать задачу? Задавать нужные данные вручную.
Если нет базы часовых поясов или нет возможности ей воспользоваться или в ней некорректные данные, то как иначе решать задачу? Задавать нужные данные вручную.
Вызов date_default_timezone_set('Europe/Moscow') будет корректен всегда, пока вы вовремя обновляете пакет tzdata.
Побуду я сегодня КО. Существует специальная база данных, хранящая как часовые пояса, так и смещения/переходы на летнее/зимнее время (также называемая базой данных Олсона, по имени автора), причем все из них, т.к. чтобы узнать корректный диапазон между двумя датами, нужно также знать прошлые переходы на летнее/зимнее время. Версия этой базы данных входит в поставку PHP и своевременно обновляется, статус можно посмотреть, например, здесь: www.php.net/manual/en/timezones.php
Россия не единственная такая страна. Многие пояса какое-то время попереключались между летними/зимними временами, а потом перестали. Все это хранится в спец.базах.
Вы не постигли дзен. Смещение часового пояса, которое вы задает для mysql ничего не говорит об этом смещении час или год назад. А есть переходы на зимнее/летнее время, есть перенос часовых поясов. Ничего этого вы не учитываете. Задавать нужно не смещение а саму временную зону, предварительно загрузив в mysql tzdata.
Если вы действительно хотите «избежать головной боли», то не нужно хранить timezone-aware даты в базе данных вообще. Конвертируйте всё в UTC, храните в базе «простую» дату, ничего не знающую о таймзонах, и все вычисления также выполняйте в UTC. И только лишь при отображении данных пользователю конвертируйте их в нужную ему таймзону, на основании пользовательских настроек, используя свежую tzdata естественно.
Да и серверное время, и все настройки PHP рекомендуется в UTC выставлять, дабы не было сюрпризов. Keep it stupid simple, как говорится.
Да и серверное время, и все настройки PHP рекомендуется в UTC выставлять, дабы не было сюрпризов. Keep it stupid simple, как говорится.
Проблема еще в том, что поле TIMESTAMP не может быть одновременно не NULL и без ON UPDATE = CURRENT TIMESTAMP…
Вчера с удивлением обнаружил что в Django вопрос с временными зонами тоже через задницу решен. Пришлось извращаться с кастомными полями ORM…
Могу, кстати, посоветовать статью очень подробную и с рецептами asvetlov.blogspot.com/2011/02/date-and-time.html если коротко — внутри приложения всегда(!!!) работаем с UTC, в БД храним в UTC, преобразуем в/из только в момент показа пользователю/получения данных из формы и бед не знаем.
Могу, кстати, посоветовать статью очень подробную и с рецептами asvetlov.blogspot.com/2011/02/date-and-time.html если коротко — внутри приложения всегда(!!!) работаем с UTC, в БД храним в UTC, преобразуем в/из только в момент показа пользователю/получения данных из формы и бед не знаем.
Ну это то да, на самом деле во временных зонах все очень просто. Но только если закладывать это с самого начала. Как и с локализацией и с юнит-тестами. Если же внедрять в уже готовое приложение, то затраты будут несравненно больше.
Меня же удивило больше всего то, что даже в таких крупных и известных проектах эта проблема решается как-то никак практически.
Меня же удивило больше всего то, что даже в таких крупных и известных проектах эта проблема решается как-то никак практически.
Просто стандартная джанговская ORM DateTimeField возвращает naive время. Мне хочется работать с aware — временем, соответственно нужно сделать подкласс этого поля которое автоматом проставляет UTC… Казалось бы просто — ан нет — умная Django для MySQL выдает Exception если ему скормить datetime с проставленной tzinfo а для постгреса сохраняет ТЗ в соответствии с настройкой TIME_ZONE… В общем нужно прилично посидеть и поковыряться чтобы можно было более-менее комфортно работать.
работаем с UTC, в БД храним в UTC, преобразуем в/из только в момент показа пользователю/получения данных из формы и бед не знаем.
Не всегда так можно. Было дело… надо было выбирать из базы данные разбитые по дням. А дни — не по UTC, конечно же :) Проще таки группировать данные уже при выборке.
> в Django вопрос с временными зонами тоже через задницу решен
Согласен. Поэтому пришлось форкнуть django-timezones и серьезно переписать.
github.com/homm/django-timezones/blob/test/timezones/fields.py
Длинные комментарии почти к каждой строчке, чтобы самому не запутаться, что откуда берется.
В моделях то выглядит так:
Поля возвращаются в указанной таймзоне и с привязанной таймзоной. Т.е. их можно безопасно вычитать и сравнивать. При выборке, например, Order не забывайте делать .select_related('restaurant__city'), иначе для каждого заказа LocalizedDateTimeField будет ходить к базе. Правда, вы все равно можете получать created_utc в сыром виде, тогда конечно select_related и поход в базу не нужен.
Согласен. Поэтому пришлось форкнуть django-timezones и серьезно переписать.
github.com/homm/django-timezones/blob/test/timezones/fields.py
Длинные комментарии почти к каждой строчке, чтобы самому не запутаться, что откуда берется.
В моделях то выглядит так:
class City(DomainAttributes):
TIMEZONE_CHOICES = make_timezone_choices(pytz.country_timezones['ru'])
timezone = models.CharField(u'Временная зона', max_length=30, choices=TIMEZONE_CHOICES, blank=True, null=True, default=None)
created = tz_fields.LocalizedDateTimeField(u'Дата создания', auto_now_add=True, default=datetime.now, editable=False, timezone=u'timezone')
class Restaurant(DomainAttributes):
city = models.ForeignKey(City, verbose_name=u'Город')
created = tz_fields.LocalizedDateTimeField(u'Дата создания', auto_now_add=True, default=datetime.now, editable=False, timezone=u'city__timezone')
class Order(models.Model):
restaurant = models.ForeignKey(Restaurant, verbose_name=u'Ресторан')
created = tz_fields.LocalizedDateTimeField(u'Дата создания', auto_now_add=True, default=datetime.now, editable=False, timezone=u'restaurant__city__timezone')
Поля возвращаются в указанной таймзоне и с привязанной таймзоной. Т.е. их можно безопасно вычитать и сравнивать. При выборке, например, Order не забывайте делать .select_related('restaurant__city'), иначе для каждого заказа LocalizedDateTimeField будет ходить к базе. Правда, вы все равно можете получать created_utc в сыром виде, тогда конечно select_related и поход в базу не нужен.
О, спасибо! Думаю в этом проекте оставлю свои костыли в последующих попробую ваши). Еще-бы README хотябы немного кто-нибудь заполнил ^_^.
Просто у github.com/brosner/django-timezones столько форков, что все их просмотреть и выбрать наиболее доработанный довольно сложно, а основная ветка какая-то недопиленная тоже — как я понял, возвращает это поле все-равно naive datetime.
> Поля возвращаются в указанной таймзоне
В таймзоне проставленной в City.timezone?
> вы все равно можете получать created_utc в сыром виде
Ну вот это по-моему самый удачный вариант. В сочетании с фильтром для шаблона, умеющем приводить ТЗ к пользовательскому, должно довольно хорошо работать.
Просто у github.com/brosner/django-timezones столько форков, что все их просмотреть и выбрать наиболее доработанный довольно сложно, а основная ветка какая-то недопиленная тоже — как я понял, возвращает это поле все-равно naive datetime.
> Поля возвращаются в указанной таймзоне
В таймзоне проставленной в City.timezone?
> вы все равно можете получать created_utc в сыром виде
Ну вот это по-моему самый удачный вариант. В сочетании с фильтром для шаблона, умеющем приводить ТЗ к пользовательскому, должно довольно хорошо работать.
1. Не создавайте себе проблем (хранение не UTC дат), тогда не нужно будет их преодолевать (масштабное переписывание кода и SQL запросов).
2. Пост конечно нужный. Как раз для случая, когда первое не соблюдалось.
2. Пост конечно нужный. Как раз для случая, когда первое не соблюдалось.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Решение проблемы часовых поясов в веб-приложении