Сначала о базовых принципах.
1) Мы полагаем, что PHP и база данных работают в одном часовом поясе. Чаще всего часовой пояс определяется настройками операционной системы, реже используются специальные настройки PHP и БД. На самом деле, PHP и БД должны работать в одном поясе.
2) Мы не ведем собственную базу часовых поясов. Мы полностью полагаемся на PHP в вопросах часовых поясов, перехода на летнее/зимнее время и прочее. Поддержка DateTime появилась в php 5.2.0 - на более старых версиях часовые пояса в продукте работать не будут. PHP использует ту же базу часовых поясов, что и многие сборки Unix. В этой базе идентификаторы зон имеют вид "Europe/Kaliningrad".
3) Все даты записываются и хранятся в БД в локальном времени сервера. Это наше наследие, которое нельзя легко изменить - никаким обновлением невозможно изменить значения во всех полях типа дата всех таблиц. Т.е. если сервер работает в московском часовом поясе, то "14:00:00", записанное в БД - это именно 14:00 по Москве.
4) При выборке из БД значения полей типа дата (в полном формате) переводятся в указанный в настройках часовой пояс (о настройках ниже). Если дата в коротком формате (без часов-минут-секунд), то арифметика часовых поясов не производится. Проще говоря, посетители видят время в своем локальном времени.
5) При записи в БД значения полей типа дата переводятся из указанного в настройках часового пояса в локальное время сервера (только для полного формата). Проще говоря, посетители во всех формах должны вводить время в своем локальном времени.
Теперь о настройках.
В настройках ядра появилась секция "Часовые пояса":
1) "Локальное время сервера" показывает, в каком поясе фактически работает сервер. Для корректной работы системы часовых поясов должна быть правильная комбинация времени и смещения от UTC (18:26:10 +0300). Из продукта время и пояс не настраиваются, они просто должны быть правильными. Если что-то не так, нужно настраивать ОС и/или PHP и БД.
2) Флажок "Разрешить использование часовых поясов" включает или полностью отключает использование часовых поясов - на усмотрение администратора. При отключенной галочке запросы к БД будут немного короче.
3) "Часовой пояс сервера по умолчанию" указывает, в какой/из какого часового пояса будут конвертированы даты по умолчанию. Например, сайт стоит на разделяемом хостинге в США, фактически работает по чикагскому времени. Управлять системным временем на хостинге возможности нет. На сайте - форумы с преимущественно калининградскими посетителями. Мы выбираем зону Europe/Kaliningrad - и вуаля, сообщения форума показываются по калининградскому времени для всех, даже для анонимов. Эта настройка может быть переопределена конкретным пользователем под себя (ниже).
4) "По умолчанию автоматически определять часовой пояс по браузеру". Javascript браузера знает, в каком часовом поясе работает пользователь на своем компьютере. Мы можем воспользоваться этой информацией, чтобы не задавать пользователю лишних вопросов, а определить его часовой пояс автоматически. Опция особенно полезна в корпоративном портале, где часовые пояса имеют большое значение.
В профиле пользователя появилась настройка "Часовые пояса":
Для каждого пользователя можно указать настройки "по умолчанию", "автоматически", "выбрать из списка". "По умолчанию" - настройки автоопределения берутся из настроек главного модуля. "Выбрать из списка" - можно выбрать конкретную зону. Настройки конкретного профиля имеют приоритет над настройками ядра.
Настройки поясов профиля продублированы в компонентах main.profile, main.register, socialnetwork.user_profile_edit, forum.user.profile.edit. Например, в настройках компонента соцсети можно указать поле "Часовой пояс":
Тогда в редактировании профиля будет доступен выбор часового пояса:
Аналогичные настройки есть в других перечисленных компонентах.
Как это выглядит.
Допустим, сервер работает в часовом поясе UTC+3 (Калиниград летом). Я у себя в профиле указал часовой пояс UTC+4 (Москва летом). Я добавляю новость, в поле "Дата новости" указываю московское время, согласно своему поясу:
При сохранении дата переводится в локальное время сервера:
Как видно, значение в БД на один час меньше - это как раз разница между Калининградом и Москвой.
Если я поменяю свой часовой пояс, допустим, на Мальту, то я увижу время новости уже по мальтийскому времени (06.10.2009 10:53:09). Причем это время будет вычислено с учетом действующего именно на тот момент смещения от UTC.
Как это работает. (Информация для разработчиков)
Мы решили, что самым правильным будет применить арифметику часовых поясов на самом низком уровне - на уровне выборки из БД и записи в БД. В противном случае пришлось бы форматировать дату непосредственно при выводе. Например, в компоненте новостей написано:
<?=$arResult["ACTIVE_FROM"]?> |
Понятно, что таких мест в продукте очень много, каждое не исправишь. Поэтому GetList инфоблока возвращает уже модифицированную дату. На самом деле, конвертация происходит на еще более низком уровне, в функциях CDatabase:: DateToCharFunction() и CDatabase::CharToDateFunction(). Поэтому если API при выборке и записи использует эти функции, то все должно заработать "само".
Часто бывает, что вычисление даты делается кодом PHP, например:
$time_string = ConvertTimeStamp(time(), "FULL"); |
В этом случае нам придется учесть часовой пояс самостоятельно:
$time_string = ConvertTimeStamp(time()+CTimeZone::GetOffset(), "FULL"); |
В этом примере мы переводим текущее время сервера в часовой пояс пользователя. В таких операциях нужно запомнить одну простую формулу, которая поможет манипулировать со временем:
CTimeZone::GetOffset() = {время пользователя} - {время сервера}
А что делать, если нам совершенно точно не потребуется конвертировать время при выборке из БД и при сохранении в БД? Нам поможет такой прием:
CTimeZone::Disable(); $DB->Add(...); CTimeZone::Enable(); |
При разработке кода теперь необходимо учитывать следующие особенности:
1) Все агенты исполняются в локальном времени сервера.
2) В кеше компонентов часовой пояс уже учитывается - чтобы посетители из разных поясов видели разное время новости.
3) В CPHPCache и CPageCache часовой пояс не учитывается. Если кешируемые данные зависят от пояса, то в ID кеша необходимо подмешивать результат выполнения CTimeZone::GetOffset();
4) В полях CEvent::Send() желательно передавать локальное время сервера - в общем случае мы не знаем, для кого предназначено письмо.
Чтобы учесть все эти нюансы, нам пришлось буквально "проползти с фонариком" по всем модулям.
Надеюсь, что нововведения понравятся нашим пользователям и разработчикам!
Но будем привыкать хотя это конечно для КП важно прежде всего.
Но на рабочем КП пока не пробовал применять.
зы. К слову сказать, его бы вообще доработать. Дать возможность только даты, помимо даты/времени, например. А сейчас по дефолту там намертво зашит вызов календаря с таймбаром, это не есть удобно. Не, можно, конечно, отдублировать и откастомить, но лучше бы штатное свойство так умело, чтобы их не плодить однотипных.
Приведу ещё раз пример, может я затупил просто. В ИБ есть свойство дата/время. Я запрашиваю у пользователя только дату, меня совершенно не интересует, какой у него часовой пояс, мне нужна только дата. Сохраняется элемент и случается
Аналогичный пример при любом оффсете, как положительном, так и отрицательном - имеем расхождение по времени в свойстве. Соответственно, сортировка по свойству плывёт - допустим, у нас нумератор, нужна выборка элементов с двойной сортировкой (первичная - дата, вторая - номер) чтобы определить последний номер документа в дате: "PROPERTY_DATE" => "DESC", "PROPERTY_NUMBER" => "DESC". Но как только у нас появляется юзер с оффсетом, нашей выборке приходит конец.
Что делать?
И да, в связи с вышеизложенным это
5) При записи в БД значения полей типа дата переводятся из указанного в настройках часового пояса в локальное время сервера (только для полного формата). Проще говоря, посетители во всех формах должны вводить время в своем локальном времени.
Вы имеете ввиду, что учитывается именно смещение между юзером и сервером на дату 06.10.2009?
Я вот насколько вижу код метода CTimeZone::GetOffset(), то в вычислении смещения участвует текущее время, а не учитывается дата, для которой нужно вычислить смещение.
А ведь может быть такая ситуация, что из-за смены зимнего и летнего времени смещение зависит от даты! Пример: юзер в таком часовом поясе, в котором время всегда UTC+6, а сервер в таком часовом поясе, в котором в летнее время UTC+4, а в зимнее UTC+3 (например как московское временами). То есть для даты около СЕЙЧАС смещение равно 2 часа, а для даты ноябрьской смещение рано уже три часа. И если этот юзер будет указывать сейчас время новости на ноябрь, то применится неправильное смещение....