Введение: почему время — это боль
Представьте: вы пишете интернет-магазин. Клиент из Нью-Йорка оформляет заказ в 15:00 по местному времени. Вы сохраняете дату в базу. А потом сервер, стоящий в Москве, решает, что заказ сделан в 22:00. И начинается хаос: доставка опаздывает, отчеты не сходятся, клиенты злятся.
Знакомая ситуация? Добро пожаловать в мир временных зон в PHP. Эта тема — настоящий квест для новичков и даже для опытных разработчиков. Но есть хорошая новость: если один раз разобраться и настроить всё правильно, вы забудете о головной боли с датами навсегда.
В этой статье мы разберем, как работать с часовыми поясами в PHP, как избежать ловушек с летним временем и почему UTC — ваш лучший друг. Поехали!
Глобальная настройка временной зоны в PHP
Самый первый шаг — убедиться, что PHP знает, в каком часовом поясе работает ваш сервер. Делается это через директиву date.timezone в php.ini либо прямо в коде с помощью функции date_default_timezone_set().
// Устанавливаем временную зону для всего скрипта
date_default_timezone_set('Europe/Moscow');
echo date('Y-m-d H:i:s'); // Выведет текущее время по Москве
Но внимание! Если вы работаете с пользователями из разных стран, фиксировать одну зону на весь проект — плохая идея. Лучше всегда хранить время в UTC (всемирное координированное время) и конвертировать его под конкретного пользователя.
Проверка текущей временной зоны
Всегда полезно знать, какая зона установлена прямо сейчас:
echo date_default_timezone_get(); // Например: 'Europe/Moscow'
Объект DateTimeZone — ваш главный инструмент
В PHP есть мощный объект DateTimeZone. Он позволяет работать с часовыми поясами как с самостоятельными сущностями. Давайте посмотрим на базовый пример:
$timezone = new DateTimeZone('America/New_York');
$datetime = new DateTime('now', $timezone);
echo $datetime->format('Y-m-d H:i:s'); // Время в Нью-Йорке сейчас
Обратите внимание: мы передали временную зону прямо в конструктор DateTime. Это самый чистый способ работы с часовыми поясами в PHP.
Список всех доступных временных зон
Хотите узнать, какие зоны вообще поддерживает PHP? Вызовите метод listIdentifiers():
$zones = DateTimeZone::listIdentifiers();
echo count($zones); // Около 400+ зон!
// Вывести первые 10
print_r(array_slice($zones, 0, 10));
Этот список огромен. Но вам не нужно запоминать все — достаточно знать стандартные идентификаторы типа Europe/London, Asia/Tokyo или UTC.
Конвертация между временными зонами
Самая частая задача: взять время в одной зоне и перевести в другую. В PHP это делается элегантно через метод setTimezone().
// Время по UTC
$utcTime = new DateTime('2025-03-15 12:00:00', new DateTimeZone('UTC'));
// Конвертируем в Токио
$tokyoTime = clone $utcTime;
$tokyoTime->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo $utcTime->format('Y-m-d H:i:s') . ' UTC'; // 2025-03-15 12:00:00 UTC
echo ' = ';
echo $tokyoTime->format('Y-m-d H:i:s') . ' Tokyo'; // 2025-03-15 21:00:00 Tokyo
Обратите внимание: мы использовали clone, чтобы не изменить исходный объект. Если этого не сделать, оригинальное время тоже "переедет" в Токио.
Лучшая практика: хранить всё в UTC
Золотое правило веб-разработки: всегда храните даты и время в UTC. База данных, кэш, логи — везде UTC. А конвертацию под пользователя делайте только на этапе вывода.
// Сохраняем в БД (примерно)
$nowUTC = new DateTime('now', new DateTimeZone('UTC'));
$db->query("INSERT INTO orders (created_at) VALUES ('{$nowUTC->format('Y-m-d H:i:s')}')");
// При выводе для пользователя из Лос-Анджелеса
$userTimezone = new DateTimeZone('America/Los_Angeles');
$localTime = $nowUTC->setTimezone($userTimezone);
echo $localTime->format('Y-m-d H:i:s');
Летнее время (DST): как с ним бороться
Ах, это летнее время! Каждый год весной мы "теряем" час, а осенью — "находим". PHP умеет корректно обрабатывать DST, но есть подводные камни.
Например, в США переход на летнее время происходит в марте. Часа с 2:00 до 3:00 просто не существует. Если вы попытаетесь создать DateTime для этого промежутка, PHP может "сдвинуть" время на корректное значение.
// В США 14 марта 2025 года в 2:00 ночи часы перевели на 3:00
$date = new DateTime('2025-03-14 02:30:00', new DateTimeZone('America/New_York'));
echo $date->format('Y-m-d H:i:s'); // Выведет 2025-03-14 03:30:00 — PHP сам подправил!
Это поведение может быть как полезным, так и опасным. Если вам нужно точное "сырое" время, работайте с Unix timestamp — он не зависит от временных зон и DST.
$timestamp = time(); // Текущий Unix timestamp (всегда UTC)
$date = new DateTime("@$timestamp"); // Создаем из timestamp
echo $date->format('Y-m-d H:i:s'); // Время по UTC
Смещение временной зоны относительно UTC
Иногда нужно узнать, на сколько часов конкретная зона отличается от UTC. Для этого используйте метод getOffset():
$timezone = new DateTimeZone('Asia/Vladivostok');
$offset = $timezone->getOffset(new DateTime('now', new DateTimeZone('UTC')));
echo $offset / 3600; // 10 часов (UTC+10)
Это особенно полезно, когда вы строите интерфейс выбора часового пояса для пользователя — можно показать не только название, но и смещение.
Получение названия временной зоны из смещения
Обратная задача: у вас есть смещение (например, +5:30), и нужно найти подходящую зону. Это сложнее, потому что одно смещение может соответствовать нескольким зонам. Но можно перебрать:
function findTimeZoneByOffset($offsetHours) {
$zones = DateTimeZone::listIdentifiers();
foreach ($zones as $zone) {
$tz = new DateTimeZone($zone);
$offset = $tz->getOffset(new DateTime('now', new