PHP LinuxWindowsmacOS

PHP: DateTimeZone показывает неверную дату при переводе часов

  • Обновлено
  • Чтение ≈ 3 минуты

Краткий ответ

Почему DateTimeZone даёт неожиданное местное время на переходе летнего и зимнего времени: отличие UTC, зоны IANA, неоднозначных часов и фиксированного смещения.

В этой статье
  1. Когда возникает ошибка
  2. Причины и признаки
  3. Пошаговая диагностика и устранение
  4. Проверяемый пример и команды
  5. Проверка исправления и действия при неудаче

#Когда возникает ошибка

Переход на летнее или зимнее время зависит от региона и конкретного года. DateTimeZone не «исправляет» ошибочную исходную дату сам: если передан часовой пояс с фиксированным смещением или строка времени, которая не существует/двусмысленна на переходе, результат может отличаться от ожиданий. Для сохранения момента времени полезно хранить UTC, а показывать его в региональной зоне, например Europe/Berlin.

#Причины и признаки

Возможная причинаКак узнатьЧто делать
Фиксированное смещение вместо регионаВместо Europe/Berlin указан +01:00, поэтому летний переход не учитывается.Зимой совпадает, летом различается на час.Используйте идентификатор региона IANA для отображения местного времени.
Неоднозначный или отсутствующий местный часВо время перевода стрелок часть времени повторяется или пропадает.Введённые 02:30 могут соответствовать двум моментам либо ни одному.Уточните UTC-смещение или исходный абсолютный момент.
Устаревшие правила или неверная исходная зонаБаза часовых поясов в системе устарела или дата сохранена без зоны.На одной машине время отличается, при одной и той же UTC-метке.Сверьте идентификатор зоны, версию базы и правила для года события.

#Пошаговая диагностика и устранение

  1. Зафиксируйте точную дату и два формата времениЗапишите ожидаемую дату, фактический вывод, регион, год события и то, поступает ли исходный момент как UTC-строка, Unix timestamp или местное время без смещения. Не сравнивайте лишь часы: у зимнего перехода два разных момента могут иметь одинаковую местную подпись.
  2. Проверьте исходный часовой пояс в кодеОткройте файл в редакторе и найдите new DateTimeZone(...), date_default_timezone_set(...), new DateTime(...) или strtotime. Посмотрите, передан ли регион IANA или фиксированная строка +01:00. Сравните date_default_timezone_get() для окружения, которое выполняет сайт. Для специальных требований не полагайтесь на неявную системную зону.
  3. Сравните два абсолютных момента вокруг переходаНа тестовой копии запустите пример с 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 в эту ночь нет. Если разницы нет, проверьте регион в исходном коде и загруженную базу часовых поясов.
  4. Разберите ввод без зоны, а не только выводЕсли форма принимает «29.03.2026 02:30» в Europe/Berlin, такое время не существует. Не полагайтесь на молчаливую нормализацию DateTime: попросите пользователя выбрать допустимое время либо задайте правила обработки. Осенью локальное 02:30 повторяется, поэтому нужно выбрать один из двух UTC-моментов на основании смещения или явного выбора.
  5. Храните момент в UTC, отдельно регион отображенияЕсли дата обозначает уже состоявшееся событие, сохраняйте ISO 8601 со смещением или UTC-время и отдельно зону пользователя для отображения. При выводе создайте DateTimeImmutable в UTC и примените setTimezone с правильной зоной. Для будущих повторяющихся событий отдельно храните правила календаря, поскольку местное время важнее фиксированного UTC при изменении законодательства.
  6. Проверьте правила на вашей системеВ терминале запустите php -r "echo timezone_version_get(), PHP_EOL;" и запишите версию базы часовых поясов. Если только один сервер даёт иной результат, сверяйте версии PHP, пакеты tzdata и окружение; изменение системного часового пояса не должно быть первым действием. Обновление пакетов согласуйте с администратором и протестируйте на копии.
  7. Сверьте оба перехода и откатите неправильное преобразованиеПроверьте на тестовых данных весенний и осенний переход и обычный летний день. Сравните timestamp исходного и преобразованного объекта: setTimezone не должен менять момент времени. Если после правки даты на сайте сдвинулись массово, верните код из копии, проверьте, не была ли старая база уже заполнена смещёнными значениями, и не выполняйте повторную автоматическую конвертацию.

#Проверяемый пример и команды

PHP
Отдельный PHP-файл, запуск 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:00
Linux · Терминал (Bash)Команда
В терминале сервера или тестовой машины. Отображает версию базы временных зон, ничего не меняет.
php -r "echo timezone_version_get(), PHP_EOL;"

#Проверка исправления и действия при неудаче

Результат правильный, когда два разных UTC-момента на переходе корректно отображаются в Europe/Berlin с отличающимися смещениями, а данные не сдвигаются повторным преобразованием. Для другого региона или года используйте его реальные правила, а не даты перехода Берлина из примера.

Источники и обновления

Последнее обновление статьи: 2 ноября 2026. Версии программ и интерфейсы меняются — если шаги не совпадают с вашей версией, сверяйтесь с документацией производителя.