Что было. На Ubuntu Server 20.04 включена синхронизация времени, timedatectl показывает System clock synchronized: yes, но PHP-скрипт через date() выводит время в UTC, на два часа меньше местного. Автор сдался и добавил в код strtotime('+2 hours'). Участник предложил проверить date_default_timezone_get().
Почему костыль плох. Смещение относительно UTC меняется дважды в год при переходе на летнее время и различается между регионами: «+2 часа» через полгода станет «+1». А в базе данных при этом хранятся значения, которые уже нельзя будет однозначно интерпретировать.
Как это устроено. Синхронизация (NTP) выравнивает момент времени, всегда в UTC. Часовой пояс это отдельная настройка, которая говорит, как этот момент показывать. И у системы, и у PHP, и у базы данных, и у контейнера часовые пояса настраиваются независимо:
- система:
timedatectl set-timezone Europe/Berlin(файл/etc/localtime); - PHP:
date.timezone = Europe/Berlinвphp.iniдля нужного SAPI (у CLI и FPM разныеphp.ini) либоdate_default_timezone_set()в начале приложения; без настройки PHP использует UTC; - MySQL/MariaDB:
default_time_zoneв конфиге, иначе берётся системный;SELECT @@global.time_zone, @@session.time_zone; - контейнер: переменная
TZ=Europe/Berlinи наличиеtzdataв образе; системный пояс хоста в контейнер не наследуется.
Что мы рекомендуем для серверов. Хранить и передавать время в UTC (в базе TIMESTAMP/timestamptz, в API ISO 8601 с суффиксом Z), а в местный пояс переводить только при показе пользователю, зная его пояс. Тогда смена пояса сервера или переезд ничего не ломают. Системный пояс сервера при этом лучше оставить UTC: журналы, cron и время из разных источников совпадают, а сотрудники в разных поясах не спорят о «серверном времени».
Диагностика.
timedatectl # системный пояс и синхронизация
php -i | grep -i 'date.timezone' # для CLI
php-fpm8.3 -i | grep -i 'date.timezone' # для веб-сервера
mysql -e 'SELECT @@global.time_zone, NOW(), UTC_TIMESTAMP();'
Если date() в браузере и в консоли отличаются, дело в разных php.ini.
Cron и systemd-таймеры тоже живут в системном поясе; задача «в 03:00» после смены пояса сервера сдвинется. В OnCalendar можно указать пояс явно: OnCalendar=*-*-* 03:00:00 Europe/Berlin.
Настройка описана в документации PHP.