#Когда возникает ошибка
Переход на летнее или зимнее время зависит от региона и конкретного года. DateTimeZone не «исправляет» ошибочную исходную дату сам: если передан часовой пояс с фиксированным смещением или строка времени, которая не существует/двусмысленна на переходе, результат может отличаться от ожиданий. Для сохранения момента времени полезно хранить UTC, а показывать его в региональной зоне, например Europe/Berlin.
#Причины и признаки
| Возможная причина | Как узнать | Что делать |
|---|---|---|
| Фиксированное смещение вместо регионаВместо Europe/Berlin указан +01:00, поэтому летний переход не учитывается. | Зимой совпадает, летом различается на час. | Используйте идентификатор региона IANA для отображения местного времени. |
| Неоднозначный или отсутствующий местный часВо время перевода стрелок часть времени повторяется или пропадает. | Введённые 02:30 могут соответствовать двум моментам либо ни одному. | Уточните UTC-смещение или исходный абсолютный момент. |
| Устаревшие правила или неверная исходная зонаБаза часовых поясов в системе устарела или дата сохранена без зоны. | На одной машине время отличается, при одной и той же UTC-метке. | Сверьте идентификатор зоны, версию базы и правила для года события. |
#Пошаговая диагностика и устранение
- Зафиксируйте точную дату и два формата времениЗапишите ожидаемую дату, фактический вывод, регион, год события и то, поступает ли исходный момент как UTC-строка, Unix timestamp или местное время без смещения. Не сравнивайте лишь часы: у зимнего перехода два разных момента могут иметь одинаковую местную подпись.
- Проверьте исходный часовой пояс в кодеОткройте файл в редакторе и найдите
new DateTimeZone(...),date_default_timezone_set(...),new DateTime(...)илиstrtotime. Посмотрите, передан ли регион IANA или фиксированная строка+01:00. Сравнитеdate_default_timezone_get()для окружения, которое выполняет сайт. Для специальных требований не полагайтесь на неявную системную зону. - Сравните два абсолютных момента вокруг переходаНа тестовой копии запустите пример с
2026-03-29 00:30:00 UTCи2026-03-29 01:30:00 UTC, затемsetTimezone(new DateTimeZone("Europe/Berlin")). Первый момент станет 01:30+01:00, второй — 03:30+02:00; локального 02:30 в эту ночь нет. Если разницы нет, проверьте регион в исходном коде и загруженную базу часовых поясов. - Разберите ввод без зоны, а не только выводЕсли форма принимает «29.03.2026 02:30» в Europe/Berlin, такое время не существует. Не полагайтесь на молчаливую нормализацию DateTime: попросите пользователя выбрать допустимое время либо задайте правила обработки. Осенью локальное 02:30 повторяется, поэтому нужно выбрать один из двух UTC-моментов на основании смещения или явного выбора.
- Храните момент в UTC, отдельно регион отображенияЕсли дата обозначает уже состоявшееся событие, сохраняйте ISO 8601 со смещением или UTC-время и отдельно зону пользователя для отображения. При выводе создайте DateTimeImmutable в UTC и примените setTimezone с правильной зоной. Для будущих повторяющихся событий отдельно храните правила календаря, поскольку местное время важнее фиксированного UTC при изменении законодательства.
- Проверьте правила на вашей системеВ терминале запустите
php -r "echo timezone_version_get(), PHP_EOL;"и запишите версию базы часовых поясов. Если только один сервер даёт иной результат, сверяйте версии PHP, пакеты tzdata и окружение; изменение системного часового пояса не должно быть первым действием. Обновление пакетов согласуйте с администратором и протестируйте на копии. - Сверьте оба перехода и откатите неправильное преобразованиеПроверьте на тестовых данных весенний и осенний переход и обычный летний день. Сравните timestamp исходного и преобразованного объекта:
setTimezoneне должен менять момент времени. Если после правки даты на сайте сдвинулись массово, верните код из копии, проверьте, не была ли старая база уже заполнена смещёнными значениями, и не выполняйте повторную автоматическую конвертацию.
#Проверяемый пример и команды
php имя.php. Проверяет конкретно весенний переход Europe/Berlin в 2026 году. Смена зоны отображения не меняет момент времени (Unix timestamp).<?php
$zone = new DateTimeZone('Europe/Berlin');
foreach (['2026-03-29 00:30:00', '2026-03-29 01:30:00'] as $utc) {
$moment = new DateTimeImmutable($utc, new DateTimeZone('UTC'));
echo $moment->setTimezone($zone)->format('Y-m-d H:i:s P') . PHP_EOL;
}
// 2026-03-29 01:30:00 +01:00
// 2026-03-29 03:30:00 +02:00php -r "echo timezone_version_get(), PHP_EOL;"#Проверка исправления и действия при неудаче
Результат правильный, когда два разных UTC-момента на переходе корректно отображаются в Europe/Berlin с отличающимися смещениями, а данные не сдвигаются повторным преобразованием. Для другого региона или года используйте его реальные правила, а не даты перехода Берлина из примера.