Правильный взгляд на временные зоны в PHP / PHP. Производительность


Оригинал: Timezones, the Right Way


Это небольшая статья о том, как работать с временными зонами при хранении значений даты / времени в базе данных. Базы данных или язык программирования, который вы решите использовать, решающего значения не имеет, так как описываемые в статье явления универсальны. Свои действия я буду показывать на примере использования связки MySQL и PHP. Проблема, связанная с временными зонами, достаточно банальна. Многие часто забывают об их существовании, а некоторые считают их чем-то чрезмерно сложным. Обычно при настройке базы данных или при пользовании услугами веб-хостинга ваши временные зоны настроены по умолчанию. Настройки по умолчанию подойдут, если вы работаете с одним сервером, но что произойдет, если вы решите переместить его?

Тот же вопрос закономерен, если у вас несколько серверов в разных местах.
Вместо того чтобы хранить временные зоны с указанием каждой даты, лучше взять за основу стандартное время и связать все ваши даты с ним. Такой способ позволяет сделать переход на стандартное время до того, как значение будет сохранено в базе данных. Вообще, хорошая идея — просто использовать UTC +00:00.

Что такое UTC

Формат UTC расшифровывается как «Всемирно координированное время» и является преемником «Среднего времени по Гринвичу» (GMT). Эти два формата практически идентичны, но UTC считается принятым стандартом. В общем, UTC основывается на выделении 40 различных временных зон. За точку отсчета в UTC принято брать 00:00 часов. Итого получается 12 основных временных зон, как с положительным, так и с отрицательным значением, а также некоторые дополнительные временные зоны, вроде получасовых UTC +04:30. Идея состоит в том, что если у вас есть временная зона UTC -05: 00, то вам придется вычесть 5 часов от начального UTC +00:00, чтобы добиться нужного соответствия. То есть, если это 14:00 (или 2 часа вечера) а ваша временная зона UTC-05: 00 (EST или Восточный часовой пояс), то UTC +00:00будет равен 19:00 (или 7 часам вечера).


Хранение времени в формате UTC +00:00

Первое, что мы должны сделать, — определиться, какую временную зону использовать для хранения всех наших значений. Я настоятельно рекомендую взять за основу UTC +00:00 — без особой причины, кроме как той, что ее легко запомнить и 0 представляет собой идеальную точку отсчета. Так как мы знаем, что все даты указаны в UTC +00:00, становится гораздо легче представлять надлежащее время для каждого пользователя на основе их личных временных зон. После того как пользователь выбрал (или у нас есть автоопределение) свой часовой пояс, вывод соответствующего времени для этого пользователя становится обычным делом.

Установка временной зоны по умолчанию

Полагая, что все наши даты / время хранятся в формате UTC, мы можем просто установить по умолчанию временную зону, которая будет использоваться для отображения каждого времени. Это позволит автоматически подобрать дату и время для конкретного пользователя. В PHP все, что нам нужно сделать, — это установить временную зону, например, так:
date_default_timezone_set('EST');

Мы можем сделать это в каком-нибудь блоке инициализации в начале нашего кода — и все готово: все даты будут отображаться надлежащим образом для того часового пояса, в котором находится пользователь.

Если мы хотим получить полный контроль над датой и временем, мы можем построить функцию следующим образом:
function get_date($date)
{
    date_default_timezone_set('EST');
    $date = date("Y-m-d H:i:s", strtotime($date));
    date_default_timezone_set('UTC');
 
    return $date;
}

Настройка PHP под UTC +00:00

Конечно, все было бы не так просто, если бы мы не настроили нашу систему под UTC, как временную зону по умолчанию. Я уверен, что это может быть сделано примерно одинаково и в других языках, но PHP это делается следующим образом:
date.timezone = UTC

Теперь всякий раз, когда мы используем такие вещи, как, например, функцию time(), это автоматически дает нам время в формате UTC с соответствующим смещением на основе нашего серверного времени. Если мы захотим настроить даты, в такой базе данных как MySQL, сделаем так:
date("Y-m-d H:i:s", time());

Если у нас не было доступа к изменению конфигурации PHP, то мы должны настроить время вручную при установке даты, вот так:
date("Y-m-d H:i:s", time() - date("Z"));

Настройка MySQL под UTC +00:00

Непосредственная установка даты, как мы делали выше, применима, если у нас нет доступа к изменению параметров конфигурации MySQL. Тем не менее, если мы хотим использовать TIMESTAMP, которая обычно самообновляется, или такую функцию даты из MySQL, как NOW(), то мы должны настроить MySQL для использования UTC в качестве временной зоны по умолчанию.

В MySQL это делается путем установки UTC смещения, которое может быть сделано так же легко, как это было в PHP, путем внесения следующих изменений:
default-time-zone = '+00:00'

Обновление существующих дат / времени

Если вы решили стандартизировать временные зоны при помощи UTC, где временной зоной по умолчанию является UTC +00:00 смещение, то теперь ваш текущее время в вашей базе данных будет неработоспособно.

К счастью, MySQL предоставляет удобную функцию, которая дает нам возможность легко обновлять существующие часовые пояса.

ПРИМЕЧАНИЕ: Если у вас есть какие-нибудь временные метки, то их нужно обновить в первую очередь.

Мы можем осуществлять поиск по всем полям даты и времени в нашей базе с помощью следующего запроса:
SELECT * 
FROM `information_schema`.`COLUMNS` 
WHERE
   `TABLE_SCHEMA`='table_name' AND
   (`DATA_TYPE`='timestamp' OR `DATA_TYPE`='datetime' OR `DATA_TYPE`='date');

Отсюда мы можем использовать следующий запрос, чтобы обновить даты / время в наших таблицах:
UPDATE `table_name` SET `timestamp`=CONVERT_TZ(`timestamp`, '-05:00', '+00:00');

Где -05:00 — текущая временная зона, в формате которой хранится время (она как и в предыдущем случае относится к Восточной). Временная зона +00:00, в которую мы хотим преобразовать, в данном случае относится к UTC.