Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Дата и время: полный справочник Date/Time
  2. Из записи встречи в понятную дату
  3. Момент, местные часы, зона и календарное правило
  4. DateTime: состояние и все методы
  5. DateTimeImmutable: состояние и все методы
  6. DateTimeInterface: принять оба вида дат
  7. Установка date, база зон и все INI-настройки
  8. Все готовые форматы DATE_* и константы интерфейса
  9. Все обозначения формата ввода даты
  10. Все обозначения формата вывода даты
  11. Три разных языка даты: ввод по форме, свободный разбор, вывод
  12. Все семейства записи времени и зоны
  13. Все семейства записи календарной даты
  14. Составные форматы: журналы, API, недели и timestamp
  15. Все относительные слова, единицы и их порядок
  16. Почему first monday и first monday of дают разные даты
  17. Календарное правило и прошедшая длительность
  18. Интервал, дата и расписание: три разных объекта
  19. Почему три часа и три часа календаря могут различаться
  20. Часовой пояс: как одна встреча становится 13:00 и 21:00
  21. Класс DateTimeZone: методы, флаги и состояние
  22. Настройка профиля: регион вместо текущего смещения
  23. Восстановление DateTimeZone: технический протокол, не формат API
  24. Полный каталог поясов: как читать эти имена
  25. Африка: все поддерживаемые идентификаторы
  26. Америка: все поддерживаемые идентификаторы
  27. Антарктида: все поддерживаемые идентификаторы
  28. Арктика: все поддерживаемые идентификаторы
  29. Азия: все поддерживаемые идентификаторы
  30. Атлантика: все поддерживаемые идентификаторы
  31. Австралия: все поддерживаемые идентификаторы
  32. Европа: все поддерживаемые идентификаторы
  33. Индийский океан: все поддерживаемые идентификаторы
  34. Тихий океан: все поддерживаемые идентификаторы
  35. Другие и совместимые имена: все поддерживаемые идентификаторы
  36. Практика: диагностика времени заказа без угадывания региона
  37. Две ветви ошибок даты: аргументы и состояние
  38. DateError: когда возникает и что делать
  39. DateObjectError: когда возникает и что делать
  40. DateRangeError: когда возникает и что делать
  41. DateException: когда возникает и что делать
  42. DateInvalidOperationException: когда возникает и что делать
  43. DateInvalidTimeZoneException: когда возникает и что делать
  44. DateMalformedIntervalStringException: когда возникает и что делать
  45. DateMalformedPeriodStringException: когда возникает и что делать
  46. DateMalformedStringException: когда возникает и что делать
  47. Все процедурные функции Date/Time
  48. Согласовать дату с внешним сервисом
  49. Функции и методы этой темы
  50. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 83 · От первой даты до справочника

Дата и время: полный справочник Date/Time

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

Из записи встречи в понятную дату

Наглядная модель

Дата события проходит через приложение

Поставщик сообщил начало встречи: 2024-06-15T07:30:00+00:00. Нужно показать посетителю в Москве то же событие.

  1. Строка из API

    Дата, часы и смещение +00:00 вместе определяют момент.

  2. Проверенный разбор

    createFromFormat, диагностика и сверка точного формата создают DateTimeImmutable.

  3. Europe/Moscow

    setTimezone меняет представление: момент события остаётся тем же.

  4. 15.06.2024 10:30

    format возвращает подпись для посетителя. В API отправляют согласованный формат со смещением.

Строка, момент и местная подпись — разные представления. Ручное прибавление трёх часов к timestamp сдвинуло бы само событие.

В интернет-магазине нужно показать время заказа, в сервисе записи — время встречи, в интеграции — передать момент события другой системе. Строка «15 июня, 10:30» без года и часового пояса не определяет точный момент. Объект даты соединяет календарные поля с правилами зоны, а format превращает их в подпись нужного вида.

Один момент и его подпись
  1. Строка: 2024-06-15 10:30:00
  2. Зона: Europe/Moscow
  3. DateTimeImmutable хранит дату с зоной
  4. format("d.m.Y H:i") возвращает 15.06.2024 10:30

$meeting — переменная для объекта. new DateTimeImmutable(...) создаёт этот объект; первая строка в скобках задаёт дату, второй аргумент — объект зоны. ->format(...) вызывает действие объекта и возвращает строку. echo выводит строку. В шаблоне d — день, m — месяц, Y — год, H — часы, i — минуты.

Показать подтверждённое время встречиСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Moscow');
$meeting = new DateTimeImmutable('2024-06-15 10:30:00', $zone);
$label = $meeting->format('d.m.Y H:i');
echo 'Встреча: ', $label, ' (Москва)', "\n";
Ожидаемый результат
Встреча: 15.06.2024 10:30 (Москва)

Zone хранит правила региона. Meeting получает строку даты и готовую зону как два аргумента. Вызов format читает meeting и возвращает строку15.06.2024 10:30 в label; echo добавляет назначение встречи и подпись региона. Format сам не печатает и не меняет дату. Полный маршрут от одной даты до сроков находится в главе «Дата и время».

Дальше идут все классы, методы, функции, форматы и настройки книги Date/Time. Для базы языка рядом доступны переменные, аргументы и return, объекты и вызов методов. DateTimeImmutable::__construct подробно разбирает создание; DateTimeImmutable::format — вывод.

Момент, местные часы, зона и календарное правило

Unix timestamp — число секунд от 1970-01-01 00:00:00 UTC, одинаковое для одного момента в разных зонах. Местное время — то, что показывают часы в выбранном регионе. Часовой пояс — правила связи между этими представлениями, включая исторические изменения. Отдельно существует календарная дата без времени, например день рождения, и правило повторения, например каждый понедельник в 09:00.

Один момент не становится другим из-за зоны
  1. 07:30 UTC
  2. Europe/Moscow на этой дате: +03:00
  3. 10:30 на московских часах
  4. Unix timestamp у обоих представлений один
Что хранитьПример и причина
Совершившееся событиеМомент UTC/Unix с согласованной точностью; для отображения учитывают зону посетителя.
Будущую местную встречуМестная дата/время и идентификатор зоны, выбранная политика DST, при необходимости рассчитанный момент. Правила tzdb могут обновиться.
ПовторениеПравило календаря и зона: «09:00 каждый понедельник» нельзя надёжно заменить постоянным количеством секунд.
День рожденияКалендарная дата без выдуманного времени UTC; приведение полуночи к другой зоне может сменить день.
Время выполнения кодаМонотонный hrtime; системные часы могут корректироваться. time/microtime нужны для иных задач.
Подготовить одну встречу для двух календарейСамостоятельный пример
<?php
$utc = new DateTimeImmutable('2024-06-15 07:30:00', new DateTimeZone('UTC'));
$local = $utc->setTimezone(new DateTimeZone('Europe/Moscow'));
echo 'Календарь UTC: ', $utc->format('d.m.Y H:i P'), "\n";
echo 'Календарь Москвы: ', $local->format('d.m.Y H:i P'), "\n";
$difference = $local->getTimestamp() - $utc->getTimestamp();
echo 'Между моментами: ', $difference, ' секунд', "\n";
Ожидаемый результат
Календарь UTC: 15.06.2024 07:30 +00:00
Календарь Москвы: 15.06.2024 10:30 +03:00
Между моментами: 0 секунд

Обе подписи относятся к одному событию: разница меток равна0 секунд. SetTimezone возвращает представление с другими местными часами, не переносит встречу на три часа. У календарной даты без времени такой перевод вообще может быть ненужным — сохраняйте её исходный смысл.

PHP использует пролептический григорианский календарь с современными правилами високосных лет и для древних дат. Есть астрономический год 0; leap seconds не поддерживаются. Внутреннее представление времени 64-битное и охватывает примерно ±292 миллиарда лет; это не обещание того же диапазона у PHP int на 32-битной платформе или у внешней БД/протокола. Свободный разбор и нормализация.

DateTime: состояние и все методы

DateTime меняет себя при modify/add/sub/set… . Две переменные могут указывать на тот же объект, поэтому изменение через одну видно через другую. Оба класса реализуют DateTimeInterface и поддерживают сравнение дат. Они не final, но наследнику нужно правильно инициализировать родителя. Для обычной работы наследование не требуется.

Кому принадлежит изменённая дата
  1. $start содержит 15 июня
  2. $other = $start указывает на тот же объект
  3. $next = $other->modify("+1 day")
  4. start/other/next — один объект, теперь 16-го
Увидеть сохранённую дату и результат переносаСамостоятельный пример
<?php
$start = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$other = $start;
echo 'Перед переносом: ', $start->format('d.m.Y H:i'), "\n";
$next = $other->modify('+1 day');
echo 'Через start после переноса: ', $start->format('d.m.Y H:i'), "\n";
echo 'Через next после переноса: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перед переносом: 15.06.2024 10:30
Через start после переноса: 16.06.2024 10:30
Через next после переноса: 16.06.2024 10:30

Присваивание other=start даёт второе имя тому же объекту. Modify через other меняет этот общий объект на16 июня и возвращает его же в next. Поэтому чтение через start уже показывает16 июня. Для независимого переноса ниже используется clone.

Сделать независимую изменяемую копиюСамостоятельный пример
<?php
declare(strict_types=1);

$received = new DateTime('2024-06-15 UTC');
$reminder = clone $received;
$reminder->modify('+1 day');
echo $received->format('Y-m-d'), "\n";
echo $reminder->format('Y-m-d'), "\n";
Ожидаемый результат
2024-06-15
2024-06-16

clone отделяет состояние даты. DateTimeImmutable позволяет получить такое сохранение исходника по умолчанию при обычных изменениях. Внешний API, требующий DateTime, может оправдывать изменяемый вариант.

МетодДля чего
DateTime::__constructСоздать дату из строки и явно выбранного часового пояса.
DateTime::createFromTimestampСоздать объект из секунд Unix, сохранив дробную часть до микросекунд.
DateTime::createFromImmutableПолучить независимый объект DateTime с тем же моментом и зоной.
DateTime::createFromInterfaceПолучить независимый объект DateTime с тем же моментом и зоной.
DateTime::createFromFormatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
DateTime::getLastErrorsСразу прочитать предупреждения и ошибки последнего разбора даты.
DateTime::formatПолучить строку даты для человека или согласованного внешнего формата.
DateTime::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.
DateTime::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.
DateTime::getTimezoneПолучить объект часового пояса, связанный с датой.
DateTime::getMicrosecondПолучить целое число микросекунд внутри секунды.
DateTime::setDateПоставить конкретный год, месяц и день, сохранив время суток и зону.
DateTime::setISODateНайти дату по ISO-году, номеру недели и дню недели.
DateTime::setTimeНазначить часы, минуты, секунды и микросекунды на существующую дату.
DateTime::setTimestampУстановить точный момент по целым секундам Unix, сохранив зону отображения.
DateTime::setTimezoneПоказать тот же момент на часах другого региона.
DateTime::setMicrosecondЗаменить только микросекундную часть, не меняя целые секунды.
DateTime::modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
DateTime::addПрибавить подготовленный календарный интервал или прошедшую длительность.
DateTime::subВычесть подготовленный календарный интервал или прошедшую длительность.
DateTime::diffПолучить календарный интервал, переводящий исходную дату в целевую.
DateTime::__serializeПодготовить внутренний массив даты для механизма serialize.
DateTime::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.
DateTime::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.
DateTime::__set_stateСоздать дату из массива состояния, используемого экспортом var_export.

Готовые строковые константы форматов общие у обоих классов; с PHP 7.2 они объявлены на DateTimeInterface, с 8.4 типизированы string. Конструктор now с PHP 7.1 сохраняет текущие микросекунды; до этого дробь была нулевой. Полные параметры и версии каждого метода доступны по ссылке в таблице.

В примере clone читает объект received и создаёт отдельный объект в reminder с той же датой15 июня. Вызов modify через reminder меняет только эту копию на16 июня; оба последующих format возвращают строки для отдельных echo. Обычное reminder=received не отделило бы состояние.

DateTimeImmutable: состояние и все методы

DateTimeImmutable возвращает новый объект при обычных изменяющих операциях; исходный остаётся прежним. Оба класса реализуют DateTimeInterface и поддерживают сравнение дат. Они не final, но наследнику нужно правильно инициализировать родителя. Для обычной работы наследование не требуется.

Кому принадлежит изменённая дата
  1. $start содержит 15 июня
  2. $other = $start указывает на тот же объект
  3. $next = $other->modify("+1 day")
  4. start остаётся 15-го; next — новый объект 16-го
Увидеть сохранённую дату и результат переносаСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$other = $start;
echo 'Перед переносом: ', $start->format('d.m.Y H:i'), "\n";
$next = $other->modify('+1 day');
echo 'Через start после переноса: ', $start->format('d.m.Y H:i'), "\n";
echo 'Через next после переноса: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перед переносом: 15.06.2024 10:30
Через start после переноса: 15.06.2024 10:30
Через next после переноса: 16.06.2024 10:30

Присваивание other=start сначала обозначает тот же исходный объект двумя именами. Modify у Immutable создаёт новый объект16 июня и возвращает его в next; start/other остаются15 июня. Поэтому исходная встреча доступна для сравнения.

Построить новое время, сохранив исходноеСамостоятельный пример
<?php
declare(strict_types=1);

$received = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
$reminder = $received->modify('+1 day')->setTime(9, 0);
echo $received->format('Y-m-d H:i'), "\n";
echo $reminder->format('Y-m-d H:i'), "\n";
Ожидаемый результат
2024-06-15 10:30
2024-06-16 09:00

Каждый метод возвращает следующий вариант. С PHP 8.5 обычные изменяющие методы Immutable имеют NoDiscard: забытый результат способен вызвать предупреждение. Служебный __unserialize — низкоуровневое исключение из привычной модели редактирования, а не бизнес-API.

МетодДля чего
DateTimeImmutable::createFromFormatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
DateTimeImmutable::formatПолучить строку даты для человека или согласованного внешнего формата.
DateTimeImmutable::modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
DateTimeImmutable::diffПолучить календарный интервал, переводящий исходную дату в целевую.
DateTimeImmutable::__constructСоздать дату из строки и явно выбранного часового пояса.
DateTimeImmutable::createFromTimestampСоздать объект из секунд Unix, сохранив дробную часть до микросекунд.
DateTimeImmutable::createFromMutableПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.
DateTimeImmutable::createFromInterfaceПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.
DateTimeImmutable::getLastErrorsСразу прочитать предупреждения и ошибки последнего разбора даты.
DateTimeImmutable::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.
DateTimeImmutable::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.
DateTimeImmutable::getTimezoneПолучить объект часового пояса, связанный с датой.
DateTimeImmutable::getMicrosecondПолучить целое число микросекунд внутри секунды.
DateTimeImmutable::setDateПоставить конкретный год, месяц и день, сохранив время суток и зону.
DateTimeImmutable::setISODateНайти дату по ISO-году, номеру недели и дню недели.
DateTimeImmutable::setTimeНазначить часы, минуты, секунды и микросекунды на существующую дату.
DateTimeImmutable::setTimestampУстановить точный момент по целым секундам Unix, сохранив зону отображения.
DateTimeImmutable::setTimezoneПоказать тот же момент на часах другого региона.
DateTimeImmutable::setMicrosecondЗаменить только микросекундную часть, не меняя целые секунды.
DateTimeImmutable::addПрибавить подготовленный календарный интервал или прошедшую длительность.
DateTimeImmutable::subВычесть подготовленный календарный интервал или прошедшую длительность.
DateTimeImmutable::__serializeПодготовить внутренний массив даты для механизма serialize.
DateTimeImmutable::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.
DateTimeImmutable::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.
DateTimeImmutable::__set_stateСоздать дату из массива состояния, используемого экспортом var_export.

Готовые строковые константы форматов общие у обоих классов; с PHP 7.2 они объявлены на DateTimeInterface, с 8.4 типизированы string. Конструктор now с PHP 7.1 сохраняет текущие микросекунды; до этого дробь была нулевой. Полные параметры и версии каждого метода доступны по ссылке в таблице.

В цепочке received->modify(...)->setTime(...) первый вызов возвращает новый объект16 июня10:30. Второй вызывается именно у него и возвращает ещё один вариант16 июня09:00. Присваивание сохраняет последний объект в reminder. Received по-прежнему15 июня10:30; если убрать присваивание, рассчитанный результат будет потерян.

DateTimeInterface: принять оба вида дат

Интерфейс нужен, когда функция только читает дату и должна принимать DateTime и DateTimeImmutable. Это общий тип параметра, результата или свойства; он не создаёт объект сам. Пользовательский класс не может напрямую написать implements DateTimeInterface: PHP оставляет реализацию внутренним классам. Наследование от DateTime/Immutable возможно, но не нужно ради одного форматирования.

Одна функция для двух классов
  1. DateTime с 15 июня
  2. DateTimeImmutable с 16 июня
  3. Оба передаются в параметр DateTimeInterface
  4. Общий format возвращает подпись
Написать одну функцию вывода датыСамостоятельный пример
<?php
declare(strict_types=1);

function dateLabel(DateTimeInterface $date): string
{
    return $date->format('d.m.Y');
}
echo dateLabel(new DateTime('2024-06-15 UTC')), "\n";
echo dateLabel(new DateTimeImmutable('2024-06-16 UTC')), "\n";
Ожидаемый результат
15.06.2024
16.06.2024

Функция получает готовый объект и возвращает строку. Тип сообщает, какие объекты допустимы; переменная date локальна функции. Вывод выполняет echo снаружи.

Зафиксировать неизменяемый вариант для своего расчётаСамостоятельный пример
<?php
declare(strict_types=1);

$external = new DateTime('2024-06-15 UTC');
$snapshot = DateTimeImmutable::createFromInterface($external);
$external->modify('+1 day');
echo $snapshot->format('Y-m-d'), "\n";
echo $external->format('Y-m-d'), "\n";
Ожидаемый результат
2024-06-15
2024-06-16

Параметр DateTimeInterface сам по себе не делает полученный DateTime неизменяемым. Отдельная фабрика нужна, когда дальнейшее внешнее изменение не должно менять сохранённое значение.

Общий методЧто читает/обслуживает
DateTimeInterface::formatПолучить строку даты для человека или согласованного внешнего формата.
DateTimeInterface::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.
DateTimeInterface::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.
DateTimeInterface::getTimezoneПолучить объект часового пояса, связанный с датой.
DateTimeInterface::getMicrosecondПолучить целое число микросекунд внутри секунды.
DateTimeInterface::diffПолучить календарный интервал, переводящий исходную дату в целевую.
DateTimeInterface::__serializeПодготовить внутренний массив даты для механизма serialize.
DateTimeInterface::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.
DateTimeInterface::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.

Часть methods с изменением не входит в интерфейс: DateTime и Immutable имеют разные правила возврата/состояния. Константы перечислены здесь. Интерфейс появился в PHP 5.5.

В первом echo выражение new DateTime(...) создаёт аргумент для dateLabel. Внутри функции этот аргумент доступен как локальный date; DateTimeInterface разрешает читать общий format. Метод возвращает строку15.06.2024, return отдаёт её вместо выражения dateLabel(...), затем echo печатает строку. Второй вызов проходит тем же путём с DateTimeImmutable и возвращает16.06.2024. Тип string после двоеточия описывает результат функции, а не исходный объект.

Установка date, база зон и все INI-настройки

Отдельно устанавливать date не требуется: расширение является частью PHP. Часовые правила идут с базой tzdb; обновление PHP или подходящего PECL timezonedb обновляет эти данные. Проверяйте реально работающий SAPI: CLI и веб-сервер могут читать разные php.ini. Зона операционной системы не должна быть неявным договором приложения.

От конфигурации к повторяемому результату
  1. Приложение определяет свою базовую зону
  2. Явный DateTimeZone применяется к конкретной дате
  3. timezone_version_get сообщает версию правил
  4. Изменения правил проверяются на значимых датах
НастройкаЗначение и область
date.timezoneПо умолчанию UTC, INI_ALL. С PHP 8.2 пустая/неверная настройка вызывает предупреждение. Для вызова без явной зоны действует приоритет date_default_timezone_set → date.timezone → UTC.
date.default_latitude31.7667, INI_ALL. Широта: −90 юг … +90 север, экватор 0.
date.default_longitude35.2333, INI_ALL. Долгота: −180 запад … +180 восток.
date.sunrise_zenith90.833333, INI_ALL; до PHP 8.0 было 90.583333. Зенит восхода.
date.sunset_zenith90.833333, INI_ALL; до PHP 8.0 было 90.583333. Зенит заката.
Четыре солнечных параметраИспользуются только устаревшими date_sunrise/date_sunset. Современный date_sun_info получает координаты явно; эти INI не являются его скрытой конфигурацией.

Зенит 90°50′ включает радиус Солнца 16′ и атмосферную рефракцию 34′ в используемой модели. Это расчётная модель, а не измерение погоды/реального горизонта. date_sun_info объясняет полярные условия и формат результата.

Получить предсказуемую подпись из базовой зоныСамостоятельный пример
<?php
$previous = date_default_timezone_get();
date_default_timezone_set('UTC');
try {
    $date = new DateTimeImmutable('2026-10-12 12:00:00');
    echo 'Базовая зона: ', date_default_timezone_get(), "\n";
    echo 'Подпись журнала: ', $date->format('d.m.Y H:i P'), "\n";
} finally {
    date_default_timezone_set($previous);
}
Ожидаемый результат
Базовая зона: UTC
Подпись журнала: 12.10.2026 12:00 +00:00

Get возвращает имя действующей базовой зоны; оно сохранено в previous. Set выбирает известную допустимую UTC. В конструкторе второй аргумент пропущен и в строке нет зоны, поэтому используется эта настройка. Format возвращает подпись12:00 +00:00. Finally восстанавливает окружение после учебного опыта; в приложении базовую зону обычно назначают один раз при запуске.

Явная зона конкретной даты независима от базовойСамостоятельный пример
<?php
declare(strict_types=1);

$previous = date_default_timezone_get();
date_default_timezone_set('UTC');
try {
    $date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('Europe/Moscow'));
    echo $date->format('H:i P'), "\n";
} finally { date_default_timezone_set($previous); }
Ожидаемый результат
10:30 +03:00

Для многопользовательского приложения явный объект зоны удобнее, чем переключение глобального значения для каждого посетителя.

Объяснить, почему изменение php.ini не перебило зону приложенияСамостоятельный пример
<?php
$previousIni = ini_get('date.timezone');
$previousZone = date_default_timezone_get();
try {
    ini_set('date.timezone', 'Asia/Tokyo');
    echo 'Только INI: ', date('Y-m-d H:i e', 1704067200), "\n";
    date_default_timezone_set('UTC');
    echo 'После настройки приложения: ', date('Y-m-d H:i e', 1704067200), "\n";
    ini_set('date.timezone', 'Europe/Paris');
    echo 'INI изменён снова: ', date('Y-m-d H:i e', 1704067200), "\n";
} finally {
    ini_set('date.timezone', $previousIni);
    date_default_timezone_set($previousZone);
}
Ожидаемый результат
Только INI: 2024-01-01 09:00 Asia/Tokyo
После настройки приложения: 2024-01-01 00:00 UTC
INI изменён снова: 2024-01-01 00:00 UTC

Запускайте этот самостоятельный файл в новом процессе: до первой строки date_default_timezone_set ещё не вызывался. Timestamp1704067200 обозначает один известный момент. Сначала берётся INI Asia/Tokyo; после явной настройки приложения действует UTC и очередная замена INI её не перебивает. Finally восстанавливает сохранённые значения. Ini_set здесь меняет текущий процесс, а не редактирует файл php.ini на диске; CLI и веб-сервер имеют собственное окружение.

Все готовые форматы DATE_* и константы интерфейса

DATE_ATOM и DateTimeInterface::ATOM — готовые строки шаблона, а не функции преобразования времени. Они доступны также через DateTime/Immutable. Таблица сохраняет все стандартные имена, включая устаревшие, чтобы можно было читать старые проекты. Шаблон нужно выбирать по договору получателя, а не по самому современному названию.

Константа только подставляет шаблон
  1. DATE_ATOM содержит Y-m-d\TH:i:sP
  2. format применяет его к текущей зоне объекта
  3. Возвращается строка со смещением
  4. Часовой пояс не меняется автоматически
Подготовить строку даты для согласованного APIСамостоятельный пример
<?php
$meeting = new DateTimeImmutable('2024-06-15 10:30:00+03:00');
$text = $meeting->format(DATE_ATOM);
echo 'starts_at=', $text, "\n";
Ожидаемый результат
starts_at=2024-06-15T10:30:00+03:00

DATE_ATOM написана без кавычек и без круглых скобок: это имя уже заданной строки шаблона. PHP подставляет её как аргумент format. Возвращённый text включает T между датой и временем и смещение+03:00; echo показывает готовое поле интеграции. Константа не меняла зону и не отправляла HTTP-запрос.

Имя: DATE_* / DateTimeInterface::*ШаблонПрименение и граница
ATOMY-m-d\TH:i:sPОбмен с явным смещением; совместимая форма ISO/RFC3339/XML Schema.
COOKIEl, d-M-Y H:i:s TИсторическая подпись cookie. Имя шаблона не заменяет актуальный договор HTTP.
ISO8601Y-m-d\TH:i:sOИсторическая ISO-подобная форма; официальный manual рекомендует ATOM или ISO8601_EXPANDED для совместимости со стандартом.
ISO8601_EXPANDEDX-m-d\TH:i:sPРасширенный год всегда со знаком; добавлена в 8.2.
RFC822D, d M y H:i:s OДвузначный год, старый формат RFC 822.
RFC850l, d-M-y H:i:s TПолный день недели и короткий год, исторический RFC 850.
RFC1036D, d M y H:i:s OКак RFC822, исторический формат.
RFC1123D, d M Y H:i:s OЧетырёхзначный год и числовое смещение.
RFC7231D, d M Y H:i:s \G\M\TGMT здесь буквальный текст: сначала UTC. Константы DATE_RFC7231 и интерфейса deprecated в 8.5; введены в 7.0.19/7.1.5.
RFC2822D, d M Y H:i:s OФорма RFC2822/RFC5322 с числовым смещением.
RFC3339Y-m-d\TH:i:sPТа же строка шаблона, что ATOM.
RFC3339_EXTENDEDY-m-d\TH:i:s.vPМиллисекунды v; оставшиеся микросекунды в подпись не попадают.
RSSD, d M Y H:i:s OСовпадает с RFC1123.
W3CY-m-d\TH:i:sPСовпадает с RFC3339/ATOM.
Сравнить именно передаваемые строки и их точностьСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.125456+03:00');
echo 'ATOM: ', $date->format(DATE_ATOM), "\n";
echo 'Interface::ATOM: ', $date->format(DateTimeInterface::ATOM), "\n";
echo 'RFC3339: ', $date->format(DATE_RFC3339), "\n";
echo 'RFC3339_EXTENDED: ', $date->format(DATE_RFC3339_EXTENDED), "\n";
echo 'RSS: ', $date->format(DATE_RSS), "\n";
echo 'RFC1123: ', $date->format(DATE_RFC1123), "\n";
Ожидаемый результат
ATOM: 2024-06-15T10:30:00+03:00
Interface::ATOM: 2024-06-15T10:30:00+03:00
RFC3339: 2024-06-15T10:30:00+03:00
RFC3339_EXTENDED: 2024-06-15T10:30:00.125+03:00
RSS: Sat, 15 Jun 2024 10:30:00 +0300
RFC1123: Sat, 15 Jun 2024 10:30:00 +0300

У трёх первых имён одинаковый шаблон, поэтому совпадает вся строка. RFC3339_EXTENDED оставляет миллисекунды125 из микросекунд125456; остаток456 не передан. RSS иRFC1123 тоже дают одинаковую подпись. Оператор:: читает константу интерфейса; создавать экземпляр интерфейса не нужно. Выбирайте формат и точность по договору получателя.

Подписать расширенный год с обязательным знакомСамостоятельный пример
<?php
declare(strict_types=1);

$date = new DateTimeImmutable('+12024-06-15 10:30:00 UTC');
echo $date->format(DATE_ISO8601_EXPANDED), "\n";
Ожидаемый результат
+12024-06-15T10:30:00+00:00

Расширенная форма нужна, только если внешний формат её допускает. Обычная четырёхзначная схема БД/API может отвергнуть такую дату.

Устаревшие солнечные константыЧто означали
SUNFUNCS_RET_TIMESTAMPint timestamp, режим возврата date_sunrise/date_sunset.
SUNFUNCS_RET_STRINGСтрока часы:минуты, например 08:02.
SUNFUNCS_RET_DOUBLEfloat часов, например 8.75 означает 08:45.

SUNFUNCS_RET_* deprecated с PHP 8.4; функции, которым они служили, deprecated с 8.1. DATE_RFC7231 и DateTimeInterface::RFC7231 deprecated с 8.5; буквальный GMT не переводит дату в UTC. gmdate и DateTimeImmutable::setTimezone помогают сформировать корректную подпись.

Не подписать московские часы буквами GMTСамостоятельный пример
<?php
$meeting = new DateTimeImmutable('2024-06-15 10:30:00+03:00');
$pattern = 'D, d M Y H:i:s \G\M\T';
echo 'Ошибочная подпись: ', $meeting->format($pattern), "\n";
$utc = $meeting->setTimezone(new DateTimeZone('UTC'));
echo 'Подпись после перевода: ', $utc->format($pattern), "\n";
Ожидаемый результат
Ошибочная подпись: Sat, 15 Jun 2024 10:30:00 GMT
Подпись после перевода: Sat, 15 Jun 2024 07:30:00 GMT

Обратные косые черты делают G/M/T буквальным текстом в шаблоне. Format печатает эти буквы даже рядом с часами+03:00 и не выполняет перевод. Только setTimezone(UTC) даёт07:30, которое можно честно подписать GMT. Используется явный шаблон, поэтому deprecated-константа RFC7231 не нужна. Это формирование строки, не отправка заголовка HTTP.

Все обозначения формата ввода даты

Эта таблица относится к createFromFormat/date_parse_from_format. Форматирование вывода описано отдельно. Шаблон читается слева направо, числа считываются жадно до своей максимальной длины: без разделителей раннее поле может забрать цифры следующего. Учитывайте правила переполнения и не считайте удачный объект доказательством корректности даты.

СимволыКак читаются
d, jДень месяца: до двух цифр, с нулём или без. Парсер принимает 00…99 и затем нормализует переполнение.
D, lАнглийский день недели коротко/полностью. Несовпадение с датой может сдвинуть результат к следующему указанному дню.
SАнглийский суффикс st/nd/rd/th: считывается и игнорируется.
zДень года от 0. Сначала должен быть разобран год Y/y; значения сверх длины года переносятся дальше.
F, MАнглийское полное/сокращённое имя месяца. Русское название этим не локализуется.
m, nНомер месяца до двух цифр; переполнение вроде 13 переносит в следующий год.
X, xГод до 19 цифр, возможен знак +/−. PHP 8.2+; фактический диапазон даты всё равно ограничен.
YГод до четырёх цифр. 25 означает 0025, а не 2025.
yДве цифры года, интерпретация в диапазоне 1970…2069.
a, AМаркер am/pm для 12-часового времени.
g, h12-часовое представление часа, до двух цифр. Значения могут переполнить период AM/PM.
G, H24-часовое представление часа; переполнение может перенести дату.
i, sМинуты/секунды с ведущим нулём; значения сверх диапазона нормализуются с диагностикой.
vДо трёх цифр доли секунды как миллисекунд, PHP 7.3+. 12 означает .12 секунды.
uДо шести цифр доли секунды. 45 означает .45 секунды, а не 45 микросекунд.
e, O, p, P, TИдентификатор зоны, UTC-смещение или сокращение. Явное значение в строке определяет зону вместо третьего аргумента.
UUnix timestamp в секундах; не номер года. Задаёт момент относительно UTC.
ПробелНоль или больше пробелов/табуляций; с PHP 8.2.9 также NBSP U+00A0 и NNBSP U+202F.
#Один разделитель из ; : / . , - ( ).
; : / . , - ( )Конкретный указанный разделитель.
?Ровно один произвольный байт. Русская буква UTF-8 обычно занимает два байта.
*Произвольные байты до следующего разделителя или цифры. Это не регулярное выражение.
!В этой позиции сбрасывает все поля в базовые значения: 1970-01-01, 00:00:00.000000 и базовую зону. Значения слева тоже могут быть сброшены; обычно ставят первым.
|Сбрасывает только ещё не разобранные поля. В Y-m-d| часы/минуты/секунды становятся нулевыми.
+Дополнительный хвост вызывает предупреждение вместо ошибки. Для строгой формы предупреждение всё равно нужно отвергнуть.
Обратный слешЭкранирует следующую буквальную букву: \T совпадает с T. Сам PHP тоже обрабатывает строковые кавычки, поэтому учитывайте оба уровня.

Неизвестную буквальную букву нужно экранировать обратным слешем, иначе разбор завершится ошибкой. Поле даты, которое должно точно соответствовать шаблону, проверяют вместе с getLastErrors и обратным форматированием. Зона и ограничения длины входа задаются отдельно.

Все обозначения формата вывода даты

Одна и та же дата может стать подписью встречи, строкой журнала или именем отчёта. Шаблон выбирает поля и порядок. Сначала знакомый случай: d.m.Y H:i превращает 15 июня 2024 в 15.06.2024 10:30. Каждая буква обрабатывается отдельно: m означает месяц, i — минуту; пробелы и знаки остаются на месте. Полный набор обозначений сохранён ниже.

СимволЗначение
d / jДень месяца 01…31 / 1…31 без ведущего нуля.
D / lАнглийский день недели: Mon…Sun / Monday…Sunday. l — маленькая латинская L.
N / wНомер дня недели: ISO 1 понедельник…7 воскресенье / 0 воскресенье…6 суббота.
SАнглийский суффикс st/nd/rd/th для дня; обычно вместе с j.
zПорядковый день года, начиная с 0: 0…365.
WНомер ISO-недели, начинается с понедельника. Для года этой недели используйте o.
F / MАнглийское название месяца полностью / три буквы. format не переводит названия по locale.
m / nНомер месяца 01…12 / 1…12 без ведущего нуля.
tЧисло дней в данном месяце: 28…31.
L1 для високосного года, иначе 0.
oГод ISO-недели: на границе декабря/января может отличаться от календарного Y.
YКалендарный год минимум четырьмя цифрами; отрицательные годы со знаком −.
yПоследние две цифры года. Для внешнего протокола обычно безопаснее полный Y.
XРасширенный год минимум четырьмя цифрами и всегда со знаком +/−; PHP 8.2+.
xГод как Y в обычном диапазоне; расширенная форма со знаком при необходимости, включая + для 10000 и больше; PHP 8.2+.
a / Aam/pm / AM/PM.
BSwatch Internet Time, 000…999. Не обычные часы/минуты.
g / h12-часовой час 1…12 / 01…12.
G / H24-часовой час 0…23 / 00…23.
i / sМинуты/секунды с ведущим нулём, 00…59. i — минуты; m — месяц.
u / vШесть цифр микросекунд / три цифры миллисекунд. date(int timestamp) не хранит дробь; у объекта она может быть.
eИмя/идентификатор часового пояса, например Europe/Moscow или фиксированное смещение.
IБольшая i: 1 при действующем летнем времени, иначе 0.
O / PUTC-смещение без двоеточия / с двоеточием: +0300 / +03:00.
pКак P, но нулевое смещение отображается Z; PHP 8.0+.
TСокращение зоны, если известно; иначе обозначение смещения. Сокращение не является уникальным ID региона.
ZСмещение от UTC в секундах, на западе отрицательное, на востоке положительное. Не буквальная буква Z для UTC.
cОбычная ISO 8601 форма Y-m-dTH:i:sP; для года за 9999 нужен расширенный формат X/x, иначе строка не отвечает обычному формату.
rТекст даты для RFC 2822/RFC 5322 с английским днём/месяцем и смещением.
UЦелая секундная часть Unix timestamp в строковом виде.
Обратный слеш и прочие знакиОбратный слеш делает следующую букву буквальной. Нераспознанные символы выводятся как есть. Учитывайте также экранирование самой PHP-строки.

DateTime::format и DateTimeImmutable::format читают объект; date_format получает его первым аргументом. Для нужной зоны сначала setTimezone, потом format. У date/gmdate целый Unix timestamp, поэтому u/v не восстановят потерянную дробь. Обозначения разбора createFromFormat и вывода похожи, но имеют разные правила неизвестных символов; у IntlDateFormatter и DateInterval вообще другой язык шаблонов.

Подготовить заголовок календаря и число днейСамостоятельный пример
<?php
$month = new DateTimeImmutable('2024-02-01 00:00:00', new DateTimeZone('UTC'));
echo 'Календарь: ', $month->format('m.Y'), "\n";
echo 'Дней для сетки: ', $month->format('t'), "\n";
echo $month->format('L') === '1' ? "Показываем 29 февраля\n" : "Февраль содержит 28 дней\n";
Ожидаемый результат
Календарь: 02.2024
Дней для сетки: 29
Показываем 29 февраля

M с маленькой буквы m возвращает 02, Y —2024, t — строку 29 с числом дней текущего месяца. L возвращает строку 1 для високосного года; сравнение со строкой 1 выбирает подпись о 29 февраля. Этот последний текст пригоден именно для выбранного февраля, а t работает для любого месяца. Здесь подготовлены данные для сетки, сама HTML-таблица не строится.

Составить английскую подпись с буквальным словомСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$label = $date->format('l jS \o\f F Y h:i:s A');
echo 'English event label: ', $label, "\n";
Ожидаемый результат
English event label: Saturday 15th of June 2024 10:30:00 AM

l даётSaturday, jS соединяет 15 иth, F —June, h —10 по 12-часовой шкале, A —AM. Обратные слеши перед o/f делают слово of буквальным. Одинарные кавычки PHP сохраняют эти слеши для обработчика формата. Если нужен буквальный обратный слеш, учитывайте два уровня: чтение строки PHP и чтение шаблона. Имена здесь всегда английские независимо от setlocale; для русского интерфейса используют IntlDateFormatter с его отдельными обозначениями.

Записать расширенный год в согласованном формате архиваСамостоятельный пример
<?php
$date = new DateTimeImmutable('+12024-06-15 10:30:00', new DateTimeZone('UTC'));
$archiveStamp = $date->format('X-m-d\TH:i:sP');
echo 'Расширенная отметка архива: ', $archiveStamp, "\n";
echo 'Обычная дата для сравнения: ', (new DateTimeImmutable('2024-06-15', new DateTimeZone('UTC')))->format('x-m-d'), "\n";
Ожидаемый результат
Расширенная отметка архива: +12024-06-15T10:30:00+00:00
Обычная дата для сравнения: 2024-06-15

PHP8.2+. X всегда печатает знак года; x делает это только когда расширение требуется, поэтому обычный 2024 остаётся без плюса. Буквальная T отделяет дату от времени. Формат c рассчитан на нерасширенный год до 9999; для архива за этой границей нужен отдельный договор с потребителем. Формирование строки не означает, что любой API её примет. DATE_ISO8601 — историческое имя, не гарантия соответствия стандарту; обычно выбирают DATE_ATOM/RFC3339, а для расширенного года — явный шаблон.

Три разных языка даты: ввод по форме, свободный разбор, вывод

В PHP рядом существуют три механизма. createFromFormat читает строку по заданному шаблону. Конструктор/modify/strtotime читают собственную гибкую грамматику дат. format создаёт строку вывода. Одинаковые буквы и похожие названия не означают одинаковые правила. Для формы выбирают однозначный формат, а свободный парсер удобен для правил, которыми управляет сама программа.

Не перепутать направление
  1. Строка 15.06.2024 → createFromFormat("!d.m.Y", …)
  2. Объект даты → modify("next monday")
  3. Объект даты → format("Y-m-d")
  4. Полученная подпись — строка, не новая настройка парсера

Полный алфавит формата ввода; алфавит вывода. Ниже — все семейства свободной грамматики. Обозначения YYYY/MM здесь учебные обозначения полей, не готовые строки createFromFormat: для него нужны Y/m/d.

Сначала один заказ. В форме день записан как 15.06.2024. Метод createFromFormat получает сначала шаблон, затем сам текст и зону; ! сбрасывает неуказанные часы. Полученный объект сохраняем в delivery. Метод modify получает английское правило +1 day и возвращает отдельную дату nextDay. Метод format получает шаблон подписи и возвращает строку для echo. Символы d.m.Y принадлежат шаблону, а слова day — свободной грамматике.

Из строки формы получить дату доставки и подпись напоминанияСамостоятельный пример
<?php
$text = '15.06.2024';
$zone = new DateTimeZone('UTC');
$delivery = DateTimeImmutable::createFromFormat('!d.m.Y', $text, $zone);
$nextDay = $delivery->modify('+1 day');
echo 'Доставка: ', $delivery->format('d.m.Y H:i'), "\n";
echo 'Проверить получение: ', $nextDay->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Доставка: 15.06.2024 00:00
Проверить получение: 16.06.2024 00:00

В этом первом опыте исходная строка известна и корректна; дата доставки не сдвинулась от создания nextDay. В форме со свободным пользовательским вводом понадобятся тип/длина/NUL, false, диагностика и обратное форматирование: полный обработчик разобран в разделе точного ввода. Слова next day нельзя подставлять вместо шаблона d.m.Y.

Базовое правилоСледствие
Диапазоны свободного парсераМесяц 0…12, день 0…31, час 0…24, минута 0…59. Секунда 60 допускается при разборе, но переносится в следующую минуту: leap seconds не моделируются. Грамматика диапазона не гарантирует существование даты.
День/месяц нольДень 0 — предыдущий день перед первым числом; месяц 0 — декабрь прошлого года. Одновременное обнуление нормализуется последовательно.
Задана датаПри построении новой даты неуказанное время сбрасывается в нули. modify изменяет существующий объект: одна календарная дата сохраняет его часы; явное today/tomorrow/midnight сбрасывает их. Задана часть времени — младшие компоненты времени сбрасываются.
Выход за грамматикуstrtotime возвращает false, конструктор бросает исключение; несуществующая дата внутри допустимых диапазонов может нормализоваться с предупреждением.
Сочетание частейВо многих форматах дату, время и зону можно разделять пробелом, запятой или точкой. Это не разрешение любой пунктуации в любом месте.
Регистр в BNFВ официальной записи одинарные кавычки означают нечувствительную к регистру букву, двойные — чувствительную. Квадратные скобки в наших схемах обозначают необязательную часть, а не буквальный ввод.
Увидеть, какую дату действительно построил гибкий парсерСамостоятельный пример
<?php
$zone = new DateTimeZone('UTC');
foreach (['2024-08-00', '2024-00-22', '2024-00-00', '2024-06-15 23:59:60'] as $text) {
    $date = new DateTimeImmutable($text, $zone);
    echo $text, ' -> ', $date->format('Y-m-d H:i:s'), "\n";
}
Ожидаемый результат
2024-08-00 -> 2024-07-31 00:00:00
2024-00-22 -> 2023-12-22 00:00:00
2024-00-00 -> 2023-11-30 00:00:00
2024-06-15 23:59:60 -> 2024-06-16 00:00:00

Августовский день 0 стал последним днём июля; месяц 0 сдвинул год; совместные нули дали 30 ноября. Лишняя секунда перенеслась в следующие сутки. Для расчётного правила это может быть полезно, но заявка на доставку 0 августа не считается подтверждённой только потому, что конструктор вернул объект.

Все семейства записи времени и зоны

Часы hh в 12-часовой записи — 1…12 с возможным ведущим нулём; HH в 24-часовой — две цифры 00…24. Минуты/обычные секунды — две цифры 00…59. Дробь начинается с точки и содержит цифры. am/pm допускают разные регистры и точки. Смещение состоит из необязательного GMT, знака, часов, необязательного двоеточия и минут; для обмена лучше полная явная форма +03:00.

12-часовая подпись
  1. Вход 7:20 pm
  2. pm означает вторую половину суток
  3. Парсер формирует час 19
  4. В 24-часовом выводе получаем 19:20
Перевести вечернее время партнёра в подпись встречиСамостоятельный пример
<?php
$day = new DateTimeImmutable('2024-06-15 00:00:00 UTC');
$meeting = $day->modify('7:20 pm');
echo 'Встреча: ', $meeting->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Встреча: 15.06.2024 19:20

Объект day задаёт известный календарный день. Аргумент 7:20 pm меняет его часы на 19:20, а return метода modify попадает в meeting. Буква H выводит 24-часовое значение; i — минуты. Дата взята из day, поэтому результат не зависит от дня запуска. Полдень —12 pm, полночь —12 am.

СемействоСтруктураПримеры
Час + am/pmhh [пробел] meridian4 am; 5PM
Час и минуты + am/pmhh [.:] MM [пробел] meridian4:08 am; 7.19P.M.
Часы, минуты, секунды + am/pmhh [.:] MM [.:] SS [пробел] meridian4:08:37 am
MS SQL с дробьюhh : MM : SS [.:] цифры meridian4:08:39:12313am
Часы и минуты, 24-часовые[t/T] HH [.:] MM04:08; 19.19; T23:43
Часы и минуты слитно[t/T] HHMM0408; t1919
Часы, минуты, секунды[t/T] HH [.:] MM [.:] SS04.08.37; t19:19:19
То же без разделителей[t/T] HHMMSS040837; T191919
Время с зоной24-часовое время + смещение или имя зоны04:08:37 CEST; T19:19:19-0700
Время с дробью[t/T] HH [.:] MM [.:] SS . цифры19:19:19.532453
Зона самостоятельноСокращение, идентификатор или GMT/числовое смещениеCEST; Europe/Amsterdam; +0430; GMT-06:00

Сокращение зоны — обычно 1…6 латинских букв, иногда в скобках; идентификатор региона может содержать несколько частей с / и _. Регион America/Indiana/Knox хранит исторические правила; числовое смещение и сокращение не заменяют его. Локальное время без даты получает дату из контекста, поэтому учебные опыты используют фиксированную базу.

Привести три записи времени к одной форме журналаСамостоятельный пример
<?php
$base = new DateTimeImmutable('2024-06-15 00:00:00 UTC');
foreach (['7:20 pm', 'T093015', '04:08:37.125456'] as $text) {
    $date = $base->modify($text);
    echo $text, ' -> ', $date->format('Y-m-d H:i:s.u'), " UTC\n";
}
Ожидаемый результат
7:20 pm -> 2024-06-15 19:20:00.000000 UTC
T093015 -> 2024-06-15 09:30:15.000000 UTC
04:08:37.125456 -> 2024-06-15 04:08:37.125456 UTC

foreach передаёт очередной текст в локальную переменную text. Каждая операция начинается с неизменного base, а не с результата прошлой строки. T здесь часть допустимого ввода; в выводе .u запрашивает точку и шесть цифр микросекунд. Так несколько источников получают сопоставимую строку журнала.

Прочитать смещение в конструкторе, а не надеяться на modifyСамостоятельный пример
<?php
$base = new DateTimeImmutable('2024-06-15 00:00:00 UTC');
$modified = $base->modify('19:20:00+03:00');
$partner = new DateTimeImmutable('2024-06-15 19:20:00+03:00');
$utc = $partner->setTimezone(new DateTimeZone('UTC'));
echo 'После modify: ', $modified->format('Y-m-d H:i P'), "\n";
echo 'У партнёра: ', $partner->format('Y-m-d H:i P'), "\n";
echo 'В журнале: ', $utc->format('Y-m-d H:i P'), "\n";
Ожидаемый результат
После modify: 2024-06-15 19:20 +00:00
У партнёра: 2024-06-15 19:20 +03:00
В журнале: 2024-06-15 16:20 +00:00

modify сохранил зону исходного объекта UTC: знак в его строке не перевёл объект в +03:00. Поэтому вход партнёра читаем конструктором целиком вместе с датой и смещением. setTimezone меняет представление уже определённого момента. Ручное добавление трёх часов дало бы иной момент. Числовое смещение подходит для этой состоявшейся встречи; для будущего повторения нужен регион и политика перехода часов.

Не обобщайте опыт modify с19:20+03:00 до всех форм: специальная модификация @timestamp устанавливает Unix-момент и UTC. Для обычного перевода уже существующей даты служит setTimezone. Сохранение неуказанных часов и особый @-случай проверены также по реализации php_date_modify в PHP8.5.11.

Все семейства записи календарной даты

dd — день с одной/двумя цифрами и возможным английским суффиксом st/nd/rd/th; DD — две цифры. mm — месяц с одной/двумя цифрами; MM — две. y — 1…4 цифры года, yy — две, YYYY — четыре. m — английское название месяца полностью, сокращённо или римскими I…XII; M — английское сокращение, включая sep/sept. В текстовых формах допустимые разделители включают пробел, tab, точку, дефис по конкретной строке.

Косая черта задаёт американский порядок
  1. Вход 06/07/2024
  2. Парсер читает месяц 06 и день 07
  3. Дата — 7 июня
  4. Если ожидалось 6 июля, нужен явный d/m/Y
СемействоСтруктураПримеры
Месяц/день по-американскиmm/dd5/12
Месяц/день/год по-американскиmm/dd/y12/22/78; 1/17/2006
Полный год/месяц/деньYYYY/mm/dd2008/6/30
Год и месяц GNUYYYY-mm2008-6; 2008-06
Год-месяц-деньy-mm-dd2008-6-30; 78-12-22; 8-6-21
День, месяц, полный годdd[точка/tab/дефис]mm[точка/дефис]YYYY30-6-2008; 22.12.1978
День, месяц, короткий годdd[точка/tab]mm.yy30.6.08; 22.12.78 — приоритеты описаны ниже
День, текст месяца, годdd + разделители + m + разделители + y30-June 2008; 22DEC78; 14 III 1879
Текст месяца и полный годm + разделители + YYYY; день становится 1June 2008; DEC1978
Полный год и текст месяцаYYYY + разделители + m; день становится 12008 June; 1978-XII
Текст месяца, день, годm + dd + y с допустимыми пробелами/пунктуацией/суффиксомJuly 1st, 2008; April 17, 1790
Текст месяца и деньm + dd, год из базового контекстаJuly 1st,; Apr 17
День и текст месяцаdd + m1 July; 17 Apr
Сокращение месяца-день-годM-DD-yMay-09-78; Apr-17-1790
Год-сокращение месяца-деньy-M-DD78-Dec-22; 1814-MAY-17
Только год, четыре цифрыYYYY; надёжно после уже распознанного времени1978; 2008 — могут стать часами/минутами
Расширенный год со знаком+ или −, затем 5…19 цифр-81120; +20192
Только текст месяцаmMarch; jun; DEC
Восемь цифр ISOYYYYMMDD20240615
ISO с косыми чертамиYYYY/MM/DD, по две цифры месяца/дня2024/06/15
ISO с коротким годомyy-MM-DD24-06-15; 78-12-22
ISO с полным годом и необязательным знаком[+/-]YYYY-MM-DD-0002-07-26; +1978-04-17
ISO с расширенным годом и обязательным знаком+/- затем 5…19 цифр-MM-DD+20192-04-17; -81120-02-26
Неочевидная границаЧто происходит
Короткие годы y/yy0…69 переводятся в 2000…2069; 70…99 — в 1970…1999. Полный год с ведущими нулями имеет иной смысл.
dd.mm.yyДля 00…60 временная грамматика имеет приоритет; документированная календарная форма надёжна для 61…99. Не угадывайте пользовательское намерение.
Только четыре цифрыЕсли они подходят HHMM, парсер может прочитать время. Для одного года используйте createFromFormat("!Y", …).
0 и 31 против 32День 0/31 может нормализоваться; 32 уже вне свободной грамматики. createFromFormat имеет собственные более широкие правила считывания/переполнения.
Год 0Используется астрономическая нумерация пролептического григорианского календаря: год 0 соответствует 1 до н. э.; год −1 — 2 до н. э. Это не исторический местный переход календаря.
Не отправить заказ на месяц раньше из-за косых чертСамостоятельный пример
<?php
$text = '06/07/2024';
$zone = new DateTimeZone('UTC');
$free = new DateTimeImmutable($text, $zone);
$agreed = DateTimeImmutable::createFromFormat('!d/m/Y', $text, $zone);
echo 'Свободное чтение: ', $free->format('d.m.Y'), "\n";
echo 'Доставка по договору день/месяц/год: ', $agreed->format('d.m.Y'), "\n";
Ожидаемый результат
Свободное чтение: 07.06.2024
Доставка по договору день/месяц/год: 06.07.2024

Обе даты существуют. Отсутствие предупреждения не обнаружит расхождение намерений. Во втором вызове d читает 06 как день, m —07 как месяц. Для настоящей формы проверяют диагностику и точное обратное представление; даже строгая проверка не выберет договор за автора формы.

Отличить короткий год от полногоСамостоятельный пример
<?php
declare(strict_types=1);

foreach (['69-01-01', '70-01-01', '0069-01-01'] as $text) {
    echo (new DateTimeImmutable($text, new DateTimeZone('UTC')))->format('Y-m-d'), "\n";
}
Ожидаемый результат
2069-01-01
1970-01-01
0069-01-01

Сокращённое представление содержит скрытое правило века. Для долговременных документов и API предпочтителен полный год.

Определить, какие поля короткой даты получены из базыСамостоятельный пример
<?php
$base = new DateTimeImmutable('2024-06-15 10:30:45 UTC');
foreach (['July 1', 'June 2024', '09:20', '2008'] as $text) {
    $date = $base->modify($text);
    echo $text, ' -> ', $date->format('Y-m-d H:i:s'), "\n";
}
$year = DateTimeImmutable::createFromFormat('!Y', '2008', new DateTimeZone('UTC'));
echo 'Год отчёта по !Y: ', $year->format('Y-m-d H:i:s'), "\n";
Ожидаемый результат
July 1 -> 2024-07-01 10:30:45
June 2024 -> 2024-06-01 10:30:45
09:20 -> 2024-06-15 09:20:00
2008 -> 2024-06-15 20:08:00
Год отчёта по !Y: 2008-01-01 00:00:00

July 1 берёт год из base; June 2024 явно устанавливает первое число. modify сохраняет исходные часы 10:30:45 при таком изменении только даты, в отличие от построения новой даты конструктором. 09:20 оставляет дату, но сбрасывает секунды. Четыре цифры 2008 подходят часам 20:08; для поля «год отчёта» задан однозначный шаблон !Y. При создании без фиксированного base недостающие компоненты могли бы зависеть от текущего дня.

Составные форматы: журналы, API, недели и timestamp

В журналах и интеграциях дата часто приходит в готовом составном формате. В таблице II обозначает минуты, SS секунды, ww — ISO-неделю 01…53, ddd — день года 001…366. Название стандарта описывает форму, но не доказывает, что гибкий парсер проверил все ограничения внешнего протокола.

EXIF → объект → подпись
  1. 2024:06:15 10:30:00 из метаданных
  2. Парсер распознаёт EXIF-структуру
  3. Зона задана отдельно, если источник её не передал
  4. format создаёт согласованную подпись
Подписать фотографию, если известна зона камерыСамостоятельный пример
<?php
$cameraText = '2024:06:15 10:30:00';
$cameraZone = new DateTimeZone('Asia/Tokyo');
$shot = new DateTimeImmutable($cameraText, $cameraZone);
$viewer = $shot->setTimezone(new DateTimeZone('Europe/Moscow'));
echo 'Камера: ', $shot->format('d.m.Y H:i e'), "\n";
echo 'Подпись посетителю: ', $viewer->format('d.m.Y H:i e'), "\n";
Ожидаемый результат
Камера: 15.06.2024 10:30 Asia/Tokyo
Подпись посетителю: 15.06.2024 04:30 Europe/Moscow

В EXIF-строке здесь нет смещения. Asia/Tokyo — отдельно известная настройка камеры, а не вывод парсера. Без неё нельзя достоверно вычислить местное время посетителя. Конструктор вернул момент, setTimezone — его другое представление, format — готовую подпись.

ИсточникСтруктураПример
Common Log Formatdd/M/YYYY:HH:II:SS смещение10/Oct/2000:13:55:36 -0700
EXIFYYYY:MM:DD HH:II:SS2024:06:15 10:30:00
ISO-год и неделяYYYY[-]Www2024W24; 2024-W24
ISO-год, неделя, деньYYYY[-]Www[-]d; d 0…7 по грамматике2024W246; 2024-W24-6
MySQLYYYY-MM-DD HH:II:SS2024-06-15 10:30:00
PostgreSQL, порядковый день годаYYYY[.]ddd; ddd 001…3662024.167; 2024167
SOAPYYYY-MM-DDTHH:II:SS.дробь[смещение]2024-06-15T10:30:00.125+03:00
Unix timestamp@[-]целые секунды@0; @-1
Unix timestamp с долей@[-]секунды . от 0 до 6 цифр@123.125456
XMLRPCYYYYMMDDThh:II:SS20240615T9:30:00
XMLRPC компактныйYYYYMMDD[t/T]hhIISS20240615t093000
WDDXYYYY-m-dTh:i:s, допускает короткие компоненты2024-6-15T9:3:7

Готовые стандартные формы ATOM, COOKIE, ISO8601-like, RFC 822/850/1036/1123/2822/3339/3339 Extended/7231, RSS и W3C также поддерживаются; все шаблоны перечислены в таблице констант. ISO W чувствительна к регистру; T в SOAP, обычном XMLRPC и WDDX — тоже. Компактная XMLRPC допускает t/T. @timestamp всегда задаёт UTC; timestamp в этой грамматике — секунды, не миллисекунды.

Согласовать даты четырёх источниковСамостоятельный пример
<?php
foreach (['2024:06:15 10:30:00', '2024-W24-6', '2024.167', '20240615T9:30:00'] as $text) {
    $date = new DateTimeImmutable($text, new DateTimeZone('UTC'));
    echo $text, ' -> ', $date->format('Y-m-d H:i:s'), "\n";
}
Ожидаемый результат
2024:06:15 10:30:00 -> 2024-06-15 10:30:00
2024-W24-6 -> 2024-06-15 00:00:00
2024.167 -> 2024-06-15 00:00:00
20240615T9:30:00 -> 2024-06-15 09:30:00

Во всех строках дата одна: 15 июня. ISO-неделя 24/день6 и порядковый день167 високосного года не передавали время, поэтому получилась полночь. Источники EXIF/XMLRPC передали и часы. Совпадение календарного дня не означает совпадения момента; таблица ниже сохраняет все допустимые семейства.

Прочитать дробные секунды и подписать событие местным временемСамостоятельный пример
<?php
$event = new DateTimeImmutable('@1704067200.125456', new DateTimeZone('Asia/Tokyo'));
$local = $event->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo 'Момент события: ', $event->format('Y-m-d H:i:s.u P'), "\n";
echo 'Токийский журнал: ', $local->format('Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
Момент события: 2024-01-01 00:00:00.125456 +00:00
Токийский журнал: 2024-01-01 09:00:00.125456 +09:00

@ задаёт секунды от Unix epoch и UTC; аргумент Asia/Tokyo в конструкторе не меняет это правило. Только отдельный setTimezone создаёт местную подпись. Дробь .125456 сохранена; значение в миллисекундах нельзя просто поставить после @ как целые секунды.

Все относительные слова, единицы и их порядок

База для конструктора/strtotime без заданного timestamp — текущее системное время; для modify — объект. dayname допускает sunday…saturday и sun…sat. ordinal: first…twelfth, next, last, previous, this. reltext: next/last/previous/this. Число целое со знаком; десятичную длительность нельзя надёжно записать как +1.5 hours.

Порядок слов способен сбросить время
  1. База: 2024-06-15 10:30
  2. tomorrow 11:00 → завтра в 11:00
  3. 11:00 tomorrow → tomorrow сбрасывает время
  4. Получается завтра в 00:00
Назначить напоминание завтра на определённый часСамостоятельный пример
<?php
$created = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
$tomorrow = $created->modify('tomorrow');
$reminder = $tomorrow->setTime(11, 0);
echo 'После tomorrow: ', $tomorrow->format('d.m.Y H:i'), "\n";
echo 'Напоминание: ', $reminder->format('d.m.Y H:i'), "\n";
Ожидаемый результат
После tomorrow: 16.06.2024 00:00
Напоминание: 16.06.2024 11:00

tomorrow изменяет день и сбрасывает время на полночь. setTime отдельно получает 11 часов и 0 минут, возвращает готовую дату напоминания. Так намерение видно в двух действиях. Если соединять слова в одну строку, порядок может менять результат, как показывает следующий пример.

КонструкцияЗначение
yesterdayВчера, время сброшено в 00:00.
midnight / today00:00 текущей/базовой даты.
nowОтносительно базы ничего не меняет; в конструкторе база — текущий момент.
noon12:00.
tomorrowСледующий день, время сброшено в 00:00.
back of N15 минут после указанного часа: back of 7pm → 19:15.
front of N15 минут до указанного часа: front of 5am → 04:45.
first day of …Первый день указанного/текущего по контексту месяца.
last day of …Последний день указанного/текущего месяца.
ordinal dayname of …Нужное по счёту вхождение дня недели в месяце: first monday of July 2024.
last dayname of …Последнее вхождение дня недели в месяце.
number unit / number weekОтносительное целое количество: +5 weeks, -7 weekdays, 12 day.
ordinal/reltext unitТекстовый номер/направление: fifth day, second month, previous year.
agoМеняет знак всех ранее найденных относительных частей; позиции частей важны.
daynameВыбирает этот/ближайший подходящий день недели; при совпадении текущего дня может не сдвинуть дату.
dayname last/this/next weekДень недели конкретной соседней недели: Monday next week.

Единицы: ms/msec/millisecond(s), µs/µsec/microsecond(s)/usec(s), sec/second(s), min/minute(s), hour(s), day(s), week/weeks, fortnight(s) и историческое forthnight(s), month(s), year(s), weekday/weekdays. fortnight — две недели. last/previous означают −1, next +1, this нулевой сдвиг; номер словами задаёт соответствующее количество. weekday не знает праздников вашей организации.

Обычно относительные части применяются после абсолютных: +1 week July 2024 и July 2024 +1 week эквивалентны. Исключения yesterday/midnight/today/noon/tomorrow напрямую меняют время и чувствительны к порядку. ago меняет знаки уже накопленных относительных частей. С PHP 7.0.8 неделя начинается с понедельника. Комбинации знаков вроде +-2 запрещены в 8.2–8.3, снова принимаются с 8.4; в собственных правилах лучше один явный знак.

Проверить два похожих текста с разным результатомСамостоятельный пример
<?php
declare(strict_types=1);

$base = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
echo $base->modify('tomorrow 11:00')->format('Y-m-d H:i'), "\n";
echo $base->modify('11:00 tomorrow')->format('Y-m-d H:i'), "\n";
Ожидаемый результат
2024-06-16 11:00
2024-06-16 00:00

Правило не нужно угадывать: зафиксируйте типичные и граничные даты тестами. Для задания времени можно отдельно применить setTime, чтобы смысл был явным.

Знакомые слова не всегда означают целый часСамостоятельный пример
<?php
declare(strict_types=1);

$base = new DateTimeImmutable('2024-06-15 UTC');
echo $base->modify('back of 7pm')->format('H:i'), "\n";
echo $base->modify('front of 5am')->format('H:i'), "\n";
echo $base->modify('+1 hour +30 minutes')->format('H:i'), "\n";
Ожидаемый результат
19:15
04:45
01:30

Полтора часа заданы двумя целыми компонентами. Не используйте десятичную запись с точкой: точка может стать разделителем отдельных частей.

Получить конец следующего месяца без переполнения дняСамостоятельный пример
<?php
declare(strict_types=1);

$base = new DateTimeImmutable('2024-01-31 10:30:00 UTC');
echo $base->modify('+1 month')->format('Y-m-d'), "\n";
echo $base->modify('last day of next month')->format('Y-m-d'), "\n";
Ожидаемый результат
2024-03-02
2024-02-29

Високосный февраль имеет 29 дней; простое прибавление месяца сохраняет номер дня и нормализует избыток. Правило конца месяца прямо отражает задачу.

Слово weekday исключает субботу и воскресенье, но не праздник вашего склада. Когда правило зависит от собственного календаря, строка парсера лишь даёт кандидата; программа должна проверить этот кандидат по данным организации.

Перенести отправку через выходные и праздник складаСамостоятельный пример
<?php
function nextDispatch(DateTimeImmutable $received, array $closedDays): DateTimeImmutable
{
    $candidate = $received->modify('+1 weekday')->setTime(9, 0);
    while (in_array($candidate->format('Y-m-d'), $closedDays, true)) {
        $candidate = $candidate->modify('+1 weekday');
    }
    return $candidate;
}
$received = new DateTimeImmutable('2024-06-14 16:30:00 UTC');
$dispatch = nextDispatch($received, ['2024-06-17']);
echo 'Получено: ', $received->format('d.m.Y H:i'), "\n";
echo 'Отправка: ', $dispatch->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Получено: 14.06.2024 16:30
Отправка: 18.06.2024 09:00

Вызов передаёт пятничную дату в локальный received, массив закрытых дней —в closedDays. +1 weekday даёт понедельник17 июня; setTime назначает09:00. in_array с true сравнивает строку даты точно; while видит праздник17 июня и передвигает кандидата на вторник18 июня. return отдаёт объект в выражение nextDispatch(...), присваивание сохраняет его в dispatch; echo использует format. Функция рассчитана на конечный явный список дат и обычную рабочую неделю; смены по субботам требуют другого правила. Для региональных09:00 отдельно учитывают переходы часов.

Почему first monday и first monday of дают разные даты

При совпадении дня недели важна форма записи. Один dayname и number dayname не обязаны сдвигать дату. ordinal dayname сдвигает к следующему вхождению. Слово of сначала устанавливает первое число месяца и меняет смысл на порядковый день недели внутри месяца.

of привязывает правило к месяцу
  1. July 2024 → 1 июля, понедельник
  2. first monday July 2024 → следующий понедельник 8 июля
  3. first monday of July 2024 → первое вхождение месяца
  4. Получаем 1 июля
Форма при совпадающем днеПравило
daynameОстаётся на этом дне.
number daynameНе сдвигает к следующему дню автоматически при совпадении.
number week daynameСначала число недель, затем выбор дня; это два блока.
ordinal daynameПереходит к следующему вхождению.
number week ordinal daynameСначала недели, затем дополнительное продвижение к ordinal dayname.
ordinal dayname ofСбрасывает день месяца на 1 и считает вхождения внутри месяца.
last dayname ofПоследний такой день именно месяца.
last dayname без ofПредыдущий такой день относительно базы, может оказаться в предыдущем месяце.
Сопоставить правила на месяце, который начинается в понедельникСамостоятельный пример
<?php
declare(strict_types=1);

foreach (['first monday July 2024', 'first monday of July 2024',
    'last monday July 2024', 'last monday of July 2024'] as $text) {
    echo (new DateTimeImmutable($text, new DateTimeZone('UTC')))->format('Y-m-d'), "\n";
}
Ожидаемый результат
2024-07-08
2024-07-01
2024-06-24
2024-07-29

Такая граница встречается в расписаниях платежей и плановых работ. Одно короткое слово меняет опорный календарь правила.

Увидеть дополнительные продвижения по неделямСамостоятельный пример
<?php
declare(strict_types=1);

foreach (['Monday July 1 2024', '1 monday July 1 2024',
    '+1 week monday July 1 2024', 'first monday July 1 2024',
    '+1 week first monday July 1 2024'] as $text) {
    echo (new DateTimeImmutable($text, new DateTimeZone('UTC')))->format('Y-m-d'), "\n";
}
Ожидаемый результат
2024-07-01
2024-07-01
2024-07-08
2024-07-08
2024-07-15

Каждый вход независим. Разница между числом и порядковым словом видна на базовом понедельнике; на другой дате некоторые результаты могли бы случайно совпасть.

Получить первое и последнее обслуживание месяцаСамостоятельный пример
<?php
function serviceDay(string $rule): string
{
    $date = new DateTimeImmutable($rule, new DateTimeZone('UTC'));
    return $date->setTime(9, 0)->format('d.m.Y H:i');
}
echo 'Первый выезд: ', serviceDay('first monday of July 2024'), "\n";
echo 'Последний выезд: ', serviceDay('last monday of July 2024'), "\n";
Ожидаемый результат
Первый выезд: 01.07.2024 09:00
Последний выезд: 29.07.2024 09:00

Каждый вызов помещает одну известную программную строку в локальный rule. of задаёт отсчёт внутри июля. Сначала построен объект date, затем setTime вернул вариант09:00, format —строку, return —эту строку обратно в echo. Функция намеренно принимает внутренние правила; для пользовательского выбора месяца нужен свой ограниченный договор, а не произвольный английский текст.

Календарное правило и прошедшая длительность

Напоминание «завтра в 09:00» связано с местным календарём. Таймер «истекает через 24 часа» связан с прошедшим временем. Эти задачи совпадают в обычный день и расходятся при переводе часов. Поэтому сперва записывают смысл правила словами, затем выбирают API.

Общий стартНезависимое правилоРезультат
2024-11-03 00:00 America/New_YorkПрибавить PT24H: ровно 86400 секунд2024-11-03 23:00, после осеннего перевода часов.
2024-11-03 00:00 America/New_YorkПрибавить P1D: следующий календарный день2024-11-04 00:00; прошло 25 часов.

Две строки — альтернативные вычисления от одной исходной даты. Второй интервал не прибавляется к результату первого.

ПравилоЧто выбирать
Прошедшие часы/минуты/секундыDateInterval("PT24H") и add/sub; явно учитывать единицы и требуемую точность.
Следующий календарный деньDateInterval("P1D") либо текстовое +1 day. В день DST длина суток может отличаться от 86400 секунд.
Компоненты времени из текстаmodify("+24 hours") и DateInterval::createFromDateString("24 hours") меняют компоненты часов. Это не то же самое, что конструктор PT24H на переходе зоны.
Последний день следующего месяцаmodify("last day of next month"); простой +1 month не ограничивает день концом месяца. Перенос той же строки в createFromDateString и add не сохраняет специальное правило последнего дня; сравните оба результата.
Разность двух локальных датdiff; интервал может иметь нетривиальные h/d при переходе DST. Для прошедшего времени сначала UTC.
Рабочие дни и праздникиНужны правила организации/региона. next weekday знает будни, но не ваш производственный календарь.

Сложение и вычитание месяцев не гарантируют взаимного восстановления: нормализация может потерять исходное намерение. Храните правило подписки («последний день месяца», «15-е число»), а не пытайтесь восстановить его из полученного timestamp. Сравните DateTimeImmutable::modify, DateTimeImmutable::add, DateTimeImmutable::diff.

Один текст «24 часа» ещё не определяет API. Следующий опыт начинается с одной осенней полуночи и сохраняет каждый результат независимо. В DateInterval запись PT24H задаёт прошедшие часы; текст +24 hours у modify изменяет компоненты местных часов.

Выбрать срок по смыслу обещания клиентуСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-11-03 00:00:00', new DateTimeZone('America/New_York'));
$timer = $start->add(new DateInterval('PT24H'));
$localHours = $start->modify('+24 hours');
$tomorrow = $start->add(new DateInterval('P1D'));
echo 'Через 24 прошедших часа: ', $timer->format('d.m.Y H:i P'), "\n";
echo 'Плюс 24 местных часа: ', $localHours->format('d.m.Y H:i P'), "\n";
echo 'Следующий календарный день: ', $tomorrow->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Через 24 прошедших часа: 03.11.2024 23:00 -05:00
Плюс 24 местных часа: 04.11.2024 00:00 -05:00
Следующий календарный день: 04.11.2024 00:00 -05:00

Осенний день содержит 25 прошедших часов. Поэтому суточный таймер заканчивается в 23:00 того же местного дня, а календарное обещание «в полночь завтра» — на час позже. Ни один результат не заменяет start, вычисления не влияют друг на друга. Для createFromDateString("24 hours") также действуют компоненты местного времени: совпадение слов с PT24H не делает конструкции взаимозаменяемыми.

Интервал, дата и расписание: три разных объекта

Подписка продлевается на месяц, резерв держится три прошедших часа, а отчёт появляется каждую неделю. Это разные правила: интервал хранит шаг, дата — момент/календарную отметку, период — последовательность применения шага. Аналогия «инструкция для календаря» не делает месяц фиксированным мешком секунд.

Что хранитьПодход
Конкретную датуDateTimeImmutable / DateTime
Один шагDateInterval
Повторяемую последовательность датDatePeriod
Прошедшие секунды между моментамиРазность timestamps с учётом требуемой точности; не %s интервала
Сначала полезный результат
  1. Счёт от 10 апреля, срок P5D
  2. Создаём объект даты и шаг
  3. add возвращает 15 апреля
  4. Выводим дату, не изменяя исходный счёт

Дата счёта хранит 10 апреля, а шаг —пять дней. P начинает запись периода,5 означает количество, D —дни. New возвращает объект в переменную; issued->add(step) применяет правило к дате и возвращает новую дату в due. Format(Y-m-d) выбирает год, месяц и день для подписи.

Назвать клиенту последний день оплаты счётаСамостоятельный пример
<?php
$issued = new DateTimeImmutable('2026-04-10T00:00:00Z');
$step = new DateInterval('P5D');
$due = $issued->add($step);
echo 'Счёт выставлен: ', $issued->format('d.m.Y'), "\n";
echo 'По правилу пяти календарных дней оплатить до: ', $due->format('d.m.Y'), "\n";
Ожидаемый результат
Счёт выставлен: 10.04.2026
По правилу пяти календарных дней оплатить до: 15.04.2026

Вызов add получил весь объект step одним аргументом. Он создал 15 апреля, вернул его в due, а echo использовал строку из due->format. Исходный issued сохранил 10 апреля. Здесь показан календарный день срока; если нужно считать оплату просроченной по точной минуте, отдельно задают момент и правило включения границы.

Далее: DateInterval::__construct, DateInterval::createFromDateString, DatePeriod::__construct.

Почему три часа и три часа календаря могут различаться

В Париже осенью часы переводят назад. Прибавить три прошедших часа и увеличить показание часов на три — разные требования. У конструктора временная часть PT3H означает прошедшее время; у createFromDateString("3 hours") меняются компоненты местного времени. Дата-компоненты P1D/1 day изменяют календарь одинаково.

Исходная точка 27 октября 2024 01:30 +02:00Результат после применения
PT3H (прошедшее время)03:30 +01:00: прошло10800 секунд.
3 hours (местные компоненты)04:30 +01:00: прошло14400 секунд.
Три прошедших часа
  1. 01:30 при смещении +02:00
  2. После перехода смещение становится +01:00
  3. PT3H продвигает момент на10800 секунд
  4. Локальные часы показывают03:30
Подписать два разных срока на осеннем переходеСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-10-27 01:30:00', new DateTimeZone('Europe/Paris'));
$hold = $start->add(new DateInterval('PT3H'));
$localTask = $start->add(DateInterval::createFromDateString('3 hours'));
echo 'Резерв на три прошедших часа до: ', $hold->format('d.m.Y H:i P'), "\n";
echo 'Длительность резерва: ', $hold->getTimestamp() - $start->getTimestamp(), ' секунд', "\n";
echo 'Правило трёх местных часов до: ', $localTask->format('d.m.Y H:i P'), "\n";
echo 'Прошедшее время второго правила: ', $localTask->getTimestamp() - $start->getTimestamp(), ' секунд', "\n";
Ожидаемый результат
Резерв на три прошедших часа до: 27.10.2024 03:30 +01:00
Длительность резерва: 10800 секунд
Правило трёх местных часов до: 27.10.2024 04:30 +01:00
Прошедшее время второго правила: 14400 секунд

Обе операции начинают с одного start. Конструктор PT3H продвинул момент на 10800 секунд. Текстовое 3 hours изменило местные компоненты с 01:30 до 04:30 и захватило повторившийся осенний час:прошло 14400 секунд. Утверждение «интервал равен трём часам» недостаточно без выбранного вида правила. Расписание, зона и прошедшая длительность должны соответствовать обещанию сервиса.

Для ежедневного события в09:00 выбирают календарное правило и зону. Для резервирования на фиксированное прошедшее время — договор длительности. Упражнение: поменяйте момент на день вне перехода и предскажите, когда результаты совпадут. Предлагаемое решение: сравнить обе полученные даты и разности timestamps, сохраняя явно заданную зону. Полный разбор DateTimeImmutable::add и DateTimeImmutable::diff.

Часовой пояс: как одна встреча становится 13:00 и 21:00

На сайте записана встреча в 12:00 UTC. Посетитель в Берлине должен увидеть 13:00 зимой, посетитель в Токио — 21:00. Сама встреча одна. DateTimeZone представляет правила отображения часов, а DateTimeImmutable хранит момент и выбранное представление. Начнём с двух переменных и одного действия; таблицы переходов понадобятся только после этого.

Момент один, часы на экранах разные
  1. $meeting: 2024-01-15 12:00 UTC
  2. $berlin: правила Europe/Berlin → 13:00
  3. $tokyo: правила Asia/Tokyo → 21:00

Знак $ начинает имя переменной; = сохраняет результат справа. new DateTimeZone(...) создаёт настроенный объект. ->setTimezone(...) получает новый объект даты, а ->format("H:i") превращает его в строку часов и минут. В примере каждый результат setTimezone сохранён в отдельную переменную, затем у него вызван format. В шаблоне d.m.Y —день, месяц и год; H:i —часы и минуты; P —смещение с двоеточием.

Показать одну встречу в двух регионахСамостоятельный пример
<?php
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
$berlin = new DateTimeZone('Europe/Berlin');
$tokyo = new DateTimeZone('Asia/Tokyo');
$berlinView = $meeting->setTimezone($berlin);
$tokyoView = $meeting->setTimezone($tokyo);
echo 'Берлин: ', $berlinView->format('d.m.Y H:i P'), "\n";
echo 'Токио: ', $tokyoView->format('d.m.Y H:i P'), "\n";
echo 'Исходный момент: ', $meeting->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Берлин: 15.01.2024 13:00 +01:00
Токио: 15.01.2024 21:00 +09:00
Исходный момент: 15.01.2024 12:00 +00:00

Z обозначает UTC. Объекты berlin и tokyo хранят правила регионов. Каждый setTimezone возвращает отдельное представление известного момента, сохранённое в berlinView/tokyoView. Format превращает эти даты в строки для двух подписей. Исходный meeting сохранил своё UTC-представление; встреча не переносилась на другое время.

Что лежит в данныхЧто это означает
2024-01-15T12:00:00ZКонкретный момент. Подходит, например, для времени уже созданного заказа.
Europe/BerlinРегиональная система правил по датам. Нужна для расписания филиала и местного показа.
+01:00Фиксированное смещение. Само по себе не знает регион и летний переход.
CETСокращение. Не используем как надёжный выбор региона клиента.

Если синтаксис объекта пока незнаком, шаги разобраны в классах и объектах. Дальше: DateTimeZone::__construct показывает три вида поясов, а DateTimeZone::listIdentifiers помогает построить выбор для профиля. Глобальная настройка date_default_timezone_set задаёт значение по умолчанию PHP, но не находит автоматически местоположение браузера.

Класс DateTimeZone: методы, флаги и состояние

Класс доступен с PHP 5.2 и входит в расширение date. Он описывает пояс, а не календарную дату. У регионального объекта есть доступ к правилам смещения, переходам и справочной географии. Набор правил берётся из базы среды; объект не выполняет сетевой запрос за прогнозом политики государства.

Два уровня доступа
  1. new DateTimeZone("Europe/Berlin") → отдельный объект правил
  2. getName/getOffset/getTransitions/getLocation → сведения об этом объекте
  3. DateTimeZone::listIdentifiers/listAbbreviations → общие каталоги класса
МетодЗадача
DateTimeZone::__constructСоздание из региона, смещения или сокращения.
DateTimeZone::getNameПрочитать имя/описание.
DateTimeZone::getOffsetСмещение на конкретный момент.
DateTimeZone::getTransitionsНачальное состояние и переходы за период.
DateTimeZone::getLocationСправочная география региона.
DateTimeZone::listIdentifiersДопустимые идентификаторы для выбора.
DateTimeZone::listAbbreviationsИсторические варианты сокращений.

Константа — именованное значение, а не вызов. Поэтому после DateTimeZone::EUROPE нет скобок. Все 14 констант класса целочисленные; с PHP 8.4 тип int указан в объявлении. Региональные флаги соединяются оператором |. Их числа не надо запоминать или сохранять вместо регионального имени.

КонстантаНазначение
DateTimeZone::AFRICAАфрика
DateTimeZone::AMERICAАмерика
DateTimeZone::ANTARCTICAАнтарктида
DateTimeZone::ARCTICАрктика
DateTimeZone::ASIAАзия
DateTimeZone::ATLANTICАтлантика
DateTimeZone::AUSTRALIAАвстралия
DateTimeZone::EUROPEЕвропа
DateTimeZone::INDIANИндийский океан
DateTimeZone::PACIFICТихий океан
DateTimeZone::UTCГруппа UTC.
DateTimeZone::ALLВсе неустаревшие идентификаторы; значение по умолчанию.
DateTimeZone::ALL_WITH_BCТакже совместимые устаревшие имена; для миграции, не нового выбора.
DateTimeZone::PER_COUNTRYПо стране: второй аргумент обязателен, например JP. Это отдельный режим.

Объект обычно создаётся один раз для конкретной операции и используется для нескольких дат. Это удобство повторного использования, а не обещание определённого ускорения. Если обрабатываете много строк, измеряйте свой объём; не запрашивайте всю историю мира для каждого заказа.

Дополнительные специальные методы восстановления существуют в исходнике PHP, хотя не имеют собственных страниц в текущем дереве класса. Они разобраны отдельно после обычных операций.

Настройка профиля: регион вместо текущего смещения

В профиле магазина пользователю нужен понятный выбор страны и региона. Сохраните строку вроде Europe/Berlin. Текущее +01:00 — результат вычисления на конкретную дату, а не долгосрочная настройка для будущих заказов. Смещение из браузера не позволяет однозначно выбрать город.

Выбор сохраняет намерение
  1. Пользователь выбирает Asia/Tokyo
  2. Сервер проверяет по своему listIdentifiers()
  3. В БД сохраняется строка Asia/Tokyo
  4. При показе заказа DateTimeZone применяет правила к моменту
Небольшая серверная политика для двух офисовСамостоятельный пример
<?php
declare(strict_types=1);

$allowed = ['Europe/Berlin', 'Asia/Tokyo'];
$submitted = 'Asia/Tokyo';
if (!in_array($submitted, $allowed, true)) {
    echo "Выберите наш офис\n";
} else {
    $zone = new DateTimeZone($submitted);
    $orderTime = new DateTimeImmutable('2024-01-15T12:00:00Z');
    echo $orderTime->setTimezone($zone)->format('Y-m-d H:i P'), "\n";
}
Ожидаемый результат
2024-01-15 21:00 +09:00

Список здесь намеренно ограничен офисами сервиса. Если требуется весь мир, берите список текущего сервера. На входе HTTP сначала проверьте, что поле строковое; в самостоятельном примере уже задана строка.

Для нового расписания «каждый день в 09:00 по филиалу» храните местное время и регион, а для уже произошедшего события — однозначный момент. Отдельно согласуйте поведение в пропущенный весенний час и повторённый осенний час. Просто создание DateTimeZone не выбирает эту бизнес-политику.

Нужные функции: timezone_identifiers_list, DateTimeZone::getOffset, timezone_version_get. Безопасный приём формы разобран в работе с формами.

Регион ещё не отвечает на вопрос, что делать с местным временем, которое пропущено или повторяется при переводе часов. Следующие примеры продолжают выбор филиала: сначала один несуществующий час, затем два разных момента с одинаковыми показаниями часов.

Берлин весной: заявка на отсутствующие часы
  1. Форма:31.03.2024 02:30
  2. Правила Europe/Berlin пропускают час 02:xx
  3. Парсер создаёт 03:30; сравнение с вводом обнаруживает изменение
  4. Подтверждения записи нет; нужен новый выбор
Отказаться от записи на час, которого нет в календаре филиалаСамостоятельный пример
<?php
$input = '2024-03-31 02:30';
$zone = new DateTimeZone('Europe/Berlin');
$candidate = DateTimeImmutable::createFromFormat('!Y-m-d H:i', $input, $zone);
$errors = DateTimeImmutable::getLastErrors();
if ($candidate === false) { echo "Не удалось прочитать дату\n"; }
else {
    echo 'Введено: ', $input, "\n";
    echo 'Построено: ', $candidate->format('Y-m-d H:i P'), "\n";
    if (($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))
        || $candidate->format('Y-m-d H:i') !== $input) {
        echo "Запись не подтверждена: выберите существующие местные часы\n";
    } else { echo "Время записи подтверждено\n"; }
}
Ожидаемый результат
Введено: 2024-03-31 02:30
Построено: 2024-03-31 03:30 +02:00
Запись не подтверждена: выберите существующие местные часы

Шаблон!Y-m-d H:i читает год/месяц/день/часы/минуты; ! сбрасывает остальные поля. Parser может нормализовать пропущенные 02:30 до 03:30 без календарного предупреждения, поэтому одного getLastErrors недостаточно. Обратное format тем же шаблоном выявляет изменение строки. В примере политика —отказ, а не молчаливый перенос на час. Для полного HTTP-поля дополнительно проверяют строковый тип, размер и NUL до парсера.

Показать два разных варианта осенних 02:30Самостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Berlin');
$first = new DateTimeImmutable('2024-10-27T02:30:00+02:00');
$second = new DateTimeImmutable('2024-10-27T02:30:00+01:00');
foreach (['Первое вхождение' => $first, 'Второе вхождение' => $second] as $label => $instant) {
    echo $label, ': ', $instant->setTimezone($zone)->format('d.m.Y H:i P'), '; ',
        $instant->setTimezone(new DateTimeZone('UTC'))->format('H:i'), " UTC\n";
}
echo 'Расстояние между вариантами: ', intdiv($second->getTimestamp() - $first->getTimestamp(), 60), " минут\n";
Ожидаемый результат
Первое вхождение: 27.10.2024 02:30 +02:00; 00:30 UTC
Второе вхождение: 27.10.2024 02:30 +01:00; 01:30 UTC
Расстояние между вариантами: 60 минут

Одинаковые 27 октября 02:30 не определяют единственный момент. Здесь оба ввода содержат явные разные смещения; проверка через регион показывает допустимые варианты, UTC-подписи различаются на час. Приложение должно заранее согласовать выбор первого/второго вхождения или спросить человека. Одна обратная проверка строки Y-m-d H:i из прошлого примера не различит их; сохраняйте выбранный однозначный момент вместе с нужным региональным намерением.

Восстановление DateTimeZone: технический протокол, не формат API

Этот шаг нужен при сопровождении внутреннего PHP-хранилища или чтении var_export(), когда обычные настройки уже понятны. Для профиля достаточно имени. Сериализованная строка может запускать протоколы объектов; данные из браузера и сторонних API не следует направлять в unserialize().

Простая настройка и внутренний снимок
  1. Для профиля: getName() → строка → new DateTimeZone
  2. Для доверенного внутреннего снимка: serialize → __serialize
  3. Восстановление: unserialize → __unserialize, правила из текущей tzdb
МетодКогда встречается
DateTimeZone::__serializePHP 8.2+: массив состояния для serialize().
DateTimeZone::__unserializePHP 8.2+: применение состояния к объекту.
DateTimeZone::__wakeupСтарый протокол; конкретный метод deprecated с PHP 8.5.
DateTimeZone::__set_stateВосстановление из PHP-экспорта var_export().

Это дополнительные записи по официальным stub/C-исходникам: не выдаём их за несуществующие отдельные страницы manual. При переносе между версиями тестируйте формат и состояние. Ни один такой снимок не вшивает всю базу tzdb в объект.

Полный каталог поясов: как читать эти имена

Ниже сохранены все имена из одиннадцати списков PHP.net, а не выбранные города. Снимок от 2026-10-13, заявленная версия базы 2026.5. Каталог работает без сетевого запроса. В этой проверочной сборке timezone_version_get() сообщает 2026.3: статический справочник и установленная среда могут обновляться в разные дни.

От справочника к работающей настройке
  1. Найти регион в каталоге
  2. Проверить имя через listIdentifiers() своего PHP
  3. Сохранить всю строку
  4. Применить к конкретной дате

Для поля выбора источник истины — DateTimeZone::listIdentifiers на сервере. PHP.net предупреждает: поведение имён вне поддерживаемого списка не определено. Не превращайте этот статический снимок в вечный список разрешений приложения; обновляйте среду и проверяйте совместимость.

Списки ниже сохраняют английские идентификаторы без перевода и изменения подчёркиваний. Это технические ключи, а русские названия рядом — объяснение для читателя. Раздел «Другие» содержит совместимость и особое предупреждение про Etc/GMT.

Африка: все поддерживаемые идентификаторы

Для филиала в Найроби выберите Africa/Nairobi. Общий континент не определяет единое смещение: внутри списка разные исторические и действующие правила.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AFRICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Africa/Abidjan
  • Africa/Accra
  • Africa/Addis_Ababa
  • Africa/Algiers
  • Africa/Asmara
  • Africa/Bamako
  • Africa/Bangui
  • Africa/Banjul
  • Africa/Bissau
  • Africa/Blantyre
  • Africa/Brazzaville
  • Africa/Bujumbura
  • Africa/Cairo
  • Africa/Casablanca
  • Africa/Ceuta
  • Africa/Conakry
  • Africa/Dakar
  • Africa/Dar_es_Salaam
  • Africa/Djibouti
  • Africa/Douala
  • Africa/El_Aaiun
  • Africa/Freetown
  • Africa/Gaborone
  • Africa/Harare
  • Africa/Johannesburg
  • Africa/Juba
  • Africa/Kampala
  • Africa/Khartoum
  • Africa/Kigali
  • Africa/Kinshasa
  • Africa/Lagos
  • Africa/Libreville
  • Africa/Lome
  • Africa/Luanda
  • Africa/Lubumbashi
  • Africa/Lusaka
  • Africa/Malabo
  • Africa/Maputo
  • Africa/Maseru
  • Africa/Mbabane
  • Africa/Mogadishu
  • Africa/Monrovia
  • Africa/Nairobi
  • Africa/Ndjamena
  • Africa/Niamey
  • Africa/Nouakchott
  • Africa/Ouagadougou
  • Africa/Porto-Novo
  • Africa/Sao_Tome
  • Africa/Tripoli
  • Africa/Tunis
  • Africa/Windhoek

Официальный список Африка; 52 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Америка: все поддерживаемые идентификаторы

Северная и Южная Америка входят в одну группу. В имени America/Argentina/Buenos_Aires три части; сохраните всю строку, не только последнее название города.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AMERICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • America/Adak
  • America/Anchorage
  • America/Anguilla
  • America/Antigua
  • America/Araguaina
  • America/Argentina/Buenos_Aires
  • America/Argentina/Catamarca
  • America/Argentina/Cordoba
  • America/Argentina/Jujuy
  • America/Argentina/La_Rioja
  • America/Argentina/Mendoza
  • America/Argentina/Rio_Gallegos
  • America/Argentina/Salta
  • America/Argentina/San_Juan
  • America/Argentina/San_Luis
  • America/Argentina/Tucuman
  • America/Argentina/Ushuaia
  • America/Aruba
  • America/Asuncion
  • America/Atikokan
  • America/Bahia
  • America/Bahia_Banderas
  • America/Barbados
  • America/Belem
  • America/Belize
  • America/Blanc-Sablon
  • America/Boa_Vista
  • America/Bogota
  • America/Boise
  • America/Cambridge_Bay
  • America/Campo_Grande
  • America/Cancun
  • America/Caracas
  • America/Cayenne
  • America/Cayman
  • America/Chicago
  • America/Chihuahua
  • America/Ciudad_Juarez
  • America/Costa_Rica
  • America/Coyhaique
  • America/Creston
  • America/Cuiaba
  • America/Curacao
  • America/Danmarkshavn
  • America/Dawson
  • America/Dawson_Creek
  • America/Denver
  • America/Detroit
  • America/Dominica
  • America/Edmonton
  • America/Eirunepe
  • America/El_Salvador
  • America/Fort_Nelson
  • America/Fortaleza
  • America/Glace_Bay
  • America/Goose_Bay
  • America/Grand_Turk
  • America/Grenada
  • America/Guadeloupe
  • America/Guatemala
  • America/Guayaquil
  • America/Guyana
  • America/Halifax
  • America/Havana
  • America/Hermosillo
  • America/Indiana/Indianapolis
  • America/Indiana/Knox
  • America/Indiana/Marengo
  • America/Indiana/Petersburg
  • America/Indiana/Tell_City
  • America/Indiana/Vevay
  • America/Indiana/Vincennes
  • America/Indiana/Winamac
  • America/Inuvik
  • America/Iqaluit
  • America/Jamaica
  • America/Juneau
  • America/Kentucky/Louisville
  • America/Kentucky/Monticello
  • America/Kralendijk
  • America/La_Paz
  • America/Lima
  • America/Los_Angeles
  • America/Lower_Princes
  • America/Maceio
  • America/Managua
  • America/Manaus
  • America/Marigot
  • America/Martinique
  • America/Matamoros
  • America/Mazatlan
  • America/Menominee
  • America/Merida
  • America/Metlakatla
  • America/Mexico_City
  • America/Miquelon
  • America/Moncton
  • America/Monterrey
  • America/Montevideo
  • America/Montserrat
  • America/Nassau
  • America/New_York
  • America/Nome
  • America/Noronha
  • America/North_Dakota/Beulah
  • America/North_Dakota/Center
  • America/North_Dakota/New_Salem
  • America/Nuuk
  • America/Ojinaga
  • America/Panama
  • America/Paramaribo
  • America/Phoenix
  • America/Port-au-Prince
  • America/Port_of_Spain
  • America/Porto_Velho
  • America/Puerto_Rico
  • America/Punta_Arenas
  • America/Rankin_Inlet
  • America/Recife
  • America/Regina
  • America/Resolute
  • America/Rio_Branco
  • America/Santarem
  • America/Santiago
  • America/Santo_Domingo
  • America/Sao_Paulo
  • America/Scoresbysund
  • America/Sitka
  • America/St_Barthelemy
  • America/St_Johns
  • America/St_Kitts
  • America/St_Lucia
  • America/St_Thomas
  • America/St_Vincent
  • America/Swift_Current
  • America/Tegucigalpa
  • America/Thule
  • America/Tijuana
  • America/Toronto
  • America/Tortola
  • America/Vancouver
  • America/Whitehorse
  • America/Winnipeg
  • America/Yakutat

Официальный список Америка; 144 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Антарктида: все поддерживаемые идентификаторы

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

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ANTARCTICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Antarctica/Casey
  • Antarctica/Davis
  • Antarctica/DumontDUrville
  • Antarctica/Macquarie
  • Antarctica/Mawson
  • Antarctica/McMurdo
  • Antarctica/Palmer
  • Antarctica/Rothera
  • Antarctica/Syowa
  • Antarctica/Troll
  • Antarctica/Vostok

Официальный список Антарктида; 11 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Арктика: все поддерживаемые идентификаторы

Каталог содержит Arctic/Longyearbyen. Наличие одного пункта в этой группе не позволяет распространить его правила на всю географическую Арктику.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ARCTIC). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Arctic/Longyearbyen

Официальный список Арктика; 1 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Азия: все поддерживаемые идентификаторы

Не все смещения кратны часу: Asia/Kathmandu использует +05:45 в показанных современных датах. Берите правило из базы по нужной дате, а не округляйте до целых часов.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ASIA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Asia/Aden
  • Asia/Almaty
  • Asia/Amman
  • Asia/Anadyr
  • Asia/Aqtau
  • Asia/Aqtobe
  • Asia/Ashgabat
  • Asia/Atyrau
  • Asia/Baghdad
  • Asia/Bahrain
  • Asia/Baku
  • Asia/Bangkok
  • Asia/Barnaul
  • Asia/Beirut
  • Asia/Bishkek
  • Asia/Brunei
  • Asia/Chita
  • Asia/Colombo
  • Asia/Damascus
  • Asia/Dhaka
  • Asia/Dili
  • Asia/Dubai
  • Asia/Dushanbe
  • Asia/Famagusta
  • Asia/Gaza
  • Asia/Hebron
  • Asia/Ho_Chi_Minh
  • Asia/Hong_Kong
  • Asia/Hovd
  • Asia/Irkutsk
  • Asia/Jakarta
  • Asia/Jayapura
  • Asia/Jerusalem
  • Asia/Kabul
  • Asia/Kamchatka
  • Asia/Karachi
  • Asia/Kathmandu
  • Asia/Khandyga
  • Asia/Kolkata
  • Asia/Krasnoyarsk
  • Asia/Kuala_Lumpur
  • Asia/Kuching
  • Asia/Kuwait
  • Asia/Macau
  • Asia/Magadan
  • Asia/Makassar
  • Asia/Manila
  • Asia/Muscat
  • Asia/Nicosia
  • Asia/Novokuznetsk
  • Asia/Novosibirsk
  • Asia/Omsk
  • Asia/Oral
  • Asia/Phnom_Penh
  • Asia/Pontianak
  • Asia/Pyongyang
  • Asia/Qatar
  • Asia/Qostanay
  • Asia/Qyzylorda
  • Asia/Riyadh
  • Asia/Sakhalin
  • Asia/Samarkand
  • Asia/Seoul
  • Asia/Shanghai
  • Asia/Singapore
  • Asia/Srednekolymsk
  • Asia/Taipei
  • Asia/Tashkent
  • Asia/Tbilisi
  • Asia/Tehran
  • Asia/Thimphu
  • Asia/Tokyo
  • Asia/Tomsk
  • Asia/Ulaanbaatar
  • Asia/Urumqi
  • Asia/Ust-Nera
  • Asia/Vientiane
  • Asia/Vladivostok
  • Asia/Yakutsk
  • Asia/Yangon
  • Asia/Yekaterinburg
  • Asia/Yerevan

Официальный список Азия; 82 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Атлантика: все поддерживаемые идентификаторы

Atlantic/Azores и Atlantic/Cape_Verde — самостоятельные идентификаторы. Группа — способ найти настройку, а не общая формула времени для всех островов.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ATLANTIC). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Atlantic/Azores
  • Atlantic/Bermuda
  • Atlantic/Canary
  • Atlantic/Cape_Verde
  • Atlantic/Faroe
  • Atlantic/Madeira
  • Atlantic/Reykjavik
  • Atlantic/South_Georgia
  • Atlantic/St_Helena
  • Atlantic/Stanley

Официальный список Атлантика; 10 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Австралия: все поддерживаемые идентификаторы

Разные штаты и острова следуют разным правилам. У Australia/Lord_Howe сезонное изменение в примере составляет полчаса, поэтому флаг DST не означает универсальное +3600.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AUSTRALIA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Australia/Adelaide
  • Australia/Brisbane
  • Australia/Broken_Hill
  • Australia/Darwin
  • Australia/Eucla
  • Australia/Hobart
  • Australia/Lindeman
  • Australia/Lord_Howe
  • Australia/Melbourne
  • Australia/Perth
  • Australia/Sydney

Официальный список Австралия; 11 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Европа: все поддерживаемые идентификаторы

Выбирайте регион филиала, например Europe/Berlin. Текущее совпадение часов с соседним городом не обещает одинаковую историю или будущие правила.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::EUROPE). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Europe/Amsterdam
  • Europe/Andorra
  • Europe/Astrakhan
  • Europe/Athens
  • Europe/Belgrade
  • Europe/Berlin
  • Europe/Bratislava
  • Europe/Brussels
  • Europe/Bucharest
  • Europe/Budapest
  • Europe/Busingen
  • Europe/Chisinau
  • Europe/Copenhagen
  • Europe/Dublin
  • Europe/Gibraltar
  • Europe/Guernsey
  • Europe/Helsinki
  • Europe/Isle_of_Man
  • Europe/Istanbul
  • Europe/Jersey
  • Europe/Kaliningrad
  • Europe/Kirov
  • Europe/Kyiv
  • Europe/Lisbon
  • Europe/Ljubljana
  • Europe/London
  • Europe/Luxembourg
  • Europe/Madrid
  • Europe/Malta
  • Europe/Mariehamn
  • Europe/Minsk
  • Europe/Monaco
  • Europe/Moscow
  • Europe/Oslo
  • Europe/Paris
  • Europe/Podgorica
  • Europe/Prague
  • Europe/Riga
  • Europe/Rome
  • Europe/Samara
  • Europe/San_Marino
  • Europe/Sarajevo
  • Europe/Saratov
  • Europe/Simferopol
  • Europe/Skopje
  • Europe/Sofia
  • Europe/Stockholm
  • Europe/Tallinn
  • Europe/Tirane
  • Europe/Ulyanovsk
  • Europe/Vaduz
  • Europe/Vatican
  • Europe/Vienna
  • Europe/Vilnius
  • Europe/Volgograd
  • Europe/Warsaw
  • Europe/Zagreb
  • Europe/Zurich

Официальный список Европа; 58 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Индийский океан: все поддерживаемые идентификаторы

Indian/Maldives — строка идентификатора, а не ISO-код государства. Для поиска вариантов по стране предназначен отдельный режим PER_COUNTRY.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::INDIAN). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Indian/Antananarivo
  • Indian/Chagos
  • Indian/Christmas
  • Indian/Cocos
  • Indian/Comoro
  • Indian/Kerguelen
  • Indian/Mahe
  • Indian/Maldives
  • Indian/Mauritius
  • Indian/Mayotte
  • Indian/Reunion

Официальный список Индийский океан; 11 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Тихий океан: все поддерживаемые идентификаторы

Для встреч между Pacific/Auckland и America/New_York сравнивайте один момент, отображённый в двух поясах. Календарная дата на двух экранах тоже может отличаться.

Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::PACIFIC). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.

  • Pacific/Apia
  • Pacific/Auckland
  • Pacific/Bougainville
  • Pacific/Chatham
  • Pacific/Chuuk
  • Pacific/Easter
  • Pacific/Efate
  • Pacific/Fakaofo
  • Pacific/Fiji
  • Pacific/Funafuti
  • Pacific/Galapagos
  • Pacific/Gambier
  • Pacific/Guadalcanal
  • Pacific/Guam
  • Pacific/Honolulu
  • Pacific/Kanton
  • Pacific/Kiritimati
  • Pacific/Kosrae
  • Pacific/Kwajalein
  • Pacific/Majuro
  • Pacific/Marquesas
  • Pacific/Midway
  • Pacific/Nauru
  • Pacific/Niue
  • Pacific/Norfolk
  • Pacific/Noumea
  • Pacific/Pago_Pago
  • Pacific/Palau
  • Pacific/Pitcairn
  • Pacific/Pohnpei
  • Pacific/Port_Moresby
  • Pacific/Rarotonga
  • Pacific/Saipan
  • Pacific/Tahiti
  • Pacific/Tarawa
  • Pacific/Tongatapu
  • Pacific/Wake
  • Pacific/Wallis

Официальный список Тихий океан; 38 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Другие и совместимые имена: все поддерживаемые идентификаторы

Этот список нужен для чтения наследованных настроек. PHP.net рекомендует не использовать его имена в новом коде, кроме UTC: совместимость может измениться, а некоторые названия ведут себя неожиданно.

Etc/GMT использует обратный знак
  1. Обычное +08:00: восемь часов восточнее UTC
  2. Etc/GMT-8 означает ту же положительную поправку
  3. Для местных правил выбираем Asia/Shanghai или Australia/Perth
Увидеть обратный знак Etc/GMT вместе с календарной датойСамостоятельный пример
<?php
$instant = new DateTimeImmutable('2024-01-15T00:00:00Z');
foreach (['Etc/GMT-8', '+08:00', 'Etc/GMT+8'] as $name) {
    $local = $instant->setTimezone(new DateTimeZone($name));
    echo $name, ': ', $local->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Etc/GMT-8: 15.01.2024 08:00 +08:00
+08:00: 15.01.2024 08:00 +08:00
Etc/GMT+8: 14.01.2024 16:00 -08:00

Совместимые Etc/GMT-имена используют обратную POSIX-конвенцию знака. Поэтому Etc/GMT+8 показывает предыдущий день 14 января, что терялось бы в одном H:i. Для нового фиксированного смещения запись+08:00 яснее; для региональных правил выбирают полный IANA ID. Все старые имена каталога сохранены для чтения существующих настроек.

  • Africa/Asmera
  • Africa/Timbuktu
  • America/Argentina/ComodRivadavia
  • America/Atka
  • America/Buenos_Aires
  • America/Catamarca
  • America/Coral_Harbour
  • America/Cordoba
  • America/Ensenada
  • America/Fort_Wayne
  • America/Godthab
  • America/Indianapolis
  • America/Jujuy
  • America/Knox_IN
  • America/Louisville
  • America/Mendoza
  • America/Montreal
  • America/Nipigon
  • America/Pangnirtung
  • America/Porto_Acre
  • America/Rainy_River
  • America/Rosario
  • America/Santa_Isabel
  • America/Shiprock
  • America/Thunder_Bay
  • America/Virgin
  • America/Yellowknife
  • Antarctica/South_Pole
  • Asia/Ashkhabad
  • Asia/Calcutta
  • Asia/Choibalsan
  • Asia/Chongqing
  • Asia/Chungking
  • Asia/Dacca
  • Asia/Harbin
  • Asia/Istanbul
  • Asia/Kashgar
  • Asia/Katmandu
  • Asia/Macao
  • Asia/Rangoon
  • Asia/Saigon
  • Asia/Tel_Aviv
  • Asia/Thimbu
  • Asia/Ujung_Pandang
  • Asia/Ulan_Bator
  • Atlantic/Faeroe
  • Atlantic/Jan_Mayen
  • Australia/ACT
  • Australia/Canberra
  • Australia/Currie
  • Australia/LHI
  • Australia/North
  • Australia/NSW
  • Australia/Queensland
  • Australia/South
  • Australia/Tasmania
  • Australia/Victoria
  • Australia/West
  • Australia/Yancowinna
  • Brazil/Acre
  • Brazil/DeNoronha
  • Brazil/East
  • Brazil/West
  • Canada/Atlantic
  • Canada/Central
  • Canada/Eastern
  • Canada/Mountain
  • Canada/Newfoundland
  • Canada/Pacific
  • Canada/Saskatchewan
  • Canada/Yukon
  • CET
  • Chile/Continental
  • Chile/EasterIsland
  • CST6CDT
  • Cuba
  • EET
  • Egypt
  • Eire
  • EST
  • EST5EDT
  • Etc/GMT
  • Etc/GMT+0
  • Etc/GMT+1
  • Etc/GMT+10
  • Etc/GMT+11
  • Etc/GMT+12
  • Etc/GMT+2
  • Etc/GMT+3
  • Etc/GMT+4
  • Etc/GMT+5
  • Etc/GMT+6
  • Etc/GMT+7
  • Etc/GMT+8
  • Etc/GMT+9
  • Etc/GMT-0
  • Etc/GMT-1
  • Etc/GMT-10
  • Etc/GMT-11
  • Etc/GMT-12
  • Etc/GMT-13
  • Etc/GMT-14
  • Etc/GMT-2
  • Etc/GMT-3
  • Etc/GMT-4
  • Etc/GMT-5
  • Etc/GMT-6
  • Etc/GMT-7
  • Etc/GMT-8
  • Etc/GMT-9
  • Etc/GMT0
  • Etc/Greenwich
  • Etc/UCT
  • Etc/Universal
  • Etc/UTC
  • Etc/Zulu
  • Europe/Belfast
  • Europe/Kiev
  • Europe/Nicosia
  • Europe/Tiraspol
  • Europe/Uzhgorod
  • Europe/Zaporozhye
  • Factory
  • GB
  • GB-Eire
  • GMT
  • GMT+0
  • GMT-0
  • GMT0
  • Greenwich
  • Hongkong
  • HST
  • Iceland
  • Iran
  • Israel
  • Jamaica
  • Japan
  • Kwajalein
  • Libya
  • MET
  • Mexico/BajaNorte
  • Mexico/BajaSur
  • Mexico/General
  • MST
  • MST7MDT
  • Navajo
  • NZ
  • NZ-CHAT
  • Pacific/Enderbury
  • Pacific/Johnston
  • Pacific/Ponape
  • Pacific/Samoa
  • Pacific/Truk
  • Pacific/Yap
  • Poland
  • Portugal
  • PRC
  • PST8PDT
  • ROC
  • ROK
  • Singapore
  • Turkey
  • UCT
  • Universal
  • US/Alaska
  • US/Aleutian
  • US/Arizona
  • US/Central
  • US/East-Indiana
  • US/Eastern
  • US/Hawaii
  • US/Indiana-Starke
  • US/Michigan
  • US/Mountain
  • US/Pacific
  • US/Samoa
  • UTC
  • W-SU
  • WET
  • Zulu

Официальный список Другие и совместимые имена; 180 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.

Практика: диагностика времени заказа без угадывания региона

Задача: заказ записан как 2024-07-15T12:00:00Z, филиал — Europe/Berlin. Получите местную строку, имя выбранного региона и его смещение. Затем замените регион на Asia/Kathmandu. До запуска предскажите, почему вариант «прибавить целое число часов» неверен для второй настройки.

Разбор одной записи заказа
  1. Исходный момент с Z → однозначная дата
  2. Настройка филиала → проверенный регион
  3. setTimezone → местная строка; getOffset → диагностика
  4. Сохраняем момент исходным, подпись создаём для читателя
Возможное решение: две региональные подписиСамостоятельный пример
<?php
declare(strict_types=1);

$event = new DateTimeImmutable('2024-07-15T12:00:00Z');
foreach (['Europe/Berlin', 'Asia/Kathmandu'] as $name) {
    $zone = new DateTimeZone($name);
    echo $zone->getName(), ': ';
    echo $event->setTimezone($zone)->format('Y-m-d H:i P');
    echo ' / ', $zone->getOffset($event), " секунд\n";
}
Ожидаемый результат
Europe/Berlin: 2024-07-15 14:00 +02:00 / 7200 секунд
Asia/Kathmandu: 2024-07-15 17:45 +05:45 / 20700 секунд

Регион и момент явные: результат не зависит от глобального default timezone. При диагностике добавьте версию tzdb. Не заменяйте реальную дату заказа сегодняшней датой: правила могли быть другими.

Следующий шаг — DateTimeZone::getTransitions для разбора смены правил и сценарии календаря. Для системного справочника всех функций этой ветки используйте карточки ниже.

Две ветви ошибок даты: аргументы и состояние

С PHP 8.3 расширение date использует специальные классы. DateException наследует Exception; DateError наследует Error. Оба относятся к Throwable, но catch(Exception) не ловит DateError. Ошибку формы можно превратить в понятное сообщение, а сломанный объект требует исправления программы.

Где возникла проблема
  1. Неразбираемая строка → DateMalformedStringException
  2. Неправильная зона → DateInvalidTimeZoneException
  3. Не вызван родительский конструктор → DateObjectError
  4. Выход за диапазон → DateRangeError

try заключает действие, которое может не завершиться обычным возвратом. Если конструктор бросил исключение, присваивание даты и последующий echo внутри try не выполнятся. PHP ищет подходящий catch; переменная error в его скобках получает объект причины. После законченного catch выполнение продолжается за всей конструкцией.

Не подтвердить доставку после неудачного чтения датыСамостоятельный пример
<?php
$text = 'definitely-not-a-date';
try {
    $delivery = new DateTimeImmutable($text, new DateTimeZone('UTC'));
    echo 'Подтверждена доставка: ', $delivery->format('d.m.Y'), "\n";
} catch (DateMalformedStringException $error) {
    echo "Дата не прочитана; доставка не подтверждена\n";
}
echo "Можно исправить поле даты\n";
Ожидаемый результат
Дата не прочитана; доставка не подтверждена
Можно исправить поле даты

Неразбираемый текст не дал объекта delivery. Catch превратил конкретную проблему ввода в понятное следующее действие. Это не означает, что любой неправильный календарный день бросит исключение: 30 февраля способен нормализоваться. Отдельный строгий пример ниже проверяет именно календарную дату.

КлассРодитель
DateErrorError
DateObjectErrorDateError
DateRangeErrorDateError
DateExceptionException
DateInvalidOperationExceptionDateException
DateInvalidTimeZoneExceptionDateException
DateMalformedIntervalStringExceptionDateException
DateMalformedPeriodStringExceptionDateException
DateMalformedStringExceptionDateException

Не все сбои date превращены в DateException: неправильный PHP-тип может дать TypeError, NULL-байт в некоторых API — ValueError, процедурные аналоги могут сохранить warning/false. Проверяйте договор конкретной функции. Как работают try/catch.

DateError: когда возникает и что делать

Сбой внутренней базы часовых поясов или родитель ошибок объекта/диапазона. Повреждение базы не имитируем изменением файлов PHP. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Error.

DateError в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateError
  4. catch подходящего типа выбирает обработку
Сохранить заявку без выдуманного времени при сбое календарного сервисаСамостоятельный пример
<?php
$orderId = 'BR41';
$status = 'ожидает расчёта';
try {
    throw new DateError('Учебная имитация недоступной базы зон');
    $status = 'время подтверждено';
} catch (DateError $error) {
    echo 'Заявка ', $orderId, ': ', $status, "\n";
    echo 'Техническая причина: ', $error->getMessage(), "\n";
}
echo "Время доставки пока не назначено\n";
Ожидаемый результат
Заявка BR41: ожидает расчёта
Техническая причина: Учебная имитация недоступной базы зон
Время доставки пока не назначено

Только здесь DateError создан вручную: мы проверяем обработку отказа, не повреждаем установленную tzdb. Throw прерывает try до второго присваивания status. Статус заказа остаётся ожидающим расчёта; произвольная UTC-дата не объявляется подтверждённой. getMessage возвращает заданную строку для технического журнала, а не готовый текст публичной страницы.

Различить ветви Exception и Error по выполненному обработчикуСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
try {
    $date = $date->setMicrosecond(1000000);
} catch (Exception $error) {
    echo "Обработчик Exception\n";
} catch (Error $error) {
    echo "Обработчик Error: время не изменено\n";
}
echo 'Сохранённая встреча: ', $date->format('Y-m-d H:i:s.u'), " UTC\n";
Ожидаемый результат
Обработчик Error: время не изменено
Сохранённая встреча: 2024-06-15 10:30:00.000000 UTC

Здесь реальный setMicrosecond бросил DateRangeError, потомка DateError и Error. Первый catch(Exception) его пропустил; сработал Error. Неудавшееся присваивание оставило исходную встречу. Method доступен с PHP8.4; это опыт диапазона микросекунд на64-битной среде, а не переполнения32-битного timestamp. Catch(Throwable) мог бы поймать обе ветви, но сам по себе не объясняет способ исправления.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыError::__construct, Error::getMessage, Error::getPrevious, Error::getCode, Error::getFile, Error::getLine, Error::getTrace, Error::getTraceAsString, Error::__toString, Error::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateObjectError: когда возникает и что делать

Объект внутреннего класса даты не инициализирован. Типичный случай: подкласс переопределил конструктор и не вызвал родительский. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Error.

DateObjectError в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateObjectError
  4. catch подходящего типа выбирает обработку
Исправить подкласс, который забыл создать внутреннюю датуСамостоятельный пример
<?php
class BrokenDate extends DateTimeImmutable
{
    public function __construct() {}
}
try {
    echo (new BrokenDate())->format('d.m.Y H:i');
} catch (DateObjectError $error) {
    echo "Встреча не создана: родитель не инициализирован\n";
}
class BookingDate extends DateTimeImmutable
{
    public function __construct(string $text)
    {
        parent::__construct($text, new DateTimeZone('UTC'));
    }
}
$meeting = new BookingDate('2024-06-15 10:30:00');
echo 'После исправления: ', $meeting->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Встреча не создана: родитель не инициализирован
После исправления: 15.06.2024 10:30 UTC

extends означает наследование. Пустой __construct в BrokenDate заменил родительский конструктор, поэтому объект PHP есть, а внутренняя дата не создана. BookingDate получает аргумент в локальный text и передаёт его parent::__construct вместе с UTC; после этого format возвращает подпись. Обычный new DateTimeImmutable проще, если своё поведение класса не требуется.

Отделить программную неисправность объекта от отказа вводаСамостоятельный пример
<?php
class MissingDate extends DateTimeImmutable
{
    public function __construct() {}
}
try {
    $label = (new MissingDate())->format('Y-m-d');
} catch (DateException $error) {
    echo "Исправьте введённую строку\n";
} catch (DateError $error) {
    echo "Подпись недоступна: требуется исправление класса\n";
}
echo "Заказ BR41 остаётся без подтверждённой даты\n";
Ожидаемый результат
Подпись недоступна: требуется исправление класса
Заказ BR41 остаётся без подтверждённой даты

Сработал общий родитель DateError. Повторный ввод посетителем не создаст пропущенную родительскую часть объекта; исправление относится к коду класса. Исключение перехвачено, но дата не объявлена пригодной и переменная label не используется.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыError::__construct, Error::getMessage, Error::getPrevious, Error::getCode, Error::getFile, Error::getLine, Error::getTrace, Error::getTraceAsString, Error::__toString, Error::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateRangeError: когда возникает и что делать

Число не помещается в допустимый диапазон: например timestamp на 32-битной платформе или microsecond вне 0…999999 в PHP 8.4+. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Error.

DateRangeError в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateRangeError
  4. catch подходящего типа выбирает обработку
Отклонить микросекунды вне диапазона и принять допустимыеСамостоятельный пример
<?php
$event = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
foreach ([1000000, 999999] as $microsecond) {
    try {
        $precise = $event->setMicrosecond($microsecond);
        echo 'Событие: ', $precise->format('Y-m-d H:i:s.u'), "\n";
    } catch (DateRangeError $error) {
        echo 'Значение ', $microsecond, ": событие не сохранено\n";
    }
}
Ожидаемый результат
Значение 1000000: событие не сохранено
Событие: 2024-06-15 10:30:00.999999

Микросекунды —шесть цифр внутри секунды, диапазон0…999999. Миллион не переносится автоматически в следующую секунду этим setter. Первая операция не возвращает дату, вторая возвращает precise и позволяет сформировать запись события. PHP8.4+.

Сохранить первичную причину при добавлении номера событияСамостоятельный пример
<?php
$event = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
try {
    try {
        $changed = $event->setMicrosecond(-1);
    } catch (DateRangeError $cause) {
        throw new DateRangeError('Событие BR41: неверная дробная часть', 41, $cause);
    }
} catch (Error $error) {
    echo $error->getMessage(), "\n";
    echo 'Код приложения: ', $error->getCode(), "\n";
    echo 'Первичная причина: ', get_class($error->getPrevious()), "\n";
}
Ожидаемый результат
Событие BR41: неверная дробная часть
Код приложения: 41
Первичная причина: DateRangeError

Внутренний catch получил реальную ошибку setter в cause. Новый DateRangeError создан нашим кодом: message добавляет контекст, code41 —наш номер, previous сохраняет исходный объект. Внешний catch(Error) демонстрирует родителя. Getters возвращают строку, число и объект причины для диагностики;41 не является HTTP-статусом. Полный trace и ввод не нужно отправлять посетителю.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыError::__construct, Error::getMessage, Error::getPrevious, Error::getCode, Error::getFile, Error::getLine, Error::getTrace, Error::getTraceAsString, Error::__toString, Error::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateException: когда возникает и что делать

Общий родитель проблем с аргументами и разбором дат. Позволяет обработать несколько специальных видов одной ветвью catch. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateException
  4. catch подходящего типа выбирает обработку
Продолжить импорт после строки с неверной датойСамостоятельный пример
<?php
$rows = ['BR41' => 'definitely-not-a-date', 'BR42' => '2024-06-15 10:30:00 UTC'];
foreach ($rows as $id => $text) {
    try {
        $date = new DateTimeImmutable($text);
        echo $id, ': встреча ', $date->format('d.m.Y H:i'), " UTC\n";
    } catch (DateException $error) {
        echo $id, ": строка отложена для исправления\n";
    }
}
Ожидаемый результат
BR41: строка отложена для исправления
BR42: встреча 15.06.2024 10:30 UTC

Один catch родителя принимает дочерний DateMalformedStringException. После обработки первой строки foreach переходит ко второй и выводит настоящую встречу. Это фиксированные тестовые записи; для импорта формата с жёстким договором нужен createFromFormat с диагностикой, а не свободный парсер.

Одним родителем обработать ошибку даты и ошибку зоныСамостоятельный пример
<?php
$rows = [
    ['BR41', '2024-06-15 10:30:00', 'Planet/Unknown'],
    ['BR42', 'definitely-not-a-date', 'UTC'],
    ['BR43', '2024-06-15 10:30:00', 'Asia/Tokyo'],
];
foreach ($rows as [$id, $text, $zoneName]) {
    try {
        $zone = new DateTimeZone($zoneName);
        $date = new DateTimeImmutable($text, $zone);
        echo $id, ': ', $date->format('d.m.Y H:i e'), "\n";
    } catch (DateException $error) {
        echo $id, ': нужен пересмотр данных (', get_class($error), ")\n";
    }
}
Ожидаемый результат
BR41: нужен пересмотр данных (DateInvalidTimeZoneException)
BR42: нужен пересмотр данных (DateMalformedStringException)
BR43: 15.06.2024 10:30 Asia/Tokyo

Каждый вложенный массив содержит номер, текст даты и зону. Деструктуризация foreach раскладывает их по трём переменным. Catch покрывает две разные DateException и позволяет продолжить импорт, но не ловит TypeError, ValueError или DateError. Поэтому структура и типы внешних данных проверяются отдельно до этого участка.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateInvalidOperationException: когда возникает и что делать

Операция не имеет поддерживаемого обратного календарного действия: например sub для «next weekday». Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateInvalidOperationException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateInvalidOperationException
  4. catch подходящего типа выбирает обработку
Явно выбрать предыдущий рабочий день вместо обращения правилаСамостоятельный пример
<?php
$meeting = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
$rule = DateInterval::createFromDateString('next weekday');
try {
    $earlier = $meeting->sub($rule);
} catch (DateInvalidOperationException $error) {
    echo "Правило next weekday нельзя вычесть\n";
}
$previous = $meeting->modify('previous weekday')->setTime(10, 30);
echo 'Предыдущий рабочий день в 10:30: ', $previous->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило next weekday нельзя вычесть
Предыдущий рабочий день в 10:30: 14.06.2024 10:30

Сам rule создан успешно. Sub не может вычислить обратное для относительного выбора следующего буднего дня. Мы задаём другое согласованное правило previous weekday и отдельно устанавливаем10:30, потому что выбор дня недели может сбросить часы. Это новая политика, а не математическое доказательство обратимости; праздники компании не учтены.

Сохранить исходную встречу при отказе редактированияСамостоятельный пример
<?php
$meeting = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
try {
    $candidate = $meeting->sub(DateInterval::createFromDateString('next weekday'));
    $meeting = $candidate;
} catch (DateException $error) {
    echo "Перенос отклонён; выберите конкретную дату\n";
}
echo 'Действующая встреча: ', $meeting->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перенос отклонён; выберите конкретную дату
Действующая встреча: 15.06.2024 10:30

Catch родителя DateException ловит DateInvalidOperationException. Присваивание meeting выполняется только после успешного sub; при исключении оно пропущено. Это полезный способ сначала вычислить кандидата, затем подтвердить замену. Процедурный date_sub имеет иной договор предупреждения, описанный в его карточке.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateInvalidTimeZoneException: когда возникает и что делать

Конструктор DateTimeZone не распознал переданный идентификатор/обозначение зоны. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateInvalidTimeZoneException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateInvalidTimeZoneException
  4. catch подходящего типа выбирает обработку
Не угадать зону встречи при ошибке идентификатораСамостоятельный пример
<?php
foreach (['Planet/Unknown', 'Asia/Tokyo'] as $zoneName) {
    try {
        $zone = new DateTimeZone($zoneName);
        $meeting = new DateTimeImmutable('2024-06-15 10:30:00', $zone);
        echo 'Встреча: ', $meeting->format('d.m.Y H:i e P'), "\n";
    } catch (DateInvalidTimeZoneException $error) {
        echo $zoneName, ": выберите существующую зону\n";
    }
}
Ожидаемый результат
Planet/Unknown: выберите существующую зону
Встреча: 15.06.2024 10:30 Asia/Tokyo +09:00

Конструктор не вернул zone для неизвестного имени; следующие строки try пропущены. Во второй итерации имеется настоящий регион и встреча. Важное местное время нельзя молча объявить UTC только для того, чтобы продолжить код.

Вернуть отсутствие результата из функции чтения настройкиСамостоятельный пример
<?php
function configuredZone(string $name): ?DateTimeZone
{
    try { return new DateTimeZone($name); }
    catch (DateException $error) { return null; }
}
$zone = configuredZone('Planet/Unknown');
if ($zone === null) {
    echo "Расписание не запущено: исправьте настройку зоны\n";
} else {
    $start = new DateTimeImmutable('2024-06-15 09:00:00', $zone);
    echo $start->format('Y-m-d H:i e'), "\n";
}
Ожидаемый результат
Расписание не запущено: исправьте настройку зоны

Строка вызова попадает в локальный name; ?DateTimeZone означает объект зоны или null. Родитель DateException перехватывает ошибку имени, return null возвращается в присваивание zone. Ветвление снаружи функции решает не запускать расписание. Функция не печатает сама и не создаёт запасную зону без согласованного правила.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateMalformedIntervalStringException: когда возникает и что делать

Неверная строка длительности для DateInterval или неразбираемая строка его OO-фабрики. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateMalformedIntervalStringException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateMalformedIntervalStringException
  4. catch подходящего типа выбирает обработку
Заменить неподходящий язык длительности и получить срок доставкиСамостоятельный пример
<?php
$sent = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
try {
    $duration = new DateInterval('two days');
} catch (DateMalformedIntervalStringException $error) {
    echo "Конструктору нужна длительность вроде P2D\n";
}
$duration = new DateInterval('P2D');
$arrival = $sent->add($duration);
echo 'Доставка через два календарных дня: ', $arrival->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Конструктору нужна длительность вроде P2D
Доставка через два календарных дня: 17.06.2024 10:30

P вводит длительность,2D —два дня. Ошибка была в языке конструктора, а не в допустимости доставки через два дня. После явного исправления interval передан в add; return метода сохранён в arrival. Английские правила читает отдельная фабрика createFromDateString. День в регионе с DST не всегда равен24часам.

Согласованно построить длительность из числовой настройкиСамостоятельный пример
<?php
$days = 2;
if ($days < 1 || $days > 30) {
    echo "Срок должен быть от 1 до 30 дней\n";
} else {
    $text = 'P' . $days . 'D';
    try {
        $duration = new DateInterval($text);
        $due = (new DateTimeImmutable('2024-06-15 UTC'))->add($duration);
        echo 'Правило ', $text, ': доставка ', $due->format('d.m.Y'), "\n";
    } catch (DateException $error) {
        echo "Не удалось построить правило доставки\n";
    }
}
Ожидаемый результат
Правило P2D: доставка 17.06.2024

Здесь days —уже целое внутреннее значение; для HTTP-поля сначала требуется проверка типа и целого числа. Точки . соединяют строки P,2,D в P2D. Собственная граница1…30 относится к сервису, не к общей грамматике DateInterval. Catch общего DateException показывает родителя, но на известных корректных данных ветвь успеха возвращает реальную дату.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateMalformedPeriodStringException: когда возникает и что делать

Неверное ISO-описание DatePeriod, недостаточные компоненты либо недопустимые параметры повторений. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateMalformedPeriodStringException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateMalformedPeriodStringException
  4. catch подходящего типа выбирает обработку
Отклонить строку периода и построить явное расписание отчётовСамостоятельный пример
<?php
try {
    $period = DatePeriod::createFromISO8601String('not a period');
} catch (DateMalformedPeriodStringException $error) {
    echo "Расписание из строки не принято\n";
}
$period = new DatePeriod(new DateTimeImmutable('2024-06-03 09:00:00 UTC'), new DateInterval('P7D'), 2);
foreach ($period as $date) {
    echo 'Отчёт: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Расписание из строки не принято
Отчёт: 03.06.2024 09:00
Отчёт: 10.06.2024 09:00
Отчёт: 17.06.2024 09:00

Фабрика не вернула период. Далее написано отдельное явное правило: начало3июня09:00, шаг7дней,2повторения сверх включённого начала. Поэтому foreach получает три даты. Это не автоматическое исправление произвольной строки; пользователь должен подтвердить такое расписание. Фабрика доступна сPHP8.3.

Не заменить рабочее расписание повреждённой настройкойСамостоятельный пример
<?php
$schedule = new DatePeriod(new DateTimeImmutable('2024-06-03 UTC'), new DateInterval('P7D'), 1);
try {
    $candidate = DatePeriod::createFromISO8601String('broken replacement');
    $schedule = $candidate;
} catch (DateException $error) {
    echo "Новая настройка отклонена; прежний план сохранён\n";
}
foreach ($schedule as $date) { echo $date->format('d.m.Y'), "\n"; }
Ожидаемый результат
Новая настройка отклонена; прежний план сохранён
03.06.2024
10.06.2024

Candidate появляется только после успешного разбора. Родительский catch обрабатывает DateMalformedPeriodStringException; присваивание schedule пропущено, поэтому перечисляются прежние две даты. Сначала проверка новой настройки, затем замена —та же последовательность нужна при сохранении конфигурации приложения.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

DateMalformedStringException: когда возникает и что делать

Конструктор DateTime/Immutable или их modify не смог разобрать строку. Это не проверка существования каждого календарного дня. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Exception.

DateMalformedStringException в программе
  1. Выполняется операция даты
  2. Нарушается её договор
  3. PHP создаёт DateMalformedStringException
  4. catch подходящего типа выбирает обработку
Различить неразбираемый текст и нормализованное 30 февраляСамостоятельный пример
<?php
try {
    $date = new DateTimeImmutable('definitely-not-a-date', new DateTimeZone('UTC'));
} catch (DateMalformedStringException $error) {
    echo "Текст не распознан; дата не создана\n";
}
$overflow = new DateTimeImmutable('2024-02-30', new DateTimeZone('UTC'));
echo 'Из 30 февраля построено: ', $overflow->format('d.m.Y'), "\n";
Ожидаемый результат
Текст не распознан; дата не создана
Из 30 февраля построено: 01.03.2024

Конструктор способен перенести лишний день в март без DateMalformedStringException. Поэтому catch не является валидатором календаря. Проверка warning_count и обратной строки в следующем примере запрещает такой перенос для поля доставки.

Вернуть пригодную дату или отказ после полной проверки одного форматаСамостоятельный пример
<?php
function deliveryDate(string $text): ?DateTimeImmutable
{
    if (strlen($text) !== 10 || str_contains($text, "\0")) { return null; }
    $date = DateTimeImmutable::createFromFormat('!Y-m-d', $text, new DateTimeZone('UTC'));
    $errors = DateTimeImmutable::getLastErrors();
    if ($date === false) { return null; }
    if ($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0)) { return null; }
    if ($date->format('Y-m-d') !== $text) { return null; }
    return $date;
}
foreach (['2024-02-30', '2024-02-29'] as $text) {
    $date = deliveryDate($text);
    echo $text, ': ', $date === null ? 'исправьте дату' : 'доставка ' . $date->format('d.m.Y'), "\n";
}
Ожидаемый результат
2024-02-30: исправьте дату
2024-02-29: доставка 29.02.2024

Вызов передаёт строку в локальный text. Сначала длина и NUL, затем шаблон !Y-m-d с UTC, сразу после него getLastErrors. СPHP8.2 отсутствие диагностики —false; это успешный случай, не ошибка. Warning/error или несовпавший обратный format возвращают null; иначе return отдаёт объект. Снаружи результат сохранён в date и используется только после проверки null. CreateFromFormat обычно возвращает false при неудаче, а NUL способен дать ValueError: поэтому один catch(DateException) не решает эту задачу. Строковый тип HTTP-поля проверяют до вызова.

Унаследованная частьКак применяется
Конструкторmessage = "", code = 0, previous = null. code не становится автоматически HTTP-статусом; previous связывает исходную причину.
Свойстваprotected message/code/file/line; private string/trace/previous у базового класса. Для чтения используют методы, а не доступ к внутренним полям.
МетодыException::__construct, Exception::getMessage, Exception::getPrevious, Exception::getCode, Exception::getFile, Exception::getLine, Exception::getTrace, Exception::getTraceAsString, Exception::__toString, Exception::__clone. get* final; __clone закрыт; __toString формирует диагностическое представление.
Что выводитьgetMessage пригоден для технической диагностики с учётом чувствительных данных. Полный trace не отправляют посетителю.

Вернуться к дереву ошибок даты. Полные контракты унаследованных методов.

Все процедурные функции Date/Time

Процедурные функции сгруппированы здесь для поиска по имени. Часть является аналогами методов, но обработка ошибок и изменяемость могут отличаться. Каждая ссылка ведёт к собственным параметрам, результатам и примерам. Название функции не должно заставлять угадывать её связь с объектом.

Найти нужный вызов
  1. Импорт строки → date_create_immutable / date_parse
  2. Показать подпись → date_format / date / gmdate
  3. Работать с зонами → timezone_*
  4. Календарь и часы → остальные функции ниже
ФункцияНазначение
checkdateОтклонить несуществующую дату доставки до её сохранения.
dateПолучить читаемую дату события в выбранном часовом поясе.
date_addПрибавить подготовленный календарный интервал или прошедшую длительность.
date_createПроцедурно создать объект даты и проверить false при неразбираемой строке.
date_create_from_formatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
date_create_immutableПроцедурно создать объект даты и проверить false при неразбираемой строке.
date_create_immutable_from_formatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
date_date_setПоставить конкретный год, месяц и день, сохранив время суток и зону.
date_default_timezone_getУзнать, в какой зоне PHP сейчас интерпретирует даты без явно заданного пояса.
date_default_timezone_setЯвно настроить общий timezone скрипта до работы с датами.
date_diffПолучить календарный интервал, переводящий исходную дату в целевую.
date_formatПолучить строку даты для человека или согласованного внешнего формата.
date_get_last_errorsСразу прочитать предупреждения и ошибки последнего разбора даты.
date_interval_create_from_date_stringПроцедурный вход в парсер относительного интервала со своей политикой ошибки.
date_interval_formatФорматировать DateInterval обычной функцией с явным аргументом-объектом.
date_isodate_setНайти дату по ISO-году, номеру недели и дню недели.
date_modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
date_offset_getПрочитать UTC-смещение зоны именно на дату объекта.
date_parseРазобрать строку в массив обнаруженных компонентов и диагностики.
date_parse_from_formatРазобрать строку в массив обнаруженных компонентов и диагностики.
date_subВычесть подготовленный календарный интервал или прошедшую длительность.
date_sun_infoПолучить расчётный дневной план восхода, заката и сумерек для координат.
date_sunriseПонять старый расчёт восхода и перейти на date_sun_info.
date_sunsetПонять старый расчёт заката и перейти на date_sun_info.
date_time_setНазначить часы, минуты, секунды и микросекунды на существующую дату.
date_timestamp_getПолучить целые секунды Unix для момента, независимо от отображаемой зоны.
date_timestamp_setУстановить точный момент по целым секундам Unix, сохранив зону отображения.
date_timezone_getПолучить объект часового пояса, связанный с датой.
date_timezone_setПоказать тот же момент на часах другого региона.
getdateПолучить календарные поля события для отчёта или простой ветки расписания.
gettimeofdayПолучить секунды и микросекунды одним согласованным снимком часов.
gmdateПолучить UTC-подпись для внешнего протокола.
gmmktimeПостроить момент отгрузки из отдельных календарных полей UTC.
gmstrftimeПрочитать старый UTC формат даты по системной locale и заменить его явным formatter.
idateПолучить одно числовое поле календаря для условия или группировки.
localtimeРазобрать timestamp в структуру tm для совместимости со старым интерфейсом.
microtimeПоставить в журнал отметку времени с дробной секундой, не путая её с секундомером.
mktimeПостроить момент отгрузки из отдельных календарных полей местного времени.
strftimeПрочитать старый локальный формат даты по системной locale и заменить его явным formatter.
strptimeПонять старый locale-парсер и заменить его контролируемым разбором даты.
strtotimeПрочитать текстовую настройку даты или относительный срок от явно заданной базы.
timeЗафиксировать момент создания записи и вычислить простой срок действия.
timezone_abbreviations_listРазобрать сокращение из импортированного журнала и увидеть варианты его значения.
timezone_identifiers_listПолучить список разрешённых регионов для формы и серверной проверки.
timezone_location_getПолучить справочную географию выбранного пояса для подписи в административном интерфейсе.
timezone_name_from_abbrПолучить предположение о названии пояса из старого сокращения; подтвердить его до применения.
timezone_name_getПолучить имя пояса в процедурном коде, например перед сохранением настроек.
timezone_offset_getВ процедурном отчёте прочитать смещение региона на дату события.
timezone_openПолучить объект часового пояса обычным вызовом функции и проверить отказ.
timezone_transitions_getПрочитать историю правил региона в процедурном коде, например для диагностики расписания.
timezone_version_getУзнать версию правил времени, чтобы расследовать разницу расчётов между серверами.

Согласовать дату с внешним сервисом

Поставщик прислал момент со смещением, а посетитель хочет видеть время в своей зоне. Сначала сохраняют смысл момента, затем выбирают представление. JSON передаёт строку, а не PHP-объект DateTime. При отправке назад формат и точность выбирают по договору API; внутренний serialize для этого не подходит.

API → объект → посетитель → API
  1. 2024-06-15T07:30:00+00:00 пришло строкой JSON
  2. Строгий разбор и проверка диагностики
  3. Europe/Moscow → 10:30 для посетителя
  4. В ответе снова согласованная строка со смещением
Получить подпись и ответ API только из принятого поля датыСамостоятельный пример
<?php
function meetingReply(string $raw): string
{
    if (strlen($raw) > 200) { throw new UnexpectedValueException('Слишком большой запрос'); }
    $payload = json_decode($raw, true, 8, JSON_THROW_ON_ERROR);
    if (!is_array($payload) || !isset($payload['starts_at']) || !is_string($payload['starts_at'])) {
        throw new UnexpectedValueException('Нужна строка starts_at');
    }
    $text = $payload['starts_at'];
    if (strlen($text) !== 25 || str_contains($text, "\0")) {
        throw new UnexpectedValueException('Нужны дата, секунды и смещение +HH:MM');
    }
    $date = DateTimeImmutable::createFromFormat('!Y-m-d\TH:i:sP', $text);
    $errors = DateTimeImmutable::getLastErrors();
    if ($date === false || ($errors !== false && ($errors['warning_count'] || $errors['error_count']))
        || $date->format('Y-m-d\TH:i:sP') !== $text) {
        throw new UnexpectedValueException('Несуществующая дата или иной формат');
    }
    $local = $date->setTimezone(new DateTimeZone('Europe/Moscow'));
    return json_encode(['starts_at' => $date->format(DATE_ATOM), 'label' => $local->format('d.m.Y H:i')], JSON_THROW_ON_ERROR);
}
$requests = [
    '{"starts_at":"2024-06-15T07:30:00+00:00"}',
    '{"starts_at":"2024-02-30T07:30:00+00:00"}',
    '{"starts_at":"2024-06-15T07:30:00+00:00\u0000"}',
];
foreach ($requests as $raw) {
    try { echo meetingReply($raw), "\n"; }
    catch (JsonException | UnexpectedValueException $error) {
        echo 'Запрос отклонён: ', $error->getMessage(), "\n";
    }
}
Ожидаемый результат
{"starts_at":"2024-06-15T07:30:00+00:00","label":"15.06.2024 10:30"}
Запрос отклонён: Несуществующая дата или иной формат
Запрос отклонён: Нужны дата, секунды и смещение +HH:MM

Вызов получает одну JSON-строку в локальный raw. Json_decode с true возвращает массив,8 ограничивает вложенность, флаг превращает ошибку JSON в JsonException. Проверки извлекают строковый starts_at;25байт —точный договор четырёхзначной даты с секундами и ±HH:MM, NUL запрещён. После разбора сразу сохранена диагностика, обратный format проверяет совпадение. SetTimezone создаёт московское представление, json_encode возвращает JSON, return отдаёт строку в echo вызывающего foreach. Во второй записи30 февраля не становится подтверждённым1марта; в третьей декодированный NUL отклоняется до парсера. Это полный самостоятельный CLI-опыт, HTTP и внешний сервис не вызывались. Реальный контроллер добавляет свои статусы и транспортные ограничения; Z и доли секунды этим конкретным договором не принимаются.

Для больших выгрузок разбирайте каждое значение один раз и сохраняйте объект/проверенный результат для последующих действий. Повторный свободный парсинг и форматирование в глубоко вложенном цикле добавляют работу; цена зависит от объёма и форматов, её измеряют в своей среде. Для тысяч будущих повторений используйте перебор DatePeriod без ненужного большого массива, предварительно задав конечную границу. Многомерные данные из JSON; приём формы; HTTP-интеграции.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

DateTime::__constructСоздать дату из строки и явно выбранного часового пояса.

PHP 5.2+ · date

public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null)

Создать дату из строки и явно выбранного часового пояса.

Часть вызоваДоговор
datetimeСтрока поддерживаемого формата. По умолчанию now: текущее время в момент вызова.
timezoneОбъект DateTimeZone или null. null использует текущий часовой пояс PHP. Если сама строка содержит зону/смещение либо @timestamp, этот параметр игнорируется.
Возврат newНовый DateTime. Методы изменения меняют этот объект; другие переменные с тем же объектом увидят изменение.
ОшибкаНеразбираемая строка: DateMalformedStringException с PHP 8.3, ранее Exception. Календарное переполнение может нормализоваться с предупреждением; успех new не является строгой проверкой даты.
МикросекундыВ now с PHP 7.1 используются фактические микросекунды текущих часов.
Путь даты в первом примере
  1. Строка 2024-06-15 10:30:00 и zone=Europe/Moscow
  2. new DateTime создаёт meeting
  3. meeting->format(d.m.Y H:i:s P) возвращает label
  4. echo добавляет назначение: подтверждена встреча

new создаёт объект. DateTimeZone — объект правил часового пояса, его передают вторым аргументом. Стрелочка -> вызывает метод объекта, а format возвращает строку. В шаблоне Y — год, m — месяц, d — день, H:i:s — часы, минуты, секунды; P — смещение UTC с двоеточием.

Подготовить подтверждение встречиСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Moscow');
$meeting = new DateTime('2024-06-15 10:30:00', $zone);
$label = $meeting->format('d.m.Y H:i:s P');
echo 'Подтверждена встреча: ', $label, "\n";
Ожидаемый результат
Подтверждена встреча: 15.06.2024 10:30:00 +03:00

Zone создан первым и передан вторым аргументом. New возвращает объект в meeting; format читает этот объект и возвращает строку в label. Только echo отправляет её в вывод. Секунды заданы явно, поэтому результат не зависит от времени запуска. Правила Europe/Moscow на этой дате дают смещение +03:00.

Сначала прочитать момент, затем показать его в ТокиоСамостоятельный пример
<?php
$incoming = new DateTime('2024-06-15T10:30:00+02:00', new DateTimeZone('Asia/Tokyo'));
echo 'Получено со смещением: ', $incoming->format('d.m.Y H:i P'), "\n";
$tokyo = $incoming->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo 'Для участника в Токио: ', $tokyo->format('d.m.Y H:i P'), "\n";
$epoch = new DateTime('@0', new DateTimeZone('Asia/Tokyo'));
echo 'Начало Unix: ', $epoch->format('Y-m-d H:i P'), "\n";
Ожидаемый результат
Получено со смещением: 15.06.2024 10:30 +02:00
Для участника в Токио: 15.06.2024 17:30 +09:00
Начало Unix: 1970-01-01 00:00 +00:00

В первой строке +02:00 уже задаёт смысл входных часов, поэтому второй аргумент Tokyo не переопределяет его. Отдельный setTimezone сохраняет момент и показывает17:30 +09:00. У DateTime меняется сам incoming; переменная tokyo обозначает этот же изменённый объект. Строка @0 означает нулевую Unix-метку: она создаётся в UTC независимо от переданного Tokyo. @ внутри строки здесь часть формата даты, а не оператор подавления ошибок PHP.

Не принять исправленный день за выбор клиентаСамостоятельный пример
<?php
$input = '2024-02-30';
$date = new DateTime($input, new DateTimeZone('UTC'));
$problems = DateTime::getLastErrors();
echo 'Запрошено: ', $input, "\n";
echo 'Построен объект: ', $date->format('Y-m-d'), "\n";
if ($problems !== false && $problems['warning_count'] > 0) {
    echo "Встреча не сохранена: исходного дня нет в календаре\n";
}
Ожидаемый результат
Запрошено: 2024-02-30
Построен объект: 2024-03-01
Встреча не сохранена: исходного дня нет в календаре

2024 год високосный: после29 февраля лишний день попал на1 марта. Конструктор смог создать объект, но getLastErrors сохранил предупреждение. Пример показывает нормализованную дату для разбора ошибки и отклоняет её для бронирования. Для фиксированного поля удобнее createFromFormat с проверкой диагностики и обратного формата.

Прервать создание конкретной записи и продолжить обработкуСамостоятельный пример
<?php
try {
    $meeting = new DateTime('not-a-date-at-all', new DateTimeZone('UTC'));
    echo 'Встреча записана: ', $meeting->format('d.m.Y H:i'), "\n";
} catch (Exception $error) {
    echo "Встреча не создана: строку даты невозможно разобрать\n";
}
echo "Можно проверить следующую заявку\n";
Ожидаемый результат
Встреча не создана: строку даты невозможно разобрать
Можно проверить следующую заявку

New не вернул объект: присваивание meeting и строка подтверждения не завершились. Catch даёт понятный отказ, после него выполняется следующая строка. Базовый Exception позволяет этому опыту работать и в PHP8.2; с8.3 конкретная ошибка — DateMalformedStringException, её можно перехватывать отдельно. Внешнему посетителю не выводится внутреннее сообщение с произвольным исходным текстом.

Сравните: DateTime::createFromFormat, DateTime::createFromTimestamp, DateTime::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::createFromTimestampСоздать объект из секунд Unix, сохранив дробную часть до микросекунд.

PHP 8.4+ · date

public static function createFromTimestamp(int|float $timestamp): static

Создать объект из секунд Unix, сохранив дробную часть до микросекунд.

Часть вызоваДоговор
timestampЦелое или float секунд с 1970-01-01 00:00:00 UTC. Дробная часть задаёт микросекунды в пределах точности float.
ВозвратНовый объект вызываемого класса в зоне +00:00; static сохраняет класс при унаследованном вызове.
ОшибкаDateRangeError, если timestamp вне диапазона PHP_INT_MIN…PHP_INT_MAX. NAN/INF не являются допустимыми моментами.
ВерсияPHP 8.4+. Старые альтернативы используют @timestamp или createFromFormat с отдельным договором.
Путь даты в первом примере
  1. Событие EVT-41 прислало 1704067200.125 секунд Unix
  2. DateTime::createFromTimestamp получает timestamp
  3. eventAt → 2024-01-01 00:00:00.125000 +00:00
  4. format(u) → строка для журнала события
Записать время события вместе с долей секундыСамостоятельный пример
<?php
$timestamp = 1704067200.125;
$eventAt = DateTime::createFromTimestamp($timestamp);
echo 'Событие EVT-41: ', $eventAt->format('Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
Событие EVT-41: 2024-01-01 00:00:00.125000 +00:00

PHP8.4+. Аргумент содержит секунды Unix; дробь0.125 — одна восьмая секунды. Фабрика возвращает объект в eventAt, format с u создаёт шесть цифр микросекунд, а echo добавляет идентификатор события. Timestamp не зависит от локальной зоны сервера. Float имеет ограниченную точность: произвольные большие дробные числа не обещают сохранения всех цифр.

Фабрика сохраняет вызываемый классСамостоятельный пример
<?php
declare(strict_types=1);

class EventTime extends DateTime {}
$date = EventTime::createFromTimestamp(0);
echo get_class($date), "\n", $date->format('Y-m-d'), "\n";
Ожидаемый результат
EventTime
1970-01-01

static в сигнатуре означает класс вызова, а не то, что объект глобален. Метод с :: вызывается у имени класса и создаёт экземпляр.

Унаследованная фабрика создаёт внутреннюю дату без вызова пользовательского __construct. Она не выполняет ваши дополнительные проверки и не заполняет обязательные свойства подкласса. static гарантирует класс результата, но не выполнение произвольных правил его конструктора.

Отклонить непригодный момент, не создавая ложную запись событияСамостоятельный пример
<?php
$timestamp = INF;
try {
    $eventAt = DateTime::createFromTimestamp($timestamp);
    echo 'Событие EVT-41: ', $eventAt->format('Y-m-d H:i:s'), "\n";
} catch (DateRangeError $error) {
    echo "Событие EVT-41 не принято: время вне диапазона\n";
}
Ожидаемый результат
Событие EVT-41 не принято: время вне диапазона

PHP8.4+. Бесконечность не обозначает конкретный момент и вызывает DateRangeError до возврата объекта. Строка подтверждения события пропущена; catch сообщает об отказе именно этой записи. Для внешнего числа сначала проверяют тип, конечность, допустимый диапазон и единицы, а не заменяют невозможную дату текущим временем.

Сравните: DateTime::__construct, DateTime::setTimestamp, time.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::__constructСоздать дату из строки и явно выбранного часового пояса.

PHP 5.5+ · date

public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null)

Создать дату из строки и явно выбранного часового пояса.

Часть вызоваДоговор
datetimeСтрока поддерживаемого формата. По умолчанию now: текущее время в момент вызова.
timezoneОбъект DateTimeZone или null. null использует текущий часовой пояс PHP. Если сама строка содержит зону/смещение либо @timestamp, этот параметр игнорируется.
Возврат newНовый DateTimeImmutable. Методы изменения возвращают новый объект; сохраняйте результат в переменную.
ОшибкаНеразбираемая строка: DateMalformedStringException с PHP 8.3, ранее Exception. Календарное переполнение может нормализоваться с предупреждением; успех new не является строгой проверкой даты.
МикросекундыВ now с PHP 7.1 используются фактические микросекунды текущих часов.
Путь даты в первом примере
  1. Строка 2024-06-15 10:30:00 и zone=Europe/Moscow
  2. new DateTimeImmutable создаёт meeting
  3. meeting->format(d.m.Y H:i:s P) возвращает label
  4. echo добавляет назначение: подтверждена встреча

new создаёт объект. DateTimeZone — объект правил часового пояса, его передают вторым аргументом. Стрелочка -> вызывает метод объекта, а format возвращает строку. В шаблоне Y — год, m — месяц, d — день, H:i:s — часы, минуты, секунды; P — смещение UTC с двоеточием.

Подготовить подтверждение встречиСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Moscow');
$meeting = new DateTimeImmutable('2024-06-15 10:30:00', $zone);
$label = $meeting->format('d.m.Y H:i:s P');
echo 'Подтверждена встреча: ', $label, "\n";
Ожидаемый результат
Подтверждена встреча: 15.06.2024 10:30:00 +03:00

Zone создан первым и передан вторым аргументом. New возвращает объект в meeting; format читает этот объект и возвращает строку в label. Только echo отправляет её в вывод. Секунды заданы явно, поэтому результат не зависит от времени запуска. Правила Europe/Moscow на этой дате дают смещение +03:00.

Сначала прочитать момент, затем показать его в ТокиоСамостоятельный пример
<?php
$incoming = new DateTimeImmutable('2024-06-15T10:30:00+02:00', new DateTimeZone('Asia/Tokyo'));
echo 'Получено со смещением: ', $incoming->format('d.m.Y H:i P'), "\n";
$tokyo = $incoming->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo 'Для участника в Токио: ', $tokyo->format('d.m.Y H:i P'), "\n";
$epoch = new DateTimeImmutable('@0', new DateTimeZone('Asia/Tokyo'));
echo 'Начало Unix: ', $epoch->format('Y-m-d H:i P'), "\n";
Ожидаемый результат
Получено со смещением: 15.06.2024 10:30 +02:00
Для участника в Токио: 15.06.2024 17:30 +09:00
Начало Unix: 1970-01-01 00:00 +00:00

В первой строке +02:00 уже задаёт смысл входных часов, поэтому второй аргумент Tokyo не переопределяет его. Отдельный setTimezone сохраняет момент и показывает17:30 +09:00. У Immutable возвращается другой объект; incoming остаётся в исходном представлении. Строка @0 означает нулевую Unix-метку: она создаётся в UTC независимо от переданного Tokyo. @ внутри строки здесь часть формата даты, а не оператор подавления ошибок PHP.

Не принять исправленный день за выбор клиентаСамостоятельный пример
<?php
$input = '2024-02-30';
$date = new DateTimeImmutable($input, new DateTimeZone('UTC'));
$problems = DateTimeImmutable::getLastErrors();
echo 'Запрошено: ', $input, "\n";
echo 'Построен объект: ', $date->format('Y-m-d'), "\n";
if ($problems !== false && $problems['warning_count'] > 0) {
    echo "Встреча не сохранена: исходного дня нет в календаре\n";
}
Ожидаемый результат
Запрошено: 2024-02-30
Построен объект: 2024-03-01
Встреча не сохранена: исходного дня нет в календаре

2024 год високосный: после29 февраля лишний день попал на1 марта. Конструктор смог создать объект, но getLastErrors сохранил предупреждение. Пример показывает нормализованную дату для разбора ошибки и отклоняет её для бронирования. Для фиксированного поля удобнее createFromFormat с проверкой диагностики и обратного формата.

Прервать создание конкретной записи и продолжить обработкуСамостоятельный пример
<?php
try {
    $meeting = new DateTimeImmutable('not-a-date-at-all', new DateTimeZone('UTC'));
    echo 'Встреча записана: ', $meeting->format('d.m.Y H:i'), "\n";
} catch (Exception $error) {
    echo "Встреча не создана: строку даты невозможно разобрать\n";
}
echo "Можно проверить следующую заявку\n";
Ожидаемый результат
Встреча не создана: строку даты невозможно разобрать
Можно проверить следующую заявку

New не вернул объект: присваивание meeting и строка подтверждения не завершились. Catch даёт понятный отказ, после него выполняется следующая строка. Базовый Exception позволяет этому опыту работать и в PHP8.2; с8.3 конкретная ошибка — DateMalformedStringException, её можно перехватывать отдельно. Внешнему посетителю не выводится внутреннее сообщение с произвольным исходным текстом.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::createFromTimestampСоздать объект из секунд Unix, сохранив дробную часть до микросекунд.

PHP 8.4+ · date

public static function createFromTimestamp(int|float $timestamp): static

Создать объект из секунд Unix, сохранив дробную часть до микросекунд.

Часть вызоваДоговор
timestampЦелое или float секунд с 1970-01-01 00:00:00 UTC. Дробная часть задаёт микросекунды в пределах точности float.
ВозвратНовый объект вызываемого класса в зоне +00:00; static сохраняет класс при унаследованном вызове.
ОшибкаDateRangeError, если timestamp вне диапазона PHP_INT_MIN…PHP_INT_MAX. NAN/INF не являются допустимыми моментами.
ВерсияPHP 8.4+. Старые альтернативы используют @timestamp или createFromFormat с отдельным договором.
Путь даты в первом примере
  1. Событие EVT-41 прислало 1704067200.125 секунд Unix
  2. DateTimeImmutable::createFromTimestamp получает timestamp
  3. eventAt → 2024-01-01 00:00:00.125000 +00:00
  4. format(u) → строка для журнала события
Записать время события вместе с долей секундыСамостоятельный пример
<?php
$timestamp = 1704067200.125;
$eventAt = DateTimeImmutable::createFromTimestamp($timestamp);
echo 'Событие EVT-41: ', $eventAt->format('Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
Событие EVT-41: 2024-01-01 00:00:00.125000 +00:00

PHP8.4+. Аргумент содержит секунды Unix; дробь0.125 — одна восьмая секунды. Фабрика возвращает объект в eventAt, format с u создаёт шесть цифр микросекунд, а echo добавляет идентификатор события. Timestamp не зависит от локальной зоны сервера. Float имеет ограниченную точность: произвольные большие дробные числа не обещают сохранения всех цифр.

Фабрика сохраняет вызываемый классСамостоятельный пример
<?php
declare(strict_types=1);

class EventTime extends DateTimeImmutable {}
$date = EventTime::createFromTimestamp(0);
echo get_class($date), "\n", $date->format('Y-m-d'), "\n";
Ожидаемый результат
EventTime
1970-01-01

static в сигнатуре означает класс вызова, а не то, что объект глобален. Метод с :: вызывается у имени класса и создаёт экземпляр.

Унаследованная фабрика создаёт внутреннюю дату без вызова пользовательского __construct. Она не выполняет ваши дополнительные проверки и не заполняет обязательные свойства подкласса. static гарантирует класс результата, но не выполнение произвольных правил его конструктора.

Отклонить непригодный момент, не создавая ложную запись событияСамостоятельный пример
<?php
$timestamp = INF;
try {
    $eventAt = DateTimeImmutable::createFromTimestamp($timestamp);
    echo 'Событие EVT-41: ', $eventAt->format('Y-m-d H:i:s'), "\n";
} catch (DateRangeError $error) {
    echo "Событие EVT-41 не принято: время вне диапазона\n";
}
Ожидаемый результат
Событие EVT-41 не принято: время вне диапазона

PHP8.4+. Бесконечность не обозначает конкретный момент и вызывает DateRangeError до возврата объекта. Строка подтверждения события пропущена; catch сообщает об отказе именно этой записи. Для внешнего числа сначала проверяют тип, конечность, допустимый диапазон и единицы, а не заменяют невозможную дату текущим временем.

Сравните: DateTimeImmutable::__construct, DateTimeImmutable::setTimestamp, time.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::createFromImmutableПолучить независимый объект DateTime с тем же моментом и зоной.

PHP 7.3+ · date

public static function createFromImmutable(DateTimeImmutable $object): static

Получить независимый объект DateTime с тем же моментом и зоной.

Часть вызоваДоговор
objectОбъект DateTimeImmutable. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется.
ВозвратНовый DateTime. С PHP 8 возвращается вызываемый класс; раньше базовый DateTime.
СмыслПерейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом.
Путь даты в первом примере
  1. original: 15 июня, 10:30:00.125000, Europe/Moscow
  2. DateTime::createFromImmutable(original) создаёт независимый converted
  3. modify(+1 day) даёт reminder16 июня
  4. original по-прежнему15 июня; зона и микросекунды сохранены
Перенести напоминание, сохранив время исходной встречиСамостоятельный пример
<?php
$original = new DateTimeImmutable('2024-06-15 10:30:00.125000', new DateTimeZone('Europe/Moscow'));
$converted = DateTime::createFromImmutable($original);
$reminder = $converted->modify('+1 day');
echo 'Исходная встреча: ', $original->format('Y-m-d H:i:s.u e'), "\n";
echo 'Напоминание: ', $reminder->format('Y-m-d H:i:s.u e'), "\n";
echo 'Преобразованный вариант теперь: ', $converted->format('Y-m-d'), "\n";
Ожидаемый результат
Исходная встреча: 2024-06-15 10:30:00.125000 Europe/Moscow
Напоминание: 2024-06-16 10:30:00.125000 Europe/Moscow
Преобразованный вариант теперь: 2024-06-16

Фабрика получает original и возвращает независимый объект converted с теми же календарём, микросекундами и зоной. Поэтому дальнейший перенос не изменяет исходную встречу. Converted — DateTime: modify меняет его самого, reminder обозначает этот же результат16 июня. Сериализация, промежуточная строка format и повторный разбор для такого преобразования не нужны.

Проверить конкретный класс результатаСамостоятельный пример
<?php
declare(strict_types=1);

class LocalDate extends DateTime {}
$original = new DateTimeImmutable('2024-01-01', new DateTimeZone('UTC'));
$converted = LocalDate::createFromImmutable($original);
echo get_class($converted), "\n";
Ожидаемый результат
LocalDate

Проверка выполнена на PHP 8.5.11. createFromInterface фактически сохраняет класс вызова, хотя опубликованный возвращаемый тип указан как базовый. Это согласуется со stub PHP 8.5.11: в нём стоит TODO уточнить тип на static. У createFromMutable/Immutable изменение на класс вызова отдельно отмечено с PHP 8.0.

Фабрика копирует внутреннее значение даты. Она не вызывает пользовательский __construct и не переносит произвольные свойства исходного подкласса. Поэтому дополнительные данные заказа или ограничения подкласса нужно инициализировать отдельно; часто проще хранить DateTimeImmutable внутри своего объекта заказа.

Увидеть, почему фабрика даты не создаёт полноценную броньСамостоятельный пример
<?php
class BookingDate extends DateTime {
    public string $bookingId;
    public function __construct(string $bookingId) {
        $this->bookingId = $bookingId;
        parent::__construct('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    }
}
$source = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$converted = BookingDate::createFromImmutable($source);
echo 'Фабрика создала дату: ', $converted->format('d.m.Y H:i'), "\n";
try { echo 'Бронь: ' . $converted->bookingId . "\n"; }
catch (Error $error) { echo "Номер брони отсутствует: конструктор BookingDate не выполнялся\n"; }
$booking = new BookingDate('B-41');
echo 'Обычное создание: ', $booking->bookingId, ', ', $booking->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Фабрика создала дату: 15.06.2024 10:30
Номер брони отсутствует: конструктор BookingDate не выполнялся
Обычное создание: B-41, 15.06.2024 10:30

Это отдельная ступень после наследования: extends создаёт подкласс, public string требует инициализированного поля, parent::__construct заполняет родительскую дату. Фабрика создаёт внутреннее календарное значение, обходя пользовательский конструктор, поэтому bookingId не заполнен. Чтение поля вызывает Error до echo готовой строки. Обычный new BookingDate(B-41) запускает конструктор и даёт настоящую подпись брони. Практически часто проще хранить объект даты в отдельном объекте брони, чем наследовать DateTime ради дополнительного поля.

Сравните: DateTime::__construct, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::createFromMutableПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.

PHP 5.6+ · date

public static function createFromMutable(DateTime $object): static

Получить независимый объект DateTimeImmutable с тем же моментом и зоной.

Часть вызоваДоговор
objectОбъект DateTime. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется.
ВозвратНовый DateTimeImmutable. С PHP 8 возвращается вызываемый класс; раньше базовый DateTimeImmutable.
СмыслПерейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом.
Путь даты в первом примере
  1. original: 15 июня, 10:30:00.125000, Europe/Moscow
  2. DateTimeImmutable::createFromMutable(original) создаёт независимый converted
  3. modify(+1 day) даёт reminder16 июня
  4. original по-прежнему15 июня; зона и микросекунды сохранены
Перенести напоминание, сохранив время исходной встречиСамостоятельный пример
<?php
$original = new DateTime('2024-06-15 10:30:00.125000', new DateTimeZone('Europe/Moscow'));
$converted = DateTimeImmutable::createFromMutable($original);
$reminder = $converted->modify('+1 day');
echo 'Исходная встреча: ', $original->format('Y-m-d H:i:s.u e'), "\n";
echo 'Напоминание: ', $reminder->format('Y-m-d H:i:s.u e'), "\n";
echo 'Преобразованный вариант теперь: ', $converted->format('Y-m-d'), "\n";
Ожидаемый результат
Исходная встреча: 2024-06-15 10:30:00.125000 Europe/Moscow
Напоминание: 2024-06-16 10:30:00.125000 Europe/Moscow
Преобразованный вариант теперь: 2024-06-15

Фабрика получает original и возвращает независимый объект converted с теми же календарём, микросекундами и зоной. Поэтому дальнейший перенос не изменяет исходную встречу. Converted — Immutable: modify создаёт ещё один объект reminder, converted остаётся15 июня. Сериализация, промежуточная строка format и повторный разбор для такого преобразования не нужны.

Проверить конкретный класс результатаСамостоятельный пример
<?php
declare(strict_types=1);

class LocalDate extends DateTimeImmutable {}
$original = new DateTime('2024-01-01', new DateTimeZone('UTC'));
$converted = LocalDate::createFromMutable($original);
echo get_class($converted), "\n";
Ожидаемый результат
LocalDate

Проверка выполнена на PHP 8.5.11. createFromInterface фактически сохраняет класс вызова, хотя опубликованный возвращаемый тип указан как базовый. Это согласуется со stub PHP 8.5.11: в нём стоит TODO уточнить тип на static. У createFromMutable/Immutable изменение на класс вызова отдельно отмечено с PHP 8.0.

Фабрика копирует внутреннее значение даты. Она не вызывает пользовательский __construct и не переносит произвольные свойства исходного подкласса. Поэтому дополнительные данные заказа или ограничения подкласса нужно инициализировать отдельно; часто проще хранить DateTimeImmutable внутри своего объекта заказа.

Увидеть, почему фабрика даты не создаёт полноценную броньСамостоятельный пример
<?php
class BookingDate extends DateTimeImmutable {
    public string $bookingId;
    public function __construct(string $bookingId) {
        $this->bookingId = $bookingId;
        parent::__construct('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    }
}
$source = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$converted = BookingDate::createFromMutable($source);
echo 'Фабрика создала дату: ', $converted->format('d.m.Y H:i'), "\n";
try { echo 'Бронь: ' . $converted->bookingId . "\n"; }
catch (Error $error) { echo "Номер брони отсутствует: конструктор BookingDate не выполнялся\n"; }
$booking = new BookingDate('B-41');
echo 'Обычное создание: ', $booking->bookingId, ', ', $booking->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Фабрика создала дату: 15.06.2024 10:30
Номер брони отсутствует: конструктор BookingDate не выполнялся
Обычное создание: B-41, 15.06.2024 10:30

Это отдельная ступень после наследования: extends создаёт подкласс, public string требует инициализированного поля, parent::__construct заполняет родительскую дату. Фабрика создаёт внутреннее календарное значение, обходя пользовательский конструктор, поэтому bookingId не заполнен. Чтение поля вызывает Error до echo готовой строки. Обычный new BookingDate(B-41) запускает конструктор и даёт настоящую подпись брони. Практически часто проще хранить объект даты в отдельном объекте брони, чем наследовать DateTime ради дополнительного поля.

Сравните: DateTime::__construct, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::createFromInterfaceПолучить независимый объект DateTime с тем же моментом и зоной.

PHP 8.0+ · date

public static function createFromInterface(DateTimeInterface $object): DateTime

Получить независимый объект DateTime с тем же моментом и зоной.

Часть вызоваДоговор
objectОбъект DateTimeInterface. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется.
ВозвратНовый DateTime. В опубликованной сигнатуре указан базовый тип. В проверенном PHP 8.5.11 вызов через подкласс возвращает подкласс: базовый возвращаемый тип допускает потомка. Не следует выводить обратное только из отсутствия static в сигнатуре.
СмыслПерейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом.
Путь даты в первом примере
  1. original: 15 июня, 10:30:00.125000, Europe/Moscow
  2. DateTime::createFromInterface(original) создаёт независимый converted
  3. modify(+1 day) даёт reminder16 июня
  4. original по-прежнему15 июня; зона и микросекунды сохранены
Перенести напоминание, сохранив время исходной встречиСамостоятельный пример
<?php
$original = new DateTime('2024-06-15 10:30:00.125000', new DateTimeZone('Europe/Moscow'));
$converted = DateTime::createFromInterface($original);
$reminder = $converted->modify('+1 day');
echo 'Исходная встреча: ', $original->format('Y-m-d H:i:s.u e'), "\n";
echo 'Напоминание: ', $reminder->format('Y-m-d H:i:s.u e'), "\n";
echo 'Преобразованный вариант теперь: ', $converted->format('Y-m-d'), "\n";
Ожидаемый результат
Исходная встреча: 2024-06-15 10:30:00.125000 Europe/Moscow
Напоминание: 2024-06-16 10:30:00.125000 Europe/Moscow
Преобразованный вариант теперь: 2024-06-16

Фабрика получает original и возвращает независимый объект converted с теми же календарём, микросекундами и зоной. Поэтому дальнейший перенос не изменяет исходную встречу. Converted — DateTime: modify меняет его самого, reminder обозначает этот же результат16 июня. Сериализация, промежуточная строка format и повторный разбор для такого преобразования не нужны.

Проверить конкретный класс результатаСамостоятельный пример
<?php
declare(strict_types=1);

class LocalDate extends DateTime {}
$original = new DateTime('2024-01-01', new DateTimeZone('UTC'));
$converted = LocalDate::createFromInterface($original);
echo get_class($converted), "\n";
Ожидаемый результат
LocalDate

Проверка выполнена на PHP 8.5.11. createFromInterface фактически сохраняет класс вызова, хотя опубликованный возвращаемый тип указан как базовый. Это согласуется со stub PHP 8.5.11: в нём стоит TODO уточнить тип на static. У createFromMutable/Immutable изменение на класс вызова отдельно отмечено с PHP 8.0.

Фабрика копирует внутреннее значение даты. Она не вызывает пользовательский __construct и не переносит произвольные свойства исходного подкласса. Поэтому дополнительные данные заказа или ограничения подкласса нужно инициализировать отдельно; часто проще хранить DateTimeImmutable внутри своего объекта заказа.

Увидеть, почему фабрика даты не создаёт полноценную броньСамостоятельный пример
<?php
class BookingDate extends DateTime {
    public string $bookingId;
    public function __construct(string $bookingId) {
        $this->bookingId = $bookingId;
        parent::__construct('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    }
}
$source = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$converted = BookingDate::createFromInterface($source);
echo 'Фабрика создала дату: ', $converted->format('d.m.Y H:i'), "\n";
try { echo 'Бронь: ' . $converted->bookingId . "\n"; }
catch (Error $error) { echo "Номер брони отсутствует: конструктор BookingDate не выполнялся\n"; }
$booking = new BookingDate('B-41');
echo 'Обычное создание: ', $booking->bookingId, ', ', $booking->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Фабрика создала дату: 15.06.2024 10:30
Номер брони отсутствует: конструктор BookingDate не выполнялся
Обычное создание: B-41, 15.06.2024 10:30

Это отдельная ступень после наследования: extends создаёт подкласс, public string требует инициализированного поля, parent::__construct заполняет родительскую дату. Фабрика создаёт внутреннее календарное значение, обходя пользовательский конструктор, поэтому bookingId не заполнен. Чтение поля вызывает Error до echo готовой строки. Обычный new BookingDate(B-41) запускает конструктор и даёт настоящую подпись брони. Практически часто проще хранить объект даты в отдельном объекте брони, чем наследовать DateTime ради дополнительного поля.

Сравните: DateTime::__construct, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::createFromInterfaceПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.

PHP 8.0+ · date

public static function createFromInterface(DateTimeInterface $object): DateTimeImmutable

Получить независимый объект DateTimeImmutable с тем же моментом и зоной.

Часть вызоваДоговор
objectОбъект DateTimeInterface. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется.
ВозвратНовый DateTimeImmutable. В опубликованной сигнатуре указан базовый тип. В проверенном PHP 8.5.11 вызов через подкласс возвращает подкласс: базовый возвращаемый тип допускает потомка. Не следует выводить обратное только из отсутствия static в сигнатуре.
СмыслПерейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом.
Путь даты в первом примере
  1. original: 15 июня, 10:30:00.125000, Europe/Moscow
  2. DateTimeImmutable::createFromInterface(original) создаёт независимый converted
  3. modify(+1 day) даёт reminder16 июня
  4. original по-прежнему15 июня; зона и микросекунды сохранены
Перенести напоминание, сохранив время исходной встречиСамостоятельный пример
<?php
$original = new DateTime('2024-06-15 10:30:00.125000', new DateTimeZone('Europe/Moscow'));
$converted = DateTimeImmutable::createFromInterface($original);
$reminder = $converted->modify('+1 day');
echo 'Исходная встреча: ', $original->format('Y-m-d H:i:s.u e'), "\n";
echo 'Напоминание: ', $reminder->format('Y-m-d H:i:s.u e'), "\n";
echo 'Преобразованный вариант теперь: ', $converted->format('Y-m-d'), "\n";
Ожидаемый результат
Исходная встреча: 2024-06-15 10:30:00.125000 Europe/Moscow
Напоминание: 2024-06-16 10:30:00.125000 Europe/Moscow
Преобразованный вариант теперь: 2024-06-15

Фабрика получает original и возвращает независимый объект converted с теми же календарём, микросекундами и зоной. Поэтому дальнейший перенос не изменяет исходную встречу. Converted — Immutable: modify создаёт ещё один объект reminder, converted остаётся15 июня. Сериализация, промежуточная строка format и повторный разбор для такого преобразования не нужны.

Проверить конкретный класс результатаСамостоятельный пример
<?php
declare(strict_types=1);

class LocalDate extends DateTimeImmutable {}
$original = new DateTime('2024-01-01', new DateTimeZone('UTC'));
$converted = LocalDate::createFromInterface($original);
echo get_class($converted), "\n";
Ожидаемый результат
LocalDate

Проверка выполнена на PHP 8.5.11. createFromInterface фактически сохраняет класс вызова, хотя опубликованный возвращаемый тип указан как базовый. Это согласуется со stub PHP 8.5.11: в нём стоит TODO уточнить тип на static. У createFromMutable/Immutable изменение на класс вызова отдельно отмечено с PHP 8.0.

Фабрика копирует внутреннее значение даты. Она не вызывает пользовательский __construct и не переносит произвольные свойства исходного подкласса. Поэтому дополнительные данные заказа или ограничения подкласса нужно инициализировать отдельно; часто проще хранить DateTimeImmutable внутри своего объекта заказа.

Увидеть, почему фабрика даты не создаёт полноценную броньСамостоятельный пример
<?php
class BookingDate extends DateTimeImmutable {
    public string $bookingId;
    public function __construct(string $bookingId) {
        $this->bookingId = $bookingId;
        parent::__construct('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    }
}
$source = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$converted = BookingDate::createFromInterface($source);
echo 'Фабрика создала дату: ', $converted->format('d.m.Y H:i'), "\n";
try { echo 'Бронь: ' . $converted->bookingId . "\n"; }
catch (Error $error) { echo "Номер брони отсутствует: конструктор BookingDate не выполнялся\n"; }
$booking = new BookingDate('B-41');
echo 'Обычное создание: ', $booking->bookingId, ', ', $booking->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Фабрика создала дату: 15.06.2024 10:30
Номер брони отсутствует: конструктор BookingDate не выполнялся
Обычное создание: B-41, 15.06.2024 10:30

Это отдельная ступень после наследования: extends создаёт подкласс, public string требует инициализированного поля, parent::__construct заполняет родительскую дату. Фабрика создаёт внутреннее календарное значение, обходя пользовательский конструктор, поэтому bookingId не заполнен. Чтение поля вызывает Error до echo готовой строки. Обычный new BookingDate(B-41) запускает конструктор и даёт настоящую подпись брони. Практически часто проще хранить объект даты в отдельном объекте брони, чем наследовать DateTime ради дополнительного поля.

Сравните: DateTime::__construct, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

date_createПроцедурно создать объект даты и проверить false при неразбираемой строке.

PHP 5.2+ · date

function date_create(string $datetime = "now", ?DateTimeZone $timezone = null): DateTime|false

Процедурно создать объект даты и проверить false при неразбираемой строке.

Часть вызоваДоговор
datetime/timezoneТе же правила строки/зоны, что у DateTime::__construct: now по умолчанию, явная зона в строке и @timestamp имеют приоритет.
ВозвратDateTime при успехе или false при ошибке разбора. Это важное отличие от new, который выбрасывает исключение.
ПроверкаИспользуйте === false. Нормализация невозможной календарной даты может всё равно дать объект с предупреждением.
Путь даты в первом примере
  1. date_create получает известную дату и UTC
  2. Возврат DateTime попадает в date
  3. Проверка date===false ложна → else
  4. format → подтверждение встречи15.06.2024 10:30
Процедурная фабрика возвращает дату для подтвержденияСамостоятельный пример
<?php
$date = date_create('2024-06-15 10:30:00', new DateTimeZone('UTC'));
if ($date === false) {
    echo "Встреча не создана: исправьте дату\n";
} else {
    echo 'Встреча в UTC: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Встреча в UTC: 15.06.2024 10:30

В отличие от new, этот вызов имеет два обычных варианта результата: объект или false. Здесь получен DateTime, проверка false не сработала, else вызывает format и использует возвращённую строку в подтверждении. Процедурное имя не делает результат строкой. Обычные операции изменения сохраняют договор класса: mutable меняется, immutable возвращает новый вариант.

Отказ разбора оставляет заявку без подтверждённой датыСамостоятельный пример
<?php
$date = date_create('not-a-date-at-all', new DateTimeZone('UTC'));
$errors = date_get_last_errors();
if ($date === false) {
    echo "Заявка ожидает исправления даты\n";
    if ($errors !== false && $errors['error_count'] > 0) {
        echo "Причина: строка не соответствует грамматике даты\n";
    }
} else {
    echo 'Подтверждено: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Заявка ожидает исправления даты
Причина: строка не соответствует грамматике даты

Неразбираемая строка дала false, а date_get_last_errors сразу сохранил её диагностику. Ветка отказа не обращается к методу false и не подставляет сегодняшний день. Удачная ветка остаётся в полном примере, чтобы было видно место использования настоящего объекта. Предупреждения о календарном переполнении проверяют отдельно: объект может существовать, хотя исходный день был невозможен.

Сравните: DateTime::__construct, date_get_last_errors.

Время, часовые пояса и календарь. Практические задачи с датами.

date_create_immutableПроцедурно создать объект даты и проверить false при неразбираемой строке.

PHP 5.5+ · date

function date_create_immutable(string $datetime = "now", ?DateTimeZone $timezone = null): DateTimeImmutable|false

Процедурно создать объект даты и проверить false при неразбираемой строке.

Часть вызоваДоговор
datetime/timezoneТе же правила строки/зоны, что у DateTimeImmutable::__construct: now по умолчанию, явная зона в строке и @timestamp имеют приоритет.
ВозвратDateTimeImmutable при успехе или false при ошибке разбора. Это важное отличие от new, который выбрасывает исключение.
ПроверкаИспользуйте === false. Нормализация невозможной календарной даты может всё равно дать объект с предупреждением.
Путь даты в первом примере
  1. date_create_immutable получает известную дату и UTC
  2. Возврат DateTimeImmutable попадает в date
  3. Проверка date===false ложна → else
  4. format → подтверждение встречи15.06.2024 10:30
Процедурная фабрика возвращает дату для подтвержденияСамостоятельный пример
<?php
$date = date_create_immutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
if ($date === false) {
    echo "Встреча не создана: исправьте дату\n";
} else {
    echo 'Встреча в UTC: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Встреча в UTC: 15.06.2024 10:30

В отличие от new, этот вызов имеет два обычных варианта результата: объект или false. Здесь получен DateTimeImmutable, проверка false не сработала, else вызывает format и использует возвращённую строку в подтверждении. Процедурное имя не делает результат строкой. Обычные операции изменения сохраняют договор класса: mutable меняется, immutable возвращает новый вариант.

Отказ разбора оставляет заявку без подтверждённой датыСамостоятельный пример
<?php
$date = date_create_immutable('not-a-date-at-all', new DateTimeZone('UTC'));
$errors = date_get_last_errors();
if ($date === false) {
    echo "Заявка ожидает исправления даты\n";
    if ($errors !== false && $errors['error_count'] > 0) {
        echo "Причина: строка не соответствует грамматике даты\n";
    }
} else {
    echo 'Подтверждено: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Заявка ожидает исправления даты
Причина: строка не соответствует грамматике даты

Неразбираемая строка дала false, а date_get_last_errors сразу сохранил её диагностику. Ветка отказа не обращается к методу false и не подставляет сегодняшний день. Удачная ветка остаётся в полном примере, чтобы было видно место использования настоящего объекта. Предупреждения о календарном переполнении проверяют отдельно: объект может существовать, хотя исходный день был невозможен.

Сравните: DateTimeImmutable::__construct, date_get_last_errors.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::createFromFormatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

PHP 5.3+ · date

public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false

Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

Часть вызоваДоговор
formatШаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы.
datetimeВходная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться.
timezoneDateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP.
ВозвратDateTime или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря.
ОшибкиNULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors.
Путь даты в первом примере
  1. input содержит 15.06.2024
  2. DateTime::createFromFormat читает !d.m.Y и получает UTC
  3. date содержит объект 2024-06-15 00:00:00
  4. format возвращает строку для подписи заявки

Шаблон ввода описывает, какую часть строки читать: d — день, m — месяц, Y — год. ! предотвращает наследование случайных часов текущего момента. Без !/| незаданные поля берутся из текущего времени; если разобрано хотя бы одно поле времени, остальные временные поля обнуляются. Не переносите буквально все символы форматирования вывода: наборы похожи, но договоры различаются.

Из даты поля получить подпись для заявкиСамостоятельный пример
<?php
$input = '15.06.2024';
$date = DateTime::createFromFormat('!d.m.Y', $input, new DateTimeZone('UTC'));
if ($date === false) {
    echo "Заявка не принята: дату не удалось разобрать\n";
} else {
    echo 'День заявки: ', $date->format('Y-m-d H:i:s e'), "\n";
}
Ожидаемый результат
День заявки: 2024-06-15 00:00:00 UTC

Format !d.m.Y связывает первые две цифры с днём15, следующие — с месяцем6, последние — с годом2024. ! даёт предсказуемые нулевые часы. Фабрика возвращает DateTime в date, else вызывает format и использует его строку как подпись заявки. Этот первый вход задан автором; проверка false ещё не достаточна для произвольного текста формы.

Показать исходный и исправленный день перед отказомСамостоятельный пример
<?php
$input = '30.02.2024';
$date = DateTime::createFromFormat('!d.m.Y', $input, new DateTimeZone('UTC'));
$issues = DateTime::getLastErrors();
$valid = $date !== false
    && ($issues === false || ($issues['warning_count'] === 0 && $issues['error_count'] === 0))
    && $date->format('d.m.Y') === $input;
echo 'В поле: ', $input, "\n";
if ($date !== false) { echo 'Парсер построил: ', $date->format('d.m.Y'), "\n"; }
if ($valid) { echo "День можно использовать в заявке\n"; }
else { echo "Заявка остановлена: исправьте исходную дату\n"; }
Ожидаемый результат
В поле: 30.02.2024
Парсер построил: 01.03.2024
Заявка остановлена: исправьте исходную дату

30 февраля превратилось в1 марта, поэтому предупреждение делает valid=false. Нормализованный объект показан для объяснения, но заявка не использует его как согласованный день. && прекращает проверку после false, поэтому исходный date=false не получил бы вызов format. Дополнительно проверяются точный обратный формат, тип исходного поля, допустимая длина и нулевые байты; полный самостоятельный обработчик есть в базовом маршруте дат.

Позиция ! меняет уже прочитанные поляСамостоятельный пример
<?php
declare(strict_types=1);

$date = DateTime::createFromFormat('Y-m-!d', '2024-06-15', new DateTimeZone('UTC'));
echo $date->format('Y-m-d H:i:s'), "\n";
$kept = DateTime::createFromFormat('Y-m-d|', '2024-06-15', new DateTimeZone('UTC'));
echo $kept->format('Y-m-d H:i:s'), "\n";
Ожидаемый результат
1970-01-15 00:00:00
2024-06-15 00:00:00

! посередине сбросил уже разобранные год/месяц. | сохранил прочитанные поля. Обычно для поля даты удобнее ! в самом начале.

Не принять лишний хвост как часть согласованного поляСамостоятельный пример
<?php
$input = '2024-06-15 imported';
$date = DateTime::createFromFormat('!Y-m-d+', $input, new DateTimeZone('UTC'));
$issues = DateTime::getLastErrors();
echo 'Получено поле: ', $input, "\n";
if ($date !== false) { echo 'Распознан день: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Импорт отложен: в поле остался лишний хвост\n";
}
Ожидаемый результат
Получено поле: 2024-06-15 imported
Распознан день: 2024-06-15
Импорт отложен: в поле остался лишний хвост

Плюс в конце шаблона разрешил создать объект при лишнем хвосте, заменив ошибку предупреждением. Он не проверил смысл слова imported и не сделал его допустимым по договору импорта. Здесь выбран отказ. Если ваш формат намеренно хранит дату и отметку, разберите и проверьте оба поля по отдельности вместо добавления+ ради исчезновения false.

NULL-байт не игнорируетсяСамостоятельный пример
<?php
declare(strict_types=1);

try { DateTime::createFromFormat('!Y-m-d', "2024-06-15\0"); }
catch (ValueError $error) { echo "Недопустимый байт во входе\n"; }
Ожидаемый результат
Недопустимый байт во входе

Это отдельный путь исключения поверх возвращаемого false. Проверка типов, ограничение длины и контроль допустимых байтов делают обработчик предсказуемым.

Полный набор символов: таблица разбора формата. Если нужны русские названия месяцев, используйте осмысленный локализованный парсер, например IntlDateFormatter, с явной локалью; замена регистра не превращает русский текст в английский.

Сравните: DateTime::__construct, DateTime::getLastErrors, date_parse_from_format, checkdate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_create_from_formatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

PHP 5.3+ · date

function date_create_from_format(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|false

Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

Часть вызоваДоговор
formatШаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы.
datetimeВходная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться.
timezoneDateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP.
ВозвратDateTime или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря.
ОшибкиNULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors.
Путь даты в первом примере
  1. input содержит 15.06.2024
  2. date_create_from_format читает !d.m.Y и получает UTC
  3. date содержит объект 2024-06-15 00:00:00
  4. format возвращает строку для подписи заявки

Шаблон ввода описывает, какую часть строки читать: d — день, m — месяц, Y — год. ! предотвращает наследование случайных часов текущего момента. Без !/| незаданные поля берутся из текущего времени; если разобрано хотя бы одно поле времени, остальные временные поля обнуляются. Не переносите буквально все символы форматирования вывода: наборы похожи, но договоры различаются.

Из даты поля получить подпись для заявкиСамостоятельный пример
<?php
$input = '15.06.2024';
$date = date_create_from_format('!d.m.Y', $input, new DateTimeZone('UTC'));
if ($date === false) {
    echo "Заявка не принята: дату не удалось разобрать\n";
} else {
    echo 'День заявки: ', $date->format('Y-m-d H:i:s e'), "\n";
}
Ожидаемый результат
День заявки: 2024-06-15 00:00:00 UTC

Format !d.m.Y связывает первые две цифры с днём15, следующие — с месяцем6, последние — с годом2024. ! даёт предсказуемые нулевые часы. Фабрика возвращает DateTime в date, else вызывает format и использует его строку как подпись заявки. Этот первый вход задан автором; проверка false ещё не достаточна для произвольного текста формы.

Показать исходный и исправленный день перед отказомСамостоятельный пример
<?php
$input = '30.02.2024';
$date = date_create_from_format('!d.m.Y', $input, new DateTimeZone('UTC'));
$issues = DateTime::getLastErrors();
$valid = $date !== false
    && ($issues === false || ($issues['warning_count'] === 0 && $issues['error_count'] === 0))
    && $date->format('d.m.Y') === $input;
echo 'В поле: ', $input, "\n";
if ($date !== false) { echo 'Парсер построил: ', $date->format('d.m.Y'), "\n"; }
if ($valid) { echo "День можно использовать в заявке\n"; }
else { echo "Заявка остановлена: исправьте исходную дату\n"; }
Ожидаемый результат
В поле: 30.02.2024
Парсер построил: 01.03.2024
Заявка остановлена: исправьте исходную дату

30 февраля превратилось в1 марта, поэтому предупреждение делает valid=false. Нормализованный объект показан для объяснения, но заявка не использует его как согласованный день. && прекращает проверку после false, поэтому исходный date=false не получил бы вызов format. Дополнительно проверяются точный обратный формат, тип исходного поля, допустимая длина и нулевые байты; полный самостоятельный обработчик есть в базовом маршруте дат.

Позиция ! меняет уже прочитанные поляСамостоятельный пример
<?php
declare(strict_types=1);

$date = date_create_from_format('Y-m-!d', '2024-06-15', new DateTimeZone('UTC'));
echo $date->format('Y-m-d H:i:s'), "\n";
$kept = date_create_from_format('Y-m-d|', '2024-06-15', new DateTimeZone('UTC'));
echo $kept->format('Y-m-d H:i:s'), "\n";
Ожидаемый результат
1970-01-15 00:00:00
2024-06-15 00:00:00

! посередине сбросил уже разобранные год/месяц. | сохранил прочитанные поля. Обычно для поля даты удобнее ! в самом начале.

Не принять лишний хвост как часть согласованного поляСамостоятельный пример
<?php
$input = '2024-06-15 imported';
$date = date_create_from_format('!Y-m-d+', $input, new DateTimeZone('UTC'));
$issues = DateTime::getLastErrors();
echo 'Получено поле: ', $input, "\n";
if ($date !== false) { echo 'Распознан день: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Импорт отложен: в поле остался лишний хвост\n";
}
Ожидаемый результат
Получено поле: 2024-06-15 imported
Распознан день: 2024-06-15
Импорт отложен: в поле остался лишний хвост

Плюс в конце шаблона разрешил создать объект при лишнем хвосте, заменив ошибку предупреждением. Он не проверил смысл слова imported и не сделал его допустимым по договору импорта. Здесь выбран отказ. Если ваш формат намеренно хранит дату и отметку, разберите и проверьте оба поля по отдельности вместо добавления+ ради исчезновения false.

NULL-байт не игнорируетсяСамостоятельный пример
<?php
declare(strict_types=1);

try { date_create_from_format('!Y-m-d', "2024-06-15\0"); }
catch (ValueError $error) { echo "Недопустимый байт во входе\n"; }
Ожидаемый результат
Недопустимый байт во входе

Это отдельный путь исключения поверх возвращаемого false. Проверка типов, ограничение длины и контроль допустимых байтов делают обработчик предсказуемым.

Полный набор символов: таблица разбора формата. Если нужны русские названия месяцев, используйте осмысленный локализованный парсер, например IntlDateFormatter, с явной локалью; замена регистра не превращает русский текст в английский.

Сравните: DateTime::__construct, DateTime::getLastErrors, date_parse_from_format, checkdate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_create_immutable_from_formatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

PHP 5.5+ · date

function date_create_immutable_from_format(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTimeImmutable|false

Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.

Часть вызоваДоговор
formatШаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы.
datetimeВходная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться.
timezoneDateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP.
ВозвратDateTimeImmutable или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря.
ОшибкиNULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors.
Путь даты в первом примере
  1. input содержит 15.06.2024
  2. date_create_immutable_from_format читает !d.m.Y и получает UTC
  3. date содержит объект 2024-06-15 00:00:00
  4. format возвращает строку для подписи заявки

Шаблон ввода описывает, какую часть строки читать: d — день, m — месяц, Y — год. ! предотвращает наследование случайных часов текущего момента. Без !/| незаданные поля берутся из текущего времени; если разобрано хотя бы одно поле времени, остальные временные поля обнуляются. Не переносите буквально все символы форматирования вывода: наборы похожи, но договоры различаются.

Из даты поля получить подпись для заявкиСамостоятельный пример
<?php
$input = '15.06.2024';
$date = date_create_immutable_from_format('!d.m.Y', $input, new DateTimeZone('UTC'));
if ($date === false) {
    echo "Заявка не принята: дату не удалось разобрать\n";
} else {
    echo 'День заявки: ', $date->format('Y-m-d H:i:s e'), "\n";
}
Ожидаемый результат
День заявки: 2024-06-15 00:00:00 UTC

Format !d.m.Y связывает первые две цифры с днём15, следующие — с месяцем6, последние — с годом2024. ! даёт предсказуемые нулевые часы. Фабрика возвращает DateTimeImmutable в date, else вызывает format и использует его строку как подпись заявки. Этот первый вход задан автором; проверка false ещё не достаточна для произвольного текста формы.

Показать исходный и исправленный день перед отказомСамостоятельный пример
<?php
$input = '30.02.2024';
$date = date_create_immutable_from_format('!d.m.Y', $input, new DateTimeZone('UTC'));
$issues = DateTimeImmutable::getLastErrors();
$valid = $date !== false
    && ($issues === false || ($issues['warning_count'] === 0 && $issues['error_count'] === 0))
    && $date->format('d.m.Y') === $input;
echo 'В поле: ', $input, "\n";
if ($date !== false) { echo 'Парсер построил: ', $date->format('d.m.Y'), "\n"; }
if ($valid) { echo "День можно использовать в заявке\n"; }
else { echo "Заявка остановлена: исправьте исходную дату\n"; }
Ожидаемый результат
В поле: 30.02.2024
Парсер построил: 01.03.2024
Заявка остановлена: исправьте исходную дату

30 февраля превратилось в1 марта, поэтому предупреждение делает valid=false. Нормализованный объект показан для объяснения, но заявка не использует его как согласованный день. && прекращает проверку после false, поэтому исходный date=false не получил бы вызов format. Дополнительно проверяются точный обратный формат, тип исходного поля, допустимая длина и нулевые байты; полный самостоятельный обработчик есть в базовом маршруте дат.

Позиция ! меняет уже прочитанные поляСамостоятельный пример
<?php
declare(strict_types=1);

$date = date_create_immutable_from_format('Y-m-!d', '2024-06-15', new DateTimeZone('UTC'));
echo $date->format('Y-m-d H:i:s'), "\n";
$kept = date_create_immutable_from_format('Y-m-d|', '2024-06-15', new DateTimeZone('UTC'));
echo $kept->format('Y-m-d H:i:s'), "\n";
Ожидаемый результат
1970-01-15 00:00:00
2024-06-15 00:00:00

! посередине сбросил уже разобранные год/месяц. | сохранил прочитанные поля. Обычно для поля даты удобнее ! в самом начале.

Не принять лишний хвост как часть согласованного поляСамостоятельный пример
<?php
$input = '2024-06-15 imported';
$date = date_create_immutable_from_format('!Y-m-d+', $input, new DateTimeZone('UTC'));
$issues = DateTimeImmutable::getLastErrors();
echo 'Получено поле: ', $input, "\n";
if ($date !== false) { echo 'Распознан день: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Импорт отложен: в поле остался лишний хвост\n";
}
Ожидаемый результат
Получено поле: 2024-06-15 imported
Распознан день: 2024-06-15
Импорт отложен: в поле остался лишний хвост

Плюс в конце шаблона разрешил создать объект при лишнем хвосте, заменив ошибку предупреждением. Он не проверил смысл слова imported и не сделал его допустимым по договору импорта. Здесь выбран отказ. Если ваш формат намеренно хранит дату и отметку, разберите и проверьте оба поля по отдельности вместо добавления+ ради исчезновения false.

NULL-байт не игнорируетсяСамостоятельный пример
<?php
declare(strict_types=1);

try { date_create_immutable_from_format('!Y-m-d', "2024-06-15\0"); }
catch (ValueError $error) { echo "Недопустимый байт во входе\n"; }
Ожидаемый результат
Недопустимый байт во входе

Это отдельный путь исключения поверх возвращаемого false. Проверка типов, ограничение длины и контроль допустимых байтов делают обработчик предсказуемым.

Полный набор символов: таблица разбора формата. Если нужны русские названия месяцев, используйте осмысленный локализованный парсер, например IntlDateFormatter, с явной локалью; замена регистра не превращает русский текст в английский.

Сравните: DateTimeImmutable::__construct, DateTimeImmutable::getLastErrors, date_parse_from_format, checkdate.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::getLastErrorsСразу прочитать предупреждения и ошибки последнего разбора даты.

PHP 5.3+ · date

public static function getLastErrors(): array|false

Сразу прочитать предупреждения и ошибки последнего разбора даты.

Часть вызоваДоговор
АргументыНет. Это состояние последнего разбора, а не поле выбранного объекта.
УспехС PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками.
Диагностикаwarning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива.
Порядок вызововСохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику.
Путь даты в первом примере
  1. createFromFormat читает невозможное 2024-02-30
  2. Объект становится 2024-03-01, появляется предупреждение
  3. DateTime::getLastErrors() сохраняет массив в issues
  4. warning_count>0 → отказ для исходного поля
Предупреждение объясняет отказ конкретного календарного дняСамостоятельный пример
<?php
$input = '2024-02-30';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$issues = DateTime::getLastErrors();
echo 'Введено: ', $input, "\n";
if ($date !== false) { echo 'Получено у парсера: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Не принимаем дату: парсер сообщил о календарном исправлении\n";
}
Ожидаемый результат
Введено: 2024-02-30
Получено у парсера: 2024-03-01
Не принимаем дату: парсер сообщил о календарном исправлении

GetLastErrors не получает date параметром: он читает состояние последней операции разбора. Возвращённый массив сохранён в issues. У этого входа объект есть и error_count=0, однако предупреждение запрещает использовать его как исходный день. Поэтому проверять только ошибки недостаточно.

Сохранить причину первого отказа после успешного второго разбораСамостоятельный пример
<?php
$first = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-30', new DateTimeZone('UTC'));
$saved = DateTime::getLastErrors();
$second = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-29', new DateTimeZone('UTC'));
$latest = DateTime::getLastErrors();
if ($second !== false && $latest === false) {
    echo 'Второе поле принято: ', $second->format('d.m.Y'), "\n";
}
if ($saved !== false && $saved['warning_count'] > 0) {
    echo "Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте\n";
}
if ($latest === false) { echo "Текущая диагностика уже не содержит первого предупреждения\n"; }
Ожидаемый результат
Второе поле принято: 29.02.2024
Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте
Текущая диагностика уже не содержит первого предупреждения

После первого разбора saved получил самостоятельную копию массива предупреждений. Следующий успешный разбор очистил общую диагностику: latest=false в PHP8.2+. Это не изменило saved, поэтому отчёт ещё объясняет отказ первого поля. Сохраняйте диагностику вместе с соответствующим исходным текстом до любого следующего создания или разбора даты.

Сравните: DateTimeImmutable::createFromFormat, date_parse.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::getLastErrorsСразу прочитать предупреждения и ошибки последнего разбора даты.

PHP 5.5+ · date

public static function getLastErrors(): array|false

Сразу прочитать предупреждения и ошибки последнего разбора даты.

Часть вызоваДоговор
АргументыНет. Это состояние последнего разбора, а не поле выбранного объекта.
УспехС PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками.
Диагностикаwarning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива.
Порядок вызововСохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику.
Путь даты в первом примере
  1. createFromFormat читает невозможное 2024-02-30
  2. Объект становится 2024-03-01, появляется предупреждение
  3. DateTimeImmutable::getLastErrors() сохраняет массив в issues
  4. warning_count>0 → отказ для исходного поля
Предупреждение объясняет отказ конкретного календарного дняСамостоятельный пример
<?php
$input = '2024-02-30';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$issues = DateTimeImmutable::getLastErrors();
echo 'Введено: ', $input, "\n";
if ($date !== false) { echo 'Получено у парсера: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Не принимаем дату: парсер сообщил о календарном исправлении\n";
}
Ожидаемый результат
Введено: 2024-02-30
Получено у парсера: 2024-03-01
Не принимаем дату: парсер сообщил о календарном исправлении

GetLastErrors не получает date параметром: он читает состояние последней операции разбора. Возвращённый массив сохранён в issues. У этого входа объект есть и error_count=0, однако предупреждение запрещает использовать его как исходный день. Поэтому проверять только ошибки недостаточно.

Сохранить причину первого отказа после успешного второго разбораСамостоятельный пример
<?php
$first = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-30', new DateTimeZone('UTC'));
$saved = DateTimeImmutable::getLastErrors();
$second = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-29', new DateTimeZone('UTC'));
$latest = DateTimeImmutable::getLastErrors();
if ($second !== false && $latest === false) {
    echo 'Второе поле принято: ', $second->format('d.m.Y'), "\n";
}
if ($saved !== false && $saved['warning_count'] > 0) {
    echo "Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте\n";
}
if ($latest === false) { echo "Текущая диагностика уже не содержит первого предупреждения\n"; }
Ожидаемый результат
Второе поле принято: 29.02.2024
Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте
Текущая диагностика уже не содержит первого предупреждения

После первого разбора saved получил самостоятельную копию массива предупреждений. Следующий успешный разбор очистил общую диагностику: latest=false в PHP8.2+. Это не изменило saved, поэтому отчёт ещё объясняет отказ первого поля. Сохраняйте диагностику вместе с соответствующим исходным текстом до любого следующего создания или разбора даты.

Сравните: DateTimeImmutable::createFromFormat, date_parse.

Время, часовые пояса и календарь. Практические задачи с датами.

date_get_last_errorsСразу прочитать предупреждения и ошибки последнего разбора даты.

PHP 5.3+ · date

function date_get_last_errors(): array|false

Сразу прочитать предупреждения и ошибки последнего разбора даты.

Часть вызоваДоговор
АргументыНет. Это состояние последнего разбора, а не поле выбранного объекта.
УспехС PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками.
Диагностикаwarning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива.
Порядок вызововСохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику.
Путь даты в первом примере
  1. createFromFormat читает невозможное 2024-02-30
  2. Объект становится 2024-03-01, появляется предупреждение
  3. date_get_last_errors() сохраняет массив в issues
  4. warning_count>0 → отказ для исходного поля
Предупреждение объясняет отказ конкретного календарного дняСамостоятельный пример
<?php
$input = '2024-02-30';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$issues = date_get_last_errors();
echo 'Введено: ', $input, "\n";
if ($date !== false) { echo 'Получено у парсера: ', $date->format('Y-m-d'), "\n"; }
if ($issues !== false && $issues['warning_count'] > 0) {
    echo "Не принимаем дату: парсер сообщил о календарном исправлении\n";
}
Ожидаемый результат
Введено: 2024-02-30
Получено у парсера: 2024-03-01
Не принимаем дату: парсер сообщил о календарном исправлении

GetLastErrors не получает date параметром: он читает состояние последней операции разбора. Возвращённый массив сохранён в issues. У этого входа объект есть и error_count=0, однако предупреждение запрещает использовать его как исходный день. Поэтому проверять только ошибки недостаточно.

Сохранить причину первого отказа после успешного второго разбораСамостоятельный пример
<?php
$first = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-30', new DateTimeZone('UTC'));
$saved = date_get_last_errors();
$second = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-29', new DateTimeZone('UTC'));
$latest = date_get_last_errors();
if ($second !== false && $latest === false) {
    echo 'Второе поле принято: ', $second->format('d.m.Y'), "\n";
}
if ($saved !== false && $saved['warning_count'] > 0) {
    echo "Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте\n";
}
if ($latest === false) { echo "Текущая диагностика уже не содержит первого предупреждения\n"; }
Ожидаемый результат
Второе поле принято: 29.02.2024
Первое поле 2024-02-30 осталось отклонённым в сохранённом отчёте
Текущая диагностика уже не содержит первого предупреждения

После первого разбора saved получил самостоятельную копию массива предупреждений. Следующий успешный разбор очистил общую диагностику: latest=false в PHP8.2+. Это не изменило saved, поэтому отчёт ещё объясняет отказ первого поля. Сохраняйте диагностику вместе с соответствующим исходным текстом до любого следующего создания или разбора даты.

Сравните: DateTimeImmutable::createFromFormat, date_parse.

Время, часовые пояса и календарь. Практические задачи с датами.

date_parseРазобрать строку в массив обнаруженных компонентов и диагностики.

PHP 5.2+ · date

function date_parse(string $datetime): array

Разобрать строку в массив обнаруженных компонентов и диагностики.

Часть вызоваДоговор
datetimeСтрока даты; правила свободного разбора как у конструктора/strtotime.
РезультатАссоциативный массив: year, month, day, hour, minute, second, fraction, is_localtime. Неизвестные поля бывают false или отсутствуют. Это не объект готовой нормализованной даты.
ЗонаПри данных зоны zone_type: 1 — смещение с zone/is_dst; 2 — сокращение с tz_abbr/is_dst; 3 — именованная зона с tz_id и соответствующими сведениями. С PHP 7.2 zone задаётся секундами с современным знаком (например +03:00 → 10800), прежде были минуты с обратным знаком.
Диагностикаwarning_count/warnings, error_count/errors; число уникальных позиций сообщений может быть меньше счётчика.
ОтносительностьУ date_parse относительные элементы дают вложенный relative: year/month/day/hour/minute/second и при необходимости weekday/weekdays. Эти числа ещё надо применить к конкретной исходной дате.
Путь даты в первом примере
  1. Строка содержит дату, 10:30 и смещение +03:00
  2. date_parse возвращает ассоциативный parts
  3. year/month/day → sprintf → events-2024-06-15.csv
  4. zone10800 секунд /3600 → подпись смещения3ч

Нужен отчёт по местной календарной дате события. В этом отдельном опыте все компоненты заранее присутствуют в известной строке. Сначала проверим диагностику, затем используем year/month/day как числовые части имени файла; это не ручное вычисление Unix-метки.

Использовать выделенные компоненты для имени отчётаСамостоятельный пример
<?php
$parts = date_parse('2024-06-15 10:30:00+03:00');
if ($parts['error_count'] > 0 || $parts['warning_count'] > 0) {
    echo "Компоненты не приняты\n";
    return;
}
$reportName = sprintf('events-%04d-%02d-%02d.csv', $parts['year'], $parts['month'], $parts['day']);
echo 'Имя отчёта: ', $reportName, "\n";
echo 'Местные часы события: ', $parts['hour'], ':', $parts['minute'], "\n";
echo 'Смещение: ', $parts['zone'], ' секунд, то есть ', $parts['zone'] / 3600, ' ч', "\n";
Ожидаемый результат
Имя отчёта: events-2024-06-15.csv
Местные часы события: 10:30
Смещение: 10800 секунд, то есть 3 ч

Парсер возвращает массив, а не DateTime. У известного входа заполнены year2024/month6/day15/hour10/minute30/zone10800. Sprintf получает шаблон и три числа: %04d пишет год четырьмя цифрами, %02d — месяц и день двумя. Возврат сохраняется в reportName и выводится; файл не создаётся. Смещение поделено на3600 для понятных часов. Для произвольного формата отдельно проверяют наличие нужных компонентов: неизвестное поле может быть false, даже без синтаксической ошибки.

Исходные компоненты объясняют причину отказа импортаСамостоятельный пример
<?php
$parts = date_parse('2024-02-30');
echo 'Парсер выделил месяц ', $parts['month'], ', день ', $parts['day'], "\n";
if ($parts['warning_count'] > 0 || $parts['error_count'] > 0) {
    echo "Отчёт не готовится: исходный день не прошёл проверку\n";
}
Ожидаемый результат
Парсер выделил месяц 2, день 30
Отчёт не готовится: исходный день не прошёл проверку

В этом массиве видны исходные2 и30, а не нормализованный мартовский объект. Предупреждение становится причиной остановить подготовку отчёта. Наличие ключей month/day само по себе не подтверждает существование даты. Это удобно для диагностики импортера, но готовый момент с зоной нужно создавать только после проверки договора.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::__construct, checkdate.

Время, часовые пояса и календарь. Практические задачи с датами.

Отдельно показать правило и полученную по нему датуСамостоятельный пример
<?php
$text = '2024-06-15 +3 days';
$parts = date_parse($text);
echo 'Базовая запись: ', $parts['year'], '-', $parts['month'], '-', $parts['day'], "\n";
echo 'Относительное правило: плюс ', $parts['relative']['day'], ' дня', "\n";
$resolved = new DateTimeImmutable($text, new DateTimeZone('UTC'));
echo 'Рассчитанный срок: ', $resolved->format('d.m.Y'), "\n";
Ожидаемый результат
Базовая запись: 2024-6-15
Относительное правило: плюс 3 дня
Рассчитанный срок: 18.06.2024

Date_parse разделил известную строку на базовые компоненты и вложенный relative, но не заменил day15 итоговым18. Затем конструктор отдельно применил ту же полную строку и вернул объект18 июня. Два разбора здесь намеренны для сравнения API; в прикладном расчёте обычно выбирают нужный результат один раз. Для произвольных relative нельзя учитывать только day: возможны другие поля и правила дней недели.

date_parse_from_formatРазобрать строку в массив обнаруженных компонентов и диагностики.

PHP 5.3+ · date

function date_parse_from_format (string $format, string $datetime): array

Разобрать строку в массив обнаруженных компонентов и диагностики.

Часть вызоваДоговор
formatТот же язык шаблона, что createFromFormat; явный порядок полей.
datetimeСтрока даты; шаблон задан первым аргументом.
РезультатАссоциативный массив: year, month, day, hour, minute, second, fraction, is_localtime. Неизвестные поля бывают false или отсутствуют. Это не объект готовой нормализованной даты.
ЗонаПри данных зоны zone_type: 1 — смещение с zone/is_dst; 2 — сокращение с tz_abbr/is_dst; 3 — именованная зона с tz_id и соответствующими сведениями. С PHP 7.2 zone задаётся секундами с современным знаком (например +03:00 → 10800), прежде были минуты с обратным знаком.
Диагностикаwarning_count/warnings, error_count/errors; число уникальных позиций сообщений может быть меньше счётчика.
ОтносительностьУ date_parse относительные элементы дают вложенный relative: year/month/day/hour/minute/second и при необходимости weekday/weekdays. Эти числа ещё надо применить к конкретной исходной дате.
Путь даты в первом примере
  1. Строка содержит дату, 10:30 и смещение +03:00
  2. date_parse_from_format возвращает ассоциативный parts
  3. year/month/day → sprintf → events-2024-06-15.csv
  4. zone10800 секунд /3600 → подпись смещения3ч

Нужен отчёт по местной календарной дате события. В этом отдельном опыте все компоненты заранее присутствуют в известной строке. Сначала проверим диагностику, затем используем year/month/day как числовые части имени файла; это не ручное вычисление Unix-метки.

Использовать выделенные компоненты для имени отчётаСамостоятельный пример
<?php
$parts = date_parse_from_format('d.m.Y H:iP', '15.06.2024 10:30+03:00');
if ($parts['error_count'] > 0 || $parts['warning_count'] > 0) {
    echo "Компоненты не приняты\n";
    return;
}
$reportName = sprintf('events-%04d-%02d-%02d.csv', $parts['year'], $parts['month'], $parts['day']);
echo 'Имя отчёта: ', $reportName, "\n";
echo 'Местные часы события: ', $parts['hour'], ':', $parts['minute'], "\n";
echo 'Смещение: ', $parts['zone'], ' секунд, то есть ', $parts['zone'] / 3600, ' ч', "\n";
Ожидаемый результат
Имя отчёта: events-2024-06-15.csv
Местные часы события: 10:30
Смещение: 10800 секунд, то есть 3 ч

Парсер возвращает массив, а не DateTime. У известного входа заполнены year2024/month6/day15/hour10/minute30/zone10800. Sprintf получает шаблон и три числа: %04d пишет год четырьмя цифрами, %02d — месяц и день двумя. Возврат сохраняется в reportName и выводится; файл не создаётся. Смещение поделено на3600 для понятных часов. Для произвольного формата отдельно проверяют наличие нужных компонентов: неизвестное поле может быть false, даже без синтаксической ошибки.

Исходные компоненты объясняют причину отказа импортаСамостоятельный пример
<?php
$parts = date_parse_from_format('Y-m-d', '2024-02-30');
echo 'Парсер выделил месяц ', $parts['month'], ', день ', $parts['day'], "\n";
if ($parts['warning_count'] > 0 || $parts['error_count'] > 0) {
    echo "Отчёт не готовится: исходный день не прошёл проверку\n";
}
Ожидаемый результат
Парсер выделил месяц 2, день 30
Отчёт не готовится: исходный день не прошёл проверку

В этом массиве видны исходные2 и30, а не нормализованный мартовский объект. Предупреждение становится причиной остановить подготовку отчёта. Наличие ключей month/day само по себе не подтверждает существование даты. Это удобно для диагностики импортера, но готовый момент с зоной нужно создавать только после проверки договора.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::__construct, checkdate.

Время, часовые пояса и календарь. Практические задачи с датами.

NULL-байт вызывает ValueError и у массива разбораСамостоятельный пример
<?php
declare(strict_types=1);

try { date_parse_from_format('Y-m-d', "2024-06-15\0"); }
catch (ValueError $error) { echo "Недопустимый байт\n"; }
Ожидаемый результат
Недопустимый байт

Это поведение введено в PHP 8.0.21 / 8.1.8 / 8.2.0. До этих исправлений NULL-байты игнорировались.

DateTimeInterface::formatПолучить строку даты для человека или согласованного внешнего формата.

PHP 5.5+ · date

public function format(string $format): string

Получить строку даты для человека или согласованного внешнего формата.

Часть вызоваДоговор
formatСтрока шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов.
ВозвратСтрока; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону.
ЯзыкНазвания дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter.
Доли секундыu/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её.
Путь даты в первом примере
  1. date = 15 июня 2024, 10:30 UTC
  2. Шаблон d.m.Y H:i выбирает день, месяц, год и часы
  3. format возвращает строку 15.06.2024 10:30 в label
  4. echo выводит подтверждение встречи

Шаблон — не регулярное выражение и не перевод даты. Каждый распознанный символ заменяется полем объекта. Например, m означает месяц, а i — минуты. Чтобы вывести буквальную T, используйте \T. Перед отправкой HTTP-заголовка с буквальным GMT сначала действительно переведите объект в UTC: подпись зоны сама по себе часы не переводит. Полная таблица всех букв формата.

В подтверждении нужна удобная подпись, а в заявке — календарный день. Создаём одну известную дату в UTC; затем выбираем, какие её поля показать. Запись date->format(...) вызывает метод у даты; внутри скобок передаётся шаблон. DateTimeInterface описывает общий договор. Сам интерфейс не создаём: ниже используется DateTimeImmutable, который его выполняет. Возвращаемое значение — обычная строка: её можно сохранить, соединить с подписью или передать в дальнейший код.

Вывести время встречи в подтвержденииСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$label = $date->format('d.m.Y H:i');
echo 'Встреча: ', $label, ' UTC', "\n";
echo 'День для заявки: ', $date->format('Y-m-d'), "\n";
Ожидаемый результат
Встреча: 15.06.2024 10:30 UTC
День для заявки: 2024-06-15

Первый format возвращает строку 15.06.2024 10:30 в label. Echo добавляет назначение и зону. Второй вызов читает тот же объект и возвращает 2024-06-15 прямо в ожидающий echo. Точки, дефисы, двоеточие и пробел — разделители; d/m/Y означают день/месяц/год, H/i — час/минуту. Объект и его часы не изменились.

Исправить подпись, которая выглядит как неверные часыСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 23:47:09', new DateTimeZone('UTC'));
echo 'Ошибочный шаблон h:m:s: ', $date->format('h:m:s'), "\n";
echo 'Время закрытия H:i:s: ', $date->format('H:i:s'), "\n";
Ожидаемый результат
Ошибочный шаблон h:m:s: 11:06:09
Время закрытия H:i:s: 23:47:09

h вывел 11 часов по 12-часовой шкале, m — месяц 06, а не 47 минут. Исправление H:i:s возвращает 23:47:09. Часовой пояс менять не нужно: дата всё время хранила правильные часы. Для 12-часового вывода добавьте a или A, чтобы отличать утро от вечера.

Назвать отчёт по неделе на границе годаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2021-01-01 10:00:00', new DateTimeZone('UTC'));
$week = $date->format('o-\WW');
$filename = 'sales-' . $week . '.csv';
echo 'Продажа от: ', $date->format('d.m.Y'), "\n";
echo 'Имя недельного отчёта: ', $filename, "\n";
echo 'Неверная группа Y-W: ', $date->format('Y-W'), "\n";
Ожидаемый результат
Продажа от: 01.01.2021
Имя недельного отчёта: sales-2020-W53.csv
Неверная группа Y-W: 2021-53

W — номер ISO-недели, o — год этой же недели. Первый W после обратного слеша становится буквальной буквой, второй даёт 53. Точка соединяет sales-, полученную строку 2020-W53 и .csv. Пример готовит имя, файл не создаёт. Пятница 1 января относится к последней неделе 2020; Y-W отправил бы её в ошибочную группу 2021-53.

Согласовать точность отметки событияСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.125456', new DateTimeZone('UTC'));
$apiTime = $date->format(DateTimeInterface::RFC3339_EXTENDED);
echo 'EVT-41 для API: ', $apiTime, "\n";
echo 'EVT-41 для журнала: ', $date->format('Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
EVT-41 для API: 2024-06-15T10:30:00.125+00:00
EVT-41 для журнала: 2024-06-15 10:30:00.125456 +00:00

DateTimeInterface::RFC3339_EXTENDED — именованная константа с готовым шаблоном. Его v оставляет три цифры миллисекунд 125; шаблон с u сохраняет шесть 125456. API получает явно заданную зону и точность, журнал — более подробное время. Это подготовка строк, без сетевой отправки и записи файла. Нужны целые секунды — договоритесь о DATE_ATOM или c; для большой даты за 9999 год нужен расширенный год.

Подготовить строку с честной отметкой GMTСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('Europe/Moscow'));
echo 'Время редакции: ', $date->format('d.m.Y H:i P'), "\n";
$utc = DateTimeImmutable::createFromInterface($date)->setTimezone(new DateTimeZone('UTC'));
$headerValue = $utc->format('D, d M Y H:i:s \G\M\T');
echo 'Значение Last-Modified: ', $headerValue, "\n";
Ожидаемый результат
Время редакции: 15.06.2024 10:30 +03:00
Значение Last-Modified: Sat, 15 Jun 2024 07:30:00 GMT

CreateFromInterface создаёт независимый Immutable, setTimezone показывает тот же момент в UTC. Только после этого шаблон добавляет буквальные G/M/T: каждый символ защищён обратным слешем от распознавания. D и M дают английские сокращения для этого протокола. Строка готова, но HTTP-заголовок здесь не отправляется. Просто написать GMT к московским 10:30 означало бы сообщить другой момент.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::setTimezone, date, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::formatПолучить строку даты для человека или согласованного внешнего формата.

PHP 5.2+ · date

public function format(string $format): string

Получить строку даты для человека или согласованного внешнего формата.

Часть вызоваДоговор
formatСтрока шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов.
ВозвратСтрока; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону.
ЯзыкНазвания дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter.
Доли секундыu/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её.
Путь даты в первом примере
  1. date = 15 июня 2024, 10:30 UTC
  2. Шаблон d.m.Y H:i выбирает день, месяц, год и часы
  3. format возвращает строку 15.06.2024 10:30 в label
  4. echo выводит подтверждение встречи

Шаблон — не регулярное выражение и не перевод даты. Каждый распознанный символ заменяется полем объекта. Например, m означает месяц, а i — минуты. Чтобы вывести буквальную T, используйте \T. Перед отправкой HTTP-заголовка с буквальным GMT сначала действительно переведите объект в UTC: подпись зоны сама по себе часы не переводит. Полная таблица всех букв формата.

В подтверждении нужна удобная подпись, а в заявке — календарный день. Создаём одну известную дату в UTC; затем выбираем, какие её поля показать. Запись date->format(...) вызывает метод у даты; внутри скобок передаётся шаблон. Возвращаемое значение — обычная строка: её можно сохранить, соединить с подписью или передать в дальнейший код.

Вывести время встречи в подтвержденииСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$label = $date->format('d.m.Y H:i');
echo 'Встреча: ', $label, ' UTC', "\n";
echo 'День для заявки: ', $date->format('Y-m-d'), "\n";
Ожидаемый результат
Встреча: 15.06.2024 10:30 UTC
День для заявки: 2024-06-15

Первый format возвращает строку 15.06.2024 10:30 в label. Echo добавляет назначение и зону. Второй вызов читает тот же объект и возвращает 2024-06-15 прямо в ожидающий echo. Точки, дефисы, двоеточие и пробел — разделители; d/m/Y означают день/месяц/год, H/i — час/минуту. Объект и его часы не изменились.

Исправить подпись, которая выглядит как неверные часыСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 23:47:09', new DateTimeZone('UTC'));
echo 'Ошибочный шаблон h:m:s: ', $date->format('h:m:s'), "\n";
echo 'Время закрытия H:i:s: ', $date->format('H:i:s'), "\n";
Ожидаемый результат
Ошибочный шаблон h:m:s: 11:06:09
Время закрытия H:i:s: 23:47:09

h вывел 11 часов по 12-часовой шкале, m — месяц 06, а не 47 минут. Исправление H:i:s возвращает 23:47:09. Часовой пояс менять не нужно: дата всё время хранила правильные часы. Для 12-часового вывода добавьте a или A, чтобы отличать утро от вечера.

Назвать отчёт по неделе на границе годаСамостоятельный пример
<?php
$date = new DateTime('2021-01-01 10:00:00', new DateTimeZone('UTC'));
$week = $date->format('o-\WW');
$filename = 'sales-' . $week . '.csv';
echo 'Продажа от: ', $date->format('d.m.Y'), "\n";
echo 'Имя недельного отчёта: ', $filename, "\n";
echo 'Неверная группа Y-W: ', $date->format('Y-W'), "\n";
Ожидаемый результат
Продажа от: 01.01.2021
Имя недельного отчёта: sales-2020-W53.csv
Неверная группа Y-W: 2021-53

W — номер ISO-недели, o — год этой же недели. Первый W после обратного слеша становится буквальной буквой, второй даёт 53. Точка соединяет sales-, полученную строку 2020-W53 и .csv. Пример готовит имя, файл не создаёт. Пятница 1 января относится к последней неделе 2020; Y-W отправил бы её в ошибочную группу 2021-53.

Согласовать точность отметки событияСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00.125456', new DateTimeZone('UTC'));
$apiTime = $date->format(DateTimeInterface::RFC3339_EXTENDED);
echo 'EVT-41 для API: ', $apiTime, "\n";
echo 'EVT-41 для журнала: ', $date->format('Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
EVT-41 для API: 2024-06-15T10:30:00.125+00:00
EVT-41 для журнала: 2024-06-15 10:30:00.125456 +00:00

DateTimeInterface::RFC3339_EXTENDED — именованная константа с готовым шаблоном. Его v оставляет три цифры миллисекунд 125; шаблон с u сохраняет шесть 125456. API получает явно заданную зону и точность, журнал — более подробное время. Это подготовка строк, без сетевой отправки и записи файла. Нужны целые секунды — договоритесь о DATE_ATOM или c; для большой даты за 9999 год нужен расширенный год.

Подготовить строку с честной отметкой GMTСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('Europe/Moscow'));
echo 'Время редакции: ', $date->format('d.m.Y H:i P'), "\n";
$utc = DateTimeImmutable::createFromInterface($date)->setTimezone(new DateTimeZone('UTC'));
$headerValue = $utc->format('D, d M Y H:i:s \G\M\T');
echo 'Значение Last-Modified: ', $headerValue, "\n";
Ожидаемый результат
Время редакции: 15.06.2024 10:30 +03:00
Значение Last-Modified: Sat, 15 Jun 2024 07:30:00 GMT

CreateFromInterface создаёт независимый Immutable, setTimezone показывает тот же момент в UTC. Только после этого шаблон добавляет буквальные G/M/T: каждый символ защищён обратным слешем от распознавания. D и M дают английские сокращения для этого протокола. Строка готова, но HTTP-заголовок здесь не отправляется. Просто написать GMT к московским 10:30 означало бы сообщить другой момент.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::setTimezone, date, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_formatПолучить строку даты для человека или согласованного внешнего формата.

PHP 5.2+ · date

function date_format(DateTimeInterface $object, string $format): string

Получить строку даты для человека или согласованного внешнего формата.

Часть вызоваДоговор
objectDateTime или DateTimeImmutable через общий DateTimeInterface. Старое краткое описание процедурного стиля, упоминающее лишь date_create, не ограничивает сигнатуру одним изменяемым классом.
formatСтрока шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов.
ВозвратСтрока; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону.
ЯзыкНазвания дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter.
Доли секундыu/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её.
Путь даты в первом примере
  1. date = 15 июня 2024, 10:30 UTC
  2. Шаблон d.m.Y H:i выбирает день, месяц, год и часы
  3. format возвращает строку 15.06.2024 10:30 в label
  4. echo выводит подтверждение встречи

Шаблон — не регулярное выражение и не перевод даты. Каждый распознанный символ заменяется полем объекта. Например, m означает месяц, а i — минуты. Чтобы вывести буквальную T, используйте \T. Перед отправкой HTTP-заголовка с буквальным GMT сначала действительно переведите объект в UTC: подпись зоны сама по себе часы не переводит. Полная таблица всех букв формата.

В подтверждении нужна удобная подпись, а в заявке — календарный день. Создаём одну известную дату в UTC; затем выбираем, какие её поля показать. Date_format получает объект первым аргументом и шаблон вторым. Возвращаемое значение — обычная строка: её можно сохранить, соединить с подписью или передать в дальнейший код.

Вывести время встречи в подтвержденииСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$label = date_format($date, 'd.m.Y H:i');
echo 'Встреча: ', $label, ' UTC', "\n";
echo 'День для заявки: ', date_format($date, 'Y-m-d'), "\n";
Ожидаемый результат
Встреча: 15.06.2024 10:30 UTC
День для заявки: 2024-06-15

Первый format возвращает строку 15.06.2024 10:30 в label. Echo добавляет назначение и зону. Второй вызов читает тот же объект и возвращает 2024-06-15 прямо в ожидающий echo. Точки, дефисы, двоеточие и пробел — разделители; d/m/Y означают день/месяц/год, H/i — час/минуту. Объект и его часы не изменились.

Исправить подпись, которая выглядит как неверные часыСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 23:47:09', new DateTimeZone('UTC'));
echo 'Ошибочный шаблон h:m:s: ', date_format($date, 'h:m:s'), "\n";
echo 'Время закрытия H:i:s: ', date_format($date, 'H:i:s'), "\n";
Ожидаемый результат
Ошибочный шаблон h:m:s: 11:06:09
Время закрытия H:i:s: 23:47:09

h вывел 11 часов по 12-часовой шкале, m — месяц 06, а не 47 минут. Исправление H:i:s возвращает 23:47:09. Часовой пояс менять не нужно: дата всё время хранила правильные часы. Для 12-часового вывода добавьте a или A, чтобы отличать утро от вечера.

Назвать отчёт по неделе на границе годаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2021-01-01 10:00:00', new DateTimeZone('UTC'));
$week = date_format($date, 'o-\WW');
$filename = 'sales-' . $week . '.csv';
echo 'Продажа от: ', date_format($date, 'd.m.Y'), "\n";
echo 'Имя недельного отчёта: ', $filename, "\n";
echo 'Неверная группа Y-W: ', date_format($date, 'Y-W'), "\n";
Ожидаемый результат
Продажа от: 01.01.2021
Имя недельного отчёта: sales-2020-W53.csv
Неверная группа Y-W: 2021-53

W — номер ISO-недели, o — год этой же недели. Первый W после обратного слеша становится буквальной буквой, второй даёт 53. Точка соединяет sales-, полученную строку 2020-W53 и .csv. Пример готовит имя, файл не создаёт. Пятница 1 января относится к последней неделе 2020; Y-W отправил бы её в ошибочную группу 2021-53.

Согласовать точность отметки событияСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.125456', new DateTimeZone('UTC'));
$apiTime = date_format($date, DateTimeInterface::RFC3339_EXTENDED);
echo 'EVT-41 для API: ', $apiTime, "\n";
echo 'EVT-41 для журнала: ', date_format($date, 'Y-m-d H:i:s.u P'), "\n";
Ожидаемый результат
EVT-41 для API: 2024-06-15T10:30:00.125+00:00
EVT-41 для журнала: 2024-06-15 10:30:00.125456 +00:00

DateTimeInterface::RFC3339_EXTENDED — именованная константа с готовым шаблоном. Его v оставляет три цифры миллисекунд 125; шаблон с u сохраняет шесть 125456. API получает явно заданную зону и точность, журнал — более подробное время. Это подготовка строк, без сетевой отправки и записи файла. Нужны целые секунды — договоритесь о DATE_ATOM или c; для большой даты за 9999 год нужен расширенный год.

Подготовить строку с честной отметкой GMTСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('Europe/Moscow'));
echo 'Время редакции: ', date_format($date, 'd.m.Y H:i P'), "\n";
$utc = DateTimeImmutable::createFromInterface($date)->setTimezone(new DateTimeZone('UTC'));
$headerValue = date_format($utc, 'D, d M Y H:i:s \G\M\T');
echo 'Значение Last-Modified: ', $headerValue, "\n";
Ожидаемый результат
Время редакции: 15.06.2024 10:30 +03:00
Значение Last-Modified: Sat, 15 Jun 2024 07:30:00 GMT

CreateFromInterface создаёт независимый Immutable, setTimezone показывает тот же момент в UTC. Только после этого шаблон добавляет буквальные G/M/T: каждый символ защищён обратным слешем от распознавания. D и M дают английские сокращения для этого протокола. Строка готова, но HTTP-заголовок здесь не отправляется. Просто написать GMT к московским 10:30 означало бы сообщить другой момент.

Сравните: DateTimeImmutable::createFromFormat, DateTimeImmutable::setTimezone, date, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.

PHP 5.5+ · date

public function getTimestamp(): int

Получить целые секунды Unix для момента, независимо от отображаемой зоны.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд с Unix epoch. Дробная часть не включена.
ДиапазонНепредставимость в int: DateRangeError с PHP 8.3, ValueError в PHP 8.0–8.2, раньше false. На 32-битной платформе диапазон int уже. format("U") может вернуть строковое представление за пределами int.
ТочностьДля доли отдельно getMicrosecond или format("u"). Приведение огромного timestamp микросекунд к int может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака.
Путь даты в первом примере
  1. expires:13:00 +03:00 =10:00 UTC
  2. Два вызова getTimestamp возвращают целые секунды
  3. deadlineSeconds − nowSeconds =900
  4. remaining >0 → вывод «Бронь действует»
Посчитать секунды до окончания бронированияСамостоятельный пример
<?php
$expires = new DateTimeImmutable('2024-06-15 13:00:00+03:00');
$now = new DateTimeImmutable('2024-06-15 09:45:00+00:00');
$deadlineSeconds = $expires->getTimestamp();
$nowSeconds = $now->getTimestamp();
$remaining = $deadlineSeconds - $nowSeconds;
echo 'Бронь до: ', $expires->format('H:i P'), "\n";
echo 'Проверяем в: ', $now->format('H:i P'), "\n";
echo 'Осталось секунд: ', $remaining, "\n";
echo $remaining > 0 ? "Бронь действует\n" : "Бронь истекла\n";
Ожидаемый результат
Бронь до: 13:00 +03:00
Проверяем в: 09:45 +00:00
Осталось секунд: 900
Бронь действует

GetTimestamp возвращает целое число секунд от общего начала Unix. Разность 13:00 +03:00 и 09:45 UTC равна 900 секундам: московский срок означает 10:00 UTC. Смещение уже учтено при создании момента; прибавлять его повторно нельзя. Now здесь зафиксирован ради повторяемости, в рабочем коде его получают из часов или передают параметром. На точной границе remaining=0 бронь по этому правилу уже истекла.

Подготовить миллисекунды для простого протоколаСамостоятельный пример
<?php
$event = new DateTimeImmutable('1970-01-01 00:02:03.500000+00:00');
$seconds = $event->getTimestamp();
$millisecondPart = (int) $event->format('v');
$protocolTime = $seconds * 1000 + $millisecondPart;
echo 'Целые секунды события: ', $seconds, "\n";
echo 'Доля в миллисекундах: ', $millisecondPart, "\n";
echo 'Поле event_time_ms: ', $protocolTime, "\n";
Ожидаемый результат
Целые секунды события: 123
Доля в миллисекундах: 500
Поле event_time_ms: 123500

Протокол этого опыта требует целое количество миллисекунд. GetTimestamp возвращает 123 без доли; format(v) — строку 500, (int) превращает её в число. Умножение 123 на 1000 и прибавление 500 даёт 123500. Для современных дат такой общий int миллисекунд требует 64-битного PHP; пример специально использует малую положительную метку. Микросекунды точнее миллисекунд и здесь были бы отброшены; отрицательные дробные метки требуют отдельно согласованного правила.

Сравните: DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setTimestamp, DateTimeImmutable::getMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.

PHP 5.3+ · date

public function getTimestamp(): int

Получить целые секунды Unix для момента, независимо от отображаемой зоны.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд с Unix epoch. Дробная часть не включена.
ДиапазонНепредставимость в int: DateRangeError с PHP 8.3, ValueError в PHP 8.0–8.2, раньше false. На 32-битной платформе диапазон int уже. format("U") может вернуть строковое представление за пределами int.
ТочностьДля доли отдельно getMicrosecond или format("u"). Приведение огромного timestamp микросекунд к int может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака.
Путь даты в первом примере
  1. expires:13:00 +03:00 =10:00 UTC
  2. Два вызова getTimestamp возвращают целые секунды
  3. deadlineSeconds − nowSeconds =900
  4. remaining >0 → вывод «Бронь действует»
Посчитать секунды до окончания бронированияСамостоятельный пример
<?php
$expires = new DateTime('2024-06-15 13:00:00+03:00');
$now = new DateTime('2024-06-15 09:45:00+00:00');
$deadlineSeconds = $expires->getTimestamp();
$nowSeconds = $now->getTimestamp();
$remaining = $deadlineSeconds - $nowSeconds;
echo 'Бронь до: ', $expires->format('H:i P'), "\n";
echo 'Проверяем в: ', $now->format('H:i P'), "\n";
echo 'Осталось секунд: ', $remaining, "\n";
echo $remaining > 0 ? "Бронь действует\n" : "Бронь истекла\n";
Ожидаемый результат
Бронь до: 13:00 +03:00
Проверяем в: 09:45 +00:00
Осталось секунд: 900
Бронь действует

GetTimestamp возвращает целое число секунд от общего начала Unix. Разность 13:00 +03:00 и 09:45 UTC равна 900 секундам: московский срок означает 10:00 UTC. Смещение уже учтено при создании момента; прибавлять его повторно нельзя. Now здесь зафиксирован ради повторяемости, в рабочем коде его получают из часов или передают параметром. На точной границе remaining=0 бронь по этому правилу уже истекла.

Подготовить миллисекунды для простого протоколаСамостоятельный пример
<?php
$event = new DateTime('1970-01-01 00:02:03.500000+00:00');
$seconds = $event->getTimestamp();
$millisecondPart = (int) $event->format('v');
$protocolTime = $seconds * 1000 + $millisecondPart;
echo 'Целые секунды события: ', $seconds, "\n";
echo 'Доля в миллисекундах: ', $millisecondPart, "\n";
echo 'Поле event_time_ms: ', $protocolTime, "\n";
Ожидаемый результат
Целые секунды события: 123
Доля в миллисекундах: 500
Поле event_time_ms: 123500

Протокол этого опыта требует целое количество миллисекунд. GetTimestamp возвращает 123 без доли; format(v) — строку 500, (int) превращает её в число. Умножение 123 на 1000 и прибавление 500 даёт 123500. Для современных дат такой общий int миллисекунд требует 64-битного PHP; пример специально использует малую положительную метку. Микросекунды точнее миллисекунд и здесь были бы отброшены; отрицательные дробные метки требуют отдельно согласованного правила.

Сравните: DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setTimestamp, DateTimeImmutable::getMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::getTimestampПолучить целые секунды Unix для момента, независимо от отображаемой зоны.

PHP 5.5+ · date

public function getTimestamp(): int

Получить целые секунды Unix для момента, независимо от отображаемой зоны.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд с Unix epoch. Дробная часть не включена.
ДиапазонНепредставимость в int: DateRangeError с PHP 8.3, ValueError в PHP 8.0–8.2, раньше false. На 32-битной платформе диапазон int уже. format("U") может вернуть строковое представление за пределами int.
ТочностьДля доли отдельно getMicrosecond или format("u"). Приведение огромного timestamp микросекунд к int может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака.
Путь даты в первом примере
  1. expires:13:00 +03:00 =10:00 UTC
  2. Два вызова getTimestamp возвращают целые секунды
  3. deadlineSeconds − nowSeconds =900
  4. remaining >0 → вывод «Бронь действует»
Посчитать секунды до окончания бронированияСамостоятельный пример
<?php
$expires = new DateTimeImmutable('2024-06-15 13:00:00+03:00');
$now = new DateTimeImmutable('2024-06-15 09:45:00+00:00');
$deadlineSeconds = $expires->getTimestamp();
$nowSeconds = $now->getTimestamp();
$remaining = $deadlineSeconds - $nowSeconds;
echo 'Бронь до: ', $expires->format('H:i P'), "\n";
echo 'Проверяем в: ', $now->format('H:i P'), "\n";
echo 'Осталось секунд: ', $remaining, "\n";
echo $remaining > 0 ? "Бронь действует\n" : "Бронь истекла\n";
Ожидаемый результат
Бронь до: 13:00 +03:00
Проверяем в: 09:45 +00:00
Осталось секунд: 900
Бронь действует

GetTimestamp возвращает целое число секунд от общего начала Unix. Разность 13:00 +03:00 и 09:45 UTC равна 900 секундам: московский срок означает 10:00 UTC. Смещение уже учтено при создании момента; прибавлять его повторно нельзя. Now здесь зафиксирован ради повторяемости, в рабочем коде его получают из часов или передают параметром. На точной границе remaining=0 бронь по этому правилу уже истекла.

Подготовить миллисекунды для простого протоколаСамостоятельный пример
<?php
$event = new DateTimeImmutable('1970-01-01 00:02:03.500000+00:00');
$seconds = $event->getTimestamp();
$millisecondPart = (int) $event->format('v');
$protocolTime = $seconds * 1000 + $millisecondPart;
echo 'Целые секунды события: ', $seconds, "\n";
echo 'Доля в миллисекундах: ', $millisecondPart, "\n";
echo 'Поле event_time_ms: ', $protocolTime, "\n";
Ожидаемый результат
Целые секунды события: 123
Доля в миллисекундах: 500
Поле event_time_ms: 123500

Протокол этого опыта требует целое количество миллисекунд. GetTimestamp возвращает 123 без доли; format(v) — строку 500, (int) превращает её в число. Умножение 123 на 1000 и прибавление 500 даёт 123500. Для современных дат такой общий int миллисекунд требует 64-битного PHP; пример специально использует малую положительную метку. Микросекунды точнее миллисекунд и здесь были бы отброшены; отрицательные дробные метки требуют отдельно согласованного правила.

Сравните: DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setTimestamp, DateTimeImmutable::getMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

date_timestamp_getПолучить целые секунды Unix для момента, независимо от отображаемой зоны.

PHP 5.3+ · date

function date_timestamp_get(DateTimeInterface $object): int

Получить целые секунды Unix для момента, независимо от отображаемой зоны.

Часть вызоваДоговор
objectDateTime или DateTimeImmutable.
Возвратint секунд с Unix epoch. Дробная часть не включена.
ДиапазонНепредставимость в int: DateRangeError с PHP 8.3, ValueError в PHP 8.0–8.2, раньше false. На 32-битной платформе диапазон int уже. format("U") может вернуть строковое представление за пределами int.
ТочностьДля доли отдельно getMicrosecond или format("u"). Приведение огромного timestamp микросекунд к int может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака.
Путь даты в первом примере
  1. expires:13:00 +03:00 =10:00 UTC
  2. Два вызова getTimestamp возвращают целые секунды
  3. deadlineSeconds − nowSeconds =900
  4. remaining >0 → вывод «Бронь действует»
Посчитать секунды до окончания бронированияСамостоятельный пример
<?php
$expires = new DateTimeImmutable('2024-06-15 13:00:00+03:00');
$now = new DateTimeImmutable('2024-06-15 09:45:00+00:00');
$deadlineSeconds = date_timestamp_get($expires);
$nowSeconds = date_timestamp_get($now);
$remaining = $deadlineSeconds - $nowSeconds;
echo 'Бронь до: ', $expires->format('H:i P'), "\n";
echo 'Проверяем в: ', $now->format('H:i P'), "\n";
echo 'Осталось секунд: ', $remaining, "\n";
echo $remaining > 0 ? "Бронь действует\n" : "Бронь истекла\n";
Ожидаемый результат
Бронь до: 13:00 +03:00
Проверяем в: 09:45 +00:00
Осталось секунд: 900
Бронь действует

GetTimestamp возвращает целое число секунд от общего начала Unix. Разность 13:00 +03:00 и 09:45 UTC равна 900 секундам: московский срок означает 10:00 UTC. Смещение уже учтено при создании момента; прибавлять его повторно нельзя. Now здесь зафиксирован ради повторяемости, в рабочем коде его получают из часов или передают параметром. На точной границе remaining=0 бронь по этому правилу уже истекла.

Подготовить миллисекунды для простого протоколаСамостоятельный пример
<?php
$event = new DateTimeImmutable('1970-01-01 00:02:03.500000+00:00');
$seconds = date_timestamp_get($event);
$millisecondPart = (int) $event->format('v');
$protocolTime = $seconds * 1000 + $millisecondPart;
echo 'Целые секунды события: ', $seconds, "\n";
echo 'Доля в миллисекундах: ', $millisecondPart, "\n";
echo 'Поле event_time_ms: ', $protocolTime, "\n";
Ожидаемый результат
Целые секунды события: 123
Доля в миллисекундах: 500
Поле event_time_ms: 123500

Протокол этого опыта требует целое количество миллисекунд. GetTimestamp возвращает 123 без доли; format(v) — строку 500, (int) превращает её в число. Умножение 123 на 1000 и прибавление 500 даёт 123500. Для современных дат такой общий int миллисекунд требует 64-битного PHP; пример специально использует малую положительную метку. Микросекунды точнее миллисекунд и здесь были бы отброшены; отрицательные дробные метки требуют отдельно согласованного правила.

Сравните: DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setTimestamp, DateTimeImmutable::getMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.

PHP 5.5+ · date

public function getOffset(): int

Прочитать UTC-смещение зоны именно на дату объекта.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд от UTC: восточнее положительный, западнее отрицательный.
ГраницаЭто смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны.
Путь даты в первом примере
  1. Две встречи:15 января и 15 июля, обе 10:00 London
  2. getOffset читает правило на дату:0 /3600 секунд
  3. Отдельный setTimezone(UTC) даёт 10:00 /09:00
  4. Две строки объясняют участнику зимнее и летнее время
Показать, почему встреча в Лондоне сдвинулась для UTCСамостоятельный пример
<?php
$winter = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$summer = new DateTimeImmutable('2024-07-15 10:00:00', new DateTimeZone('Europe/London'));
foreach ([$winter, $summer] as $meeting) {
    $offsetSeconds = $meeting->getOffset();
    $utc = DateTimeImmutable::createFromInterface($meeting)->setTimezone(new DateTimeZone('UTC'));
    echo $meeting->format('d.m.Y H:i'), ' London; смещение ', $offsetSeconds,
        ' с; в UTC ', $utc->format('H:i'), "\n";
}
Ожидаемый результат
15.01.2024 10:00 London; смещение 0 с; в UTC 10:00
15.07.2024 10:00 London; смещение 3600 с; в UTC 09:00

Foreach по очереди помещает зимнюю и летнюю дату в meeting. Метод возвращает 0 или 3600 в offsetSeconds для этой даты, а setTimezone отдельно готовит часы участника в UTC. В обеих встречах локально 10:00; летние правила London сместили соответствующий UTC на 09:00. Нельзя однажды записать 3600 вместо Europe/London и ожидать правильное зимнее расписание.

Разобрать присланное отрицательное смещениеСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15 12:00:00-05:30');
$offsetSeconds = $received->getOffset();
$offsetMinutes = intdiv($offsetSeconds, 60);
$utc = DateTimeImmutable::createFromInterface($received)->setTimezone(new DateTimeZone('UTC'));
echo 'Прислано: ', $received->format('H:i P'), "\n";
echo 'Смещение для карточки события: ', $offsetMinutes, ' минут', "\n";
echo 'Тот же момент UTC: ', $utc->format('H:i'), "\n";
Ожидаемый результат
Прислано: 12:00 -05:30
Смещение для карточки события: -330 минут
Тот же момент UTC: 17:30

Метод вернул−19800 секунд. Intdiv делит на 60 и возвращает целую часть: здесь без остатка получилось−330 минут, то есть−5 часов 30 минут. Знак относится ко всему смещению. В исторических зонах бывают смещения с секундами — там такой перевод в целые минуты потеряет остаток. Время в UTC получено сменой представления, а не прибавлением offset к timestamp.

Сравните: DateTimeZone::getOffset, DateTimeImmutable::getTimezone, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.

PHP 5.2+ · date

public function getOffset(): int

Прочитать UTC-смещение зоны именно на дату объекта.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд от UTC: восточнее положительный, западнее отрицательный.
ГраницаЭто смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны.
Путь даты в первом примере
  1. Две встречи:15 января и 15 июля, обе 10:00 London
  2. getOffset читает правило на дату:0 /3600 секунд
  3. Отдельный setTimezone(UTC) даёт 10:00 /09:00
  4. Две строки объясняют участнику зимнее и летнее время
Показать, почему встреча в Лондоне сдвинулась для UTCСамостоятельный пример
<?php
$winter = new DateTime('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$summer = new DateTime('2024-07-15 10:00:00', new DateTimeZone('Europe/London'));
foreach ([$winter, $summer] as $meeting) {
    $offsetSeconds = $meeting->getOffset();
    $utc = DateTimeImmutable::createFromInterface($meeting)->setTimezone(new DateTimeZone('UTC'));
    echo $meeting->format('d.m.Y H:i'), ' London; смещение ', $offsetSeconds,
        ' с; в UTC ', $utc->format('H:i'), "\n";
}
Ожидаемый результат
15.01.2024 10:00 London; смещение 0 с; в UTC 10:00
15.07.2024 10:00 London; смещение 3600 с; в UTC 09:00

Foreach по очереди помещает зимнюю и летнюю дату в meeting. Метод возвращает 0 или 3600 в offsetSeconds для этой даты, а setTimezone отдельно готовит часы участника в UTC. В обеих встречах локально 10:00; летние правила London сместили соответствующий UTC на 09:00. Нельзя однажды записать 3600 вместо Europe/London и ожидать правильное зимнее расписание.

Разобрать присланное отрицательное смещениеСамостоятельный пример
<?php
$received = new DateTime('2024-01-15 12:00:00-05:30');
$offsetSeconds = $received->getOffset();
$offsetMinutes = intdiv($offsetSeconds, 60);
$utc = DateTimeImmutable::createFromInterface($received)->setTimezone(new DateTimeZone('UTC'));
echo 'Прислано: ', $received->format('H:i P'), "\n";
echo 'Смещение для карточки события: ', $offsetMinutes, ' минут', "\n";
echo 'Тот же момент UTC: ', $utc->format('H:i'), "\n";
Ожидаемый результат
Прислано: 12:00 -05:30
Смещение для карточки события: -330 минут
Тот же момент UTC: 17:30

Метод вернул−19800 секунд. Intdiv делит на 60 и возвращает целую часть: здесь без остатка получилось−330 минут, то есть−5 часов 30 минут. Знак относится ко всему смещению. В исторических зонах бывают смещения с секундами — там такой перевод в целые минуты потеряет остаток. Время в UTC получено сменой представления, а не прибавлением offset к timestamp.

Сравните: DateTimeZone::getOffset, DateTimeImmutable::getTimezone, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::getOffsetПрочитать UTC-смещение зоны именно на дату объекта.

PHP 5.5+ · date

public function getOffset(): int

Прочитать UTC-смещение зоны именно на дату объекта.

Часть вызоваДоговор
ПараметрыНет.
Возвратint секунд от UTC: восточнее положительный, западнее отрицательный.
ГраницаЭто смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны.
Путь даты в первом примере
  1. Две встречи:15 января и 15 июля, обе 10:00 London
  2. getOffset читает правило на дату:0 /3600 секунд
  3. Отдельный setTimezone(UTC) даёт 10:00 /09:00
  4. Две строки объясняют участнику зимнее и летнее время
Показать, почему встреча в Лондоне сдвинулась для UTCСамостоятельный пример
<?php
$winter = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$summer = new DateTimeImmutable('2024-07-15 10:00:00', new DateTimeZone('Europe/London'));
foreach ([$winter, $summer] as $meeting) {
    $offsetSeconds = $meeting->getOffset();
    $utc = DateTimeImmutable::createFromInterface($meeting)->setTimezone(new DateTimeZone('UTC'));
    echo $meeting->format('d.m.Y H:i'), ' London; смещение ', $offsetSeconds,
        ' с; в UTC ', $utc->format('H:i'), "\n";
}
Ожидаемый результат
15.01.2024 10:00 London; смещение 0 с; в UTC 10:00
15.07.2024 10:00 London; смещение 3600 с; в UTC 09:00

Foreach по очереди помещает зимнюю и летнюю дату в meeting. Метод возвращает 0 или 3600 в offsetSeconds для этой даты, а setTimezone отдельно готовит часы участника в UTC. В обеих встречах локально 10:00; летние правила London сместили соответствующий UTC на 09:00. Нельзя однажды записать 3600 вместо Europe/London и ожидать правильное зимнее расписание.

Разобрать присланное отрицательное смещениеСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15 12:00:00-05:30');
$offsetSeconds = $received->getOffset();
$offsetMinutes = intdiv($offsetSeconds, 60);
$utc = DateTimeImmutable::createFromInterface($received)->setTimezone(new DateTimeZone('UTC'));
echo 'Прислано: ', $received->format('H:i P'), "\n";
echo 'Смещение для карточки события: ', $offsetMinutes, ' минут', "\n";
echo 'Тот же момент UTC: ', $utc->format('H:i'), "\n";
Ожидаемый результат
Прислано: 12:00 -05:30
Смещение для карточки события: -330 минут
Тот же момент UTC: 17:30

Метод вернул−19800 секунд. Intdiv делит на 60 и возвращает целую часть: здесь без остатка получилось−330 минут, то есть−5 часов 30 минут. Знак относится ко всему смещению. В исторических зонах бывают смещения с секундами — там такой перевод в целые минуты потеряет остаток. Время в UTC получено сменой представления, а не прибавлением offset к timestamp.

Сравните: DateTimeZone::getOffset, DateTimeImmutable::getTimezone, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

date_offset_getПрочитать UTC-смещение зоны именно на дату объекта.

PHP 5.2+ · date

function date_offset_get(DateTimeInterface $object): int

Прочитать UTC-смещение зоны именно на дату объекта.

Часть вызоваДоговор
objectDateTime или DateTimeImmutable.
Возвратint секунд от UTC: восточнее положительный, западнее отрицательный.
ГраницаЭто смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны.
Путь даты в первом примере
  1. Две встречи:15 января и 15 июля, обе 10:00 London
  2. getOffset читает правило на дату:0 /3600 секунд
  3. Отдельный setTimezone(UTC) даёт 10:00 /09:00
  4. Две строки объясняют участнику зимнее и летнее время
Показать, почему встреча в Лондоне сдвинулась для UTCСамостоятельный пример
<?php
$winter = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$summer = new DateTimeImmutable('2024-07-15 10:00:00', new DateTimeZone('Europe/London'));
foreach ([$winter, $summer] as $meeting) {
    $offsetSeconds = date_offset_get($meeting);
    $utc = DateTimeImmutable::createFromInterface($meeting)->setTimezone(new DateTimeZone('UTC'));
    echo $meeting->format('d.m.Y H:i'), ' London; смещение ', $offsetSeconds,
        ' с; в UTC ', $utc->format('H:i'), "\n";
}
Ожидаемый результат
15.01.2024 10:00 London; смещение 0 с; в UTC 10:00
15.07.2024 10:00 London; смещение 3600 с; в UTC 09:00

Foreach по очереди помещает зимнюю и летнюю дату в meeting. Метод возвращает 0 или 3600 в offsetSeconds для этой даты, а setTimezone отдельно готовит часы участника в UTC. В обеих встречах локально 10:00; летние правила London сместили соответствующий UTC на 09:00. Нельзя однажды записать 3600 вместо Europe/London и ожидать правильное зимнее расписание.

Разобрать присланное отрицательное смещениеСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15 12:00:00-05:30');
$offsetSeconds = date_offset_get($received);
$offsetMinutes = intdiv($offsetSeconds, 60);
$utc = DateTimeImmutable::createFromInterface($received)->setTimezone(new DateTimeZone('UTC'));
echo 'Прислано: ', $received->format('H:i P'), "\n";
echo 'Смещение для карточки события: ', $offsetMinutes, ' минут', "\n";
echo 'Тот же момент UTC: ', $utc->format('H:i'), "\n";
Ожидаемый результат
Прислано: 12:00 -05:30
Смещение для карточки события: -330 минут
Тот же момент UTC: 17:30

Метод вернул−19800 секунд. Intdiv делит на 60 и возвращает целую часть: здесь без остатка получилось−330 минут, то есть−5 часов 30 минут. Знак относится ко всему смещению. В исторических зонах бывают смещения с секундами — там такой перевод в целые минуты потеряет остаток. Время в UTC получено сменой представления, а не прибавлением offset к timestamp.

Сравните: DateTimeZone::getOffset, DateTimeImmutable::getTimezone, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::getTimezoneПолучить объект часового пояса, связанный с датой.

PHP 5.5+ · date

public function getTimezone(): DateTimeZone|false

Получить объект часового пояса, связанный с датой.

Часть вызоваДоговор
ПараметрыНет.
ВозвратDateTimeZone при успехе, в опубликованном договоре допускается false при неудаче.
Что хранитсяИменованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона.
Путь даты в первом примере
  1. first хранит 15 января 10:00 и Europe/London
  2. getTimezone возвращает объект zone
  3. new дата 15 июля +тот же zone →10:00 +01:00
  4. getName/format дают подписи двух встреч
Назначить следующую встречу по правилам того же регионаСамостоятельный пример
<?php
$first = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$zone = $first->getTimezone();
if ($zone === false) {
    echo "Новая встреча не назначена: зона недоступна\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'Регион расписания: ', $zone->getName(), "\n";
    echo 'Первая встреча: ', $first->format('d.m.Y H:i P'), "\n";
    echo 'Новая встреча: ', $next->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Регион расписания: Europe/London
Первая встреча: 15.01.2024 10:00 +00:00
Новая встреча: 15.07.2024 10:00 +01:00

GetTimezone возвращает объект DateTimeZone в zone. После проверки false передаём именно объект вторым аргументом нового конструктора. Он содержит правила London, поэтому летняя встреча в 10:00 получает+01:00, зимняя сохраняет+00:00. GetName возвращает строку только для подписи. Сама дата из первой встречи в новую не копируется: день и часы заданы новой строкой.

Увидеть пределы зоны, полученной из одного смещенияСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15T10:00:00+03:00');
$zone = $received->getTimezone();
if ($zone === false) {
    echo "Нельзя восстановить зону события\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'В исходных данных известна зона: ', $zone->getName(), "\n";
    echo 'Расписание с этим фиксированным правилом: ', $next->format('d.m.Y H:i P'), "\n";
    echo "Для регионального расписания нужно отдельно получить название региона\n";
}
Ожидаемый результат
В исходных данных известна зона: +03:00
Расписание с этим фиксированным правилом: 15.07.2024 10:00 +03:00
Для регионального расписания нужно отдельно получить название региона

Во входной строке нет Europe/Moscow или другого региона, только+03:00. Возвращённый объект сохраняет это фиксированное смещение и летом. GetTimezone не угадывает географию: одинаковое смещение в один день может соответствовать разным региональным правилам. Такое правило подходит, если договор требует именно постоянное+03:00; для местного расписания регион запрашивают отдельно.

Сравните: DateTimeZone::getName, DateTimeImmutable::getOffset, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::getTimezoneПолучить объект часового пояса, связанный с датой.

PHP 5.2+ · date

public function getTimezone(): DateTimeZone|false

Получить объект часового пояса, связанный с датой.

Часть вызоваДоговор
ПараметрыНет.
ВозвратDateTimeZone при успехе, в опубликованном договоре допускается false при неудаче.
Что хранитсяИменованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона.
Путь даты в первом примере
  1. first хранит 15 января 10:00 и Europe/London
  2. getTimezone возвращает объект zone
  3. new дата 15 июля +тот же zone →10:00 +01:00
  4. getName/format дают подписи двух встреч
Назначить следующую встречу по правилам того же регионаСамостоятельный пример
<?php
$first = new DateTime('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$zone = $first->getTimezone();
if ($zone === false) {
    echo "Новая встреча не назначена: зона недоступна\n";
} else {
    $next = new DateTime('2024-07-15 10:00:00', $zone);
    echo 'Регион расписания: ', $zone->getName(), "\n";
    echo 'Первая встреча: ', $first->format('d.m.Y H:i P'), "\n";
    echo 'Новая встреча: ', $next->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Регион расписания: Europe/London
Первая встреча: 15.01.2024 10:00 +00:00
Новая встреча: 15.07.2024 10:00 +01:00

GetTimezone возвращает объект DateTimeZone в zone. После проверки false передаём именно объект вторым аргументом нового конструктора. Он содержит правила London, поэтому летняя встреча в 10:00 получает+01:00, зимняя сохраняет+00:00. GetName возвращает строку только для подписи. Сама дата из первой встречи в новую не копируется: день и часы заданы новой строкой.

Увидеть пределы зоны, полученной из одного смещенияСамостоятельный пример
<?php
$received = new DateTime('2024-01-15T10:00:00+03:00');
$zone = $received->getTimezone();
if ($zone === false) {
    echo "Нельзя восстановить зону события\n";
} else {
    $next = new DateTime('2024-07-15 10:00:00', $zone);
    echo 'В исходных данных известна зона: ', $zone->getName(), "\n";
    echo 'Расписание с этим фиксированным правилом: ', $next->format('d.m.Y H:i P'), "\n";
    echo "Для регионального расписания нужно отдельно получить название региона\n";
}
Ожидаемый результат
В исходных данных известна зона: +03:00
Расписание с этим фиксированным правилом: 15.07.2024 10:00 +03:00
Для регионального расписания нужно отдельно получить название региона

Во входной строке нет Europe/Moscow или другого региона, только+03:00. Возвращённый объект сохраняет это фиксированное смещение и летом. GetTimezone не угадывает географию: одинаковое смещение в один день может соответствовать разным региональным правилам. Такое правило подходит, если договор требует именно постоянное+03:00; для местного расписания регион запрашивают отдельно.

Сравните: DateTimeZone::getName, DateTimeImmutable::getOffset, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::getTimezoneПолучить объект часового пояса, связанный с датой.

PHP 5.5+ · date

public function getTimezone(): DateTimeZone|false

Получить объект часового пояса, связанный с датой.

Часть вызоваДоговор
ПараметрыНет.
ВозвратDateTimeZone при успехе, в опубликованном договоре допускается false при неудаче.
Что хранитсяИменованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона.
Путь даты в первом примере
  1. first хранит 15 января 10:00 и Europe/London
  2. getTimezone возвращает объект zone
  3. new дата 15 июля +тот же zone →10:00 +01:00
  4. getName/format дают подписи двух встреч
Назначить следующую встречу по правилам того же регионаСамостоятельный пример
<?php
$first = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$zone = $first->getTimezone();
if ($zone === false) {
    echo "Новая встреча не назначена: зона недоступна\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'Регион расписания: ', $zone->getName(), "\n";
    echo 'Первая встреча: ', $first->format('d.m.Y H:i P'), "\n";
    echo 'Новая встреча: ', $next->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Регион расписания: Europe/London
Первая встреча: 15.01.2024 10:00 +00:00
Новая встреча: 15.07.2024 10:00 +01:00

GetTimezone возвращает объект DateTimeZone в zone. После проверки false передаём именно объект вторым аргументом нового конструктора. Он содержит правила London, поэтому летняя встреча в 10:00 получает+01:00, зимняя сохраняет+00:00. GetName возвращает строку только для подписи. Сама дата из первой встречи в новую не копируется: день и часы заданы новой строкой.

Увидеть пределы зоны, полученной из одного смещенияСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15T10:00:00+03:00');
$zone = $received->getTimezone();
if ($zone === false) {
    echo "Нельзя восстановить зону события\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'В исходных данных известна зона: ', $zone->getName(), "\n";
    echo 'Расписание с этим фиксированным правилом: ', $next->format('d.m.Y H:i P'), "\n";
    echo "Для регионального расписания нужно отдельно получить название региона\n";
}
Ожидаемый результат
В исходных данных известна зона: +03:00
Расписание с этим фиксированным правилом: 15.07.2024 10:00 +03:00
Для регионального расписания нужно отдельно получить название региона

Во входной строке нет Europe/Moscow или другого региона, только+03:00. Возвращённый объект сохраняет это фиксированное смещение и летом. GetTimezone не угадывает географию: одинаковое смещение в один день может соответствовать разным региональным правилам. Такое правило подходит, если договор требует именно постоянное+03:00; для местного расписания регион запрашивают отдельно.

Сравните: DateTimeZone::getName, DateTimeImmutable::getOffset, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

date_timezone_getПолучить объект часового пояса, связанный с датой.

PHP 5.2+ · date

function date_timezone_get(DateTimeInterface $object): DateTimeZone|false

Получить объект часового пояса, связанный с датой.

Часть вызоваДоговор
objectDateTime или DateTimeImmutable.
ВозвратDateTimeZone при успехе, в опубликованном договоре допускается false при неудаче.
Что хранитсяИменованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона.
Путь даты в первом примере
  1. first хранит 15 января 10:00 и Europe/London
  2. getTimezone возвращает объект zone
  3. new дата 15 июля +тот же zone →10:00 +01:00
  4. getName/format дают подписи двух встреч
Назначить следующую встречу по правилам того же регионаСамостоятельный пример
<?php
$first = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone('Europe/London'));
$zone = date_timezone_get($first);
if ($zone === false) {
    echo "Новая встреча не назначена: зона недоступна\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'Регион расписания: ', $zone->getName(), "\n";
    echo 'Первая встреча: ', $first->format('d.m.Y H:i P'), "\n";
    echo 'Новая встреча: ', $next->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Регион расписания: Europe/London
Первая встреча: 15.01.2024 10:00 +00:00
Новая встреча: 15.07.2024 10:00 +01:00

GetTimezone возвращает объект DateTimeZone в zone. После проверки false передаём именно объект вторым аргументом нового конструктора. Он содержит правила London, поэтому летняя встреча в 10:00 получает+01:00, зимняя сохраняет+00:00. GetName возвращает строку только для подписи. Сама дата из первой встречи в новую не копируется: день и часы заданы новой строкой.

Увидеть пределы зоны, полученной из одного смещенияСамостоятельный пример
<?php
$received = new DateTimeImmutable('2024-01-15T10:00:00+03:00');
$zone = date_timezone_get($received);
if ($zone === false) {
    echo "Нельзя восстановить зону события\n";
} else {
    $next = new DateTimeImmutable('2024-07-15 10:00:00', $zone);
    echo 'В исходных данных известна зона: ', $zone->getName(), "\n";
    echo 'Расписание с этим фиксированным правилом: ', $next->format('d.m.Y H:i P'), "\n";
    echo "Для регионального расписания нужно отдельно получить название региона\n";
}
Ожидаемый результат
В исходных данных известна зона: +03:00
Расписание с этим фиксированным правилом: 15.07.2024 10:00 +03:00
Для регионального расписания нужно отдельно получить название региона

Во входной строке нет Europe/Moscow или другого региона, только+03:00. Возвращённый объект сохраняет это фиксированное смещение и летом. GetTimezone не угадывает географию: одинаковое смещение в один день может соответствовать разным региональным правилам. Такое правило подходит, если договор требует именно постоянное+03:00; для местного расписания регион запрашивают отдельно.

Сравните: DateTimeZone::getName, DateTimeImmutable::getOffset, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::getMicrosecondПолучить целое число микросекунд внутри секунды.

PHP 8.4+ · date

public function getMicrosecond(): int

Получить целое число микросекунд внутри секунды.

Часть вызоваДоговор
ПараметрыНет.
Возвратint дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах.
Различиеformat("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления.
ВерсияPHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением.
Путь даты в первом примере
  1. Два события в 12:34:56 UTC с долями 000123 /000323
  2. getMicrosecond возвращает int123 /323
  3. secondPart − firstPart →gap=200
  4. echo сообщает разницу 200 мкс
Измерить разницу двух событий внутри одной секундыСамостоятельный пример
<?php
$first = new DateTimeImmutable('2024-01-01 12:34:56.000123', new DateTimeZone('UTC'));
$second = new DateTimeImmutable('2024-01-01 12:34:56.000323', new DateTimeZone('UTC'));
$firstPart = $first->getMicrosecond();
$secondPart = $second->getMicrosecond();
$gap = $secondPart - $firstPart;
echo 'Событие A: ', $first->format('H:i:s.u'), "\n";
echo 'Событие B: ', $second->format('H:i:s.u'), "\n";
echo 'Разница внутри этой секунды: ', $gap, ' мкс', "\n";
Ожидаемый результат
Событие A: 12:34:56.000123
Событие B: 12:34:56.000323
Разница внутри этой секунды: 200 мкс

PHP8.4+. Метод вернул числа 123 и 323; их разность 200 микросекунд сохранена в gap и включена в подпись. Format(u) отдельно вывел 000123 и 000323 с ведущими нулями. Вычитание частей допустимо здесь потому, что целая секунда и момент в UTC у событий одинаковы. Для произвольных дат нужно учитывать также целые секунды; иначе переход через границу секунды даст неверную длительность.

Принять событие с нулевой долей секундыСамостоятельный пример
<?php
$event = new DateTimeImmutable('2024-01-01 12:34:56', new DateTimeZone('UTC'));
$part = $event->getMicrosecond();
echo 'Событие EVT-42: ', $event->format('H:i:s.u'), "\n";
if ($part === 0) {
    echo "Дробная часть равна нулю; событие принято\n";
} else {
    echo 'Дробная часть события: ', $part, ' мкс', "\n";
}
Ожидаемый результат
Событие EVT-42: 12:34:56.000000
Дробная часть равна нулю; событие принято

PHP8.4+. В строке не было дроби, поэтому метод вернул 0 — допустимое число микросекунд. Оно не сообщает об ошибке и само по себе не позволяет узнать, с какой точностью работали исходные часы. Дата передаётся дальше как обычное принятое событие; сведения о точности источника при необходимости хранят отдельно.

Сравните: DateTimeImmutable::setMicrosecond, DateTimeImmutable::getTimestamp, DateTimeImmutable::format.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::getMicrosecondПолучить целое число микросекунд внутри секунды.

PHP 8.4+ · date

public function getMicrosecond(): int

Получить целое число микросекунд внутри секунды.

Часть вызоваДоговор
ПараметрыНет.
Возвратint дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах.
Различиеformat("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления.
ВерсияPHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением.
Путь даты в первом примере
  1. Два события в 12:34:56 UTC с долями 000123 /000323
  2. getMicrosecond возвращает int123 /323
  3. secondPart − firstPart →gap=200
  4. echo сообщает разницу 200 мкс
Измерить разницу двух событий внутри одной секундыСамостоятельный пример
<?php
$first = new DateTime('2024-01-01 12:34:56.000123', new DateTimeZone('UTC'));
$second = new DateTime('2024-01-01 12:34:56.000323', new DateTimeZone('UTC'));
$firstPart = $first->getMicrosecond();
$secondPart = $second->getMicrosecond();
$gap = $secondPart - $firstPart;
echo 'Событие A: ', $first->format('H:i:s.u'), "\n";
echo 'Событие B: ', $second->format('H:i:s.u'), "\n";
echo 'Разница внутри этой секунды: ', $gap, ' мкс', "\n";
Ожидаемый результат
Событие A: 12:34:56.000123
Событие B: 12:34:56.000323
Разница внутри этой секунды: 200 мкс

PHP8.4+. Метод вернул числа 123 и 323; их разность 200 микросекунд сохранена в gap и включена в подпись. Format(u) отдельно вывел 000123 и 000323 с ведущими нулями. Вычитание частей допустимо здесь потому, что целая секунда и момент в UTC у событий одинаковы. Для произвольных дат нужно учитывать также целые секунды; иначе переход через границу секунды даст неверную длительность.

Принять событие с нулевой долей секундыСамостоятельный пример
<?php
$event = new DateTime('2024-01-01 12:34:56', new DateTimeZone('UTC'));
$part = $event->getMicrosecond();
echo 'Событие EVT-42: ', $event->format('H:i:s.u'), "\n";
if ($part === 0) {
    echo "Дробная часть равна нулю; событие принято\n";
} else {
    echo 'Дробная часть события: ', $part, ' мкс', "\n";
}
Ожидаемый результат
Событие EVT-42: 12:34:56.000000
Дробная часть равна нулю; событие принято

PHP8.4+. В строке не было дроби, поэтому метод вернул 0 — допустимое число микросекунд. Оно не сообщает об ошибке и само по себе не позволяет узнать, с какой точностью работали исходные часы. Дата передаётся дальше как обычное принятое событие; сведения о точности источника при необходимости хранят отдельно.

Сравните: DateTimeImmutable::setMicrosecond, DateTimeImmutable::getTimestamp, DateTimeImmutable::format.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::getMicrosecondПолучить целое число микросекунд внутри секунды.

PHP 8.4+ · date

public function getMicrosecond(): int

Получить целое число микросекунд внутри секунды.

Часть вызоваДоговор
ПараметрыНет.
Возвратint дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах.
Различиеformat("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления.
ВерсияPHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением.
Путь даты в первом примере
  1. Два события в 12:34:56 UTC с долями 000123 /000323
  2. getMicrosecond возвращает int123 /323
  3. secondPart − firstPart →gap=200
  4. echo сообщает разницу 200 мкс
Измерить разницу двух событий внутри одной секундыСамостоятельный пример
<?php
$first = new DateTimeImmutable('2024-01-01 12:34:56.000123', new DateTimeZone('UTC'));
$second = new DateTimeImmutable('2024-01-01 12:34:56.000323', new DateTimeZone('UTC'));
$firstPart = $first->getMicrosecond();
$secondPart = $second->getMicrosecond();
$gap = $secondPart - $firstPart;
echo 'Событие A: ', $first->format('H:i:s.u'), "\n";
echo 'Событие B: ', $second->format('H:i:s.u'), "\n";
echo 'Разница внутри этой секунды: ', $gap, ' мкс', "\n";
Ожидаемый результат
Событие A: 12:34:56.000123
Событие B: 12:34:56.000323
Разница внутри этой секунды: 200 мкс

PHP8.4+. Метод вернул числа 123 и 323; их разность 200 микросекунд сохранена в gap и включена в подпись. Format(u) отдельно вывел 000123 и 000323 с ведущими нулями. Вычитание частей допустимо здесь потому, что целая секунда и момент в UTC у событий одинаковы. Для произвольных дат нужно учитывать также целые секунды; иначе переход через границу секунды даст неверную длительность.

Принять событие с нулевой долей секундыСамостоятельный пример
<?php
$event = new DateTimeImmutable('2024-01-01 12:34:56', new DateTimeZone('UTC'));
$part = $event->getMicrosecond();
echo 'Событие EVT-42: ', $event->format('H:i:s.u'), "\n";
if ($part === 0) {
    echo "Дробная часть равна нулю; событие принято\n";
} else {
    echo 'Дробная часть события: ', $part, ' мкс', "\n";
}
Ожидаемый результат
Событие EVT-42: 12:34:56.000000
Дробная часть равна нулю; событие принято

PHP8.4+. В строке не было дроби, поэтому метод вернул 0 — допустимое число микросекунд. Оно не сообщает об ошибке и само по себе не позволяет узнать, с какой точностью работали исходные часы. Дата передаётся дальше как обычное принятое событие; сведения о точности источника при необходимости хранят отдельно.

Сравните: DateTimeImmutable::setMicrosecond, DateTimeImmutable::getTimestamp, DateTimeImmutable::format.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::setDateПоставить конкретный год, месяц и день, сохранив время суток и зону.

PHP 5.2+ · date

public function setDate(int $year, int $month, int $day): DateTime

Поставить конкретный год, месяц и день, сохранив время суток и зону.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
year / month / dayТри целых числа календарной даты. Выход за диапазон нормализуется: это не автоматическая проверка поля формы.
ПроверкаДля пользовательских частей сначала checkdate(month, day, year); порядок аргументов у checkdate другой.
Путь даты в первом примере
  1. date:15 января 2024, 10:30 UTC
  2. setDate получает 2024, 2, 29
  3. next:29 февраля 2024, 10:30 UTC
  4. format возвращает строку нового приглашения

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setDate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Встреча уже назначена на 15 января в 10:30 UTC. Нужно перенести её на 29 февраля того же года и сохранить время. Передаём три целых числа в порядке год, месяц, день. Скобки после setDate содержат новые компоненты; запятые разделяют аргументы.

Перенести встречу, сохранив её времяСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = $date->setDate(2024, 2, 29);
echo 'Новое приглашение: ', $next->format('d.m.Y H:i P'), "\n";
echo 'Исходная переменная после переноса: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Новое приглашение: 29.02.2024 10:30 +00:00
Исходная переменная после переноса: 29.02.2024 10:30

Аргументы 2024, 2, 29 — год, месяц, день. Они заменили только календарную часть; 10:30 и UTC сохранились. Next передан в format, полученная строка включена в новое приглашение. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отвергнуть невозможный день до изменения действующей встречиСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$year = 2023;
$month = 2;
$day = 29;
$normalized = (clone $date)->setDate($year, $month, $day);
echo 'Без проверки получилось бы: ', $normalized->format('d.m.Y H:i'), "\n";
if (!checkdate($month, $day, $year)) {
    echo 'Перенос отклонён; действующая встреча: ', $date->format('d.m.Y H:i'), "\n";
} else {
    $accepted = $date->setDate($year, $month, $day);
    echo 'Перенос принят: ', $accepted->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Без проверки получилось бы: 01.03.2023 10:30
Перенос отклонён; действующая встреча: 15.01.2024 10:30

2023 год не високосный: setDate на отдельной копии нормализует 29 февраля в 1 марта. Checkdate принимает другой порядок — месяц, день, год — и возвращает false для исходных частей. Знак ! превращает это в условие отказа. Рабочий date менялся бы только в else, поэтому ошибочный перенос не затронул действующую встречу. Демонстрационную копию normalized не нужно создавать при обычной валидации формы.

Рассчитать внутренний сдвиг номера месяцаСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = $date->setDate(2024, 14, 3);
echo 'Внутренние компоненты: год 2024, месяц 14, день 3', "\n";
echo 'Нормализованная дата отчёта: ', $next->format('d.m.Y H:i'), "\n";
if (!checkdate(14, 3, 2024)) {
    echo "Такие компоненты от посетителя нужно отклонить\n";
}
Ожидаемый результат
Внутренние компоненты: год 2024, месяц 14, день 3
Нормализованная дата отчёта: 03.02.2025 10:30
Такие компоненты от посетителя нужно отклонить

После двенадцатого месяца счёт продолжается в следующем году: 14 означает февраль 2025. Возвращённая дата может пригодиться внутреннему расчёту, который сознательно допускает перенос. Для поля «месяц» это другое требование: checkdate не принимает 14 как календарный месяц. Нормализация и валидация отвечают на разные вопросы.

Сравните: checkdate, DateTimeImmutable::setTime, DateTimeImmutable::setISODate.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::setDateПоставить конкретный год, месяц и день, сохранив время суток и зону.

PHP 5.5+ · date

public function setDate(int $year, int $month, int $day): DateTimeImmutable

Поставить конкретный год, месяц и день, сохранив время суток и зону.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
year / month / dayТри целых числа календарной даты. Выход за диапазон нормализуется: это не автоматическая проверка поля формы.
ПроверкаДля пользовательских частей сначала checkdate(month, day, year); порядок аргументов у checkdate другой.
Путь даты в первом примере
  1. date:15 января 2024, 10:30 UTC
  2. setDate получает 2024, 2, 29
  3. next:29 февраля 2024, 10:30 UTC
  4. format возвращает строку нового приглашения

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setDate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Встреча уже назначена на 15 января в 10:30 UTC. Нужно перенести её на 29 февраля того же года и сохранить время. Передаём три целых числа в порядке год, месяц, день. Скобки после setDate содержат новые компоненты; запятые разделяют аргументы.

Перенести встречу, сохранив её времяСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = $date->setDate(2024, 2, 29);
echo 'Новое приглашение: ', $next->format('d.m.Y H:i P'), "\n";
echo 'Исходная переменная после переноса: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Новое приглашение: 29.02.2024 10:30 +00:00
Исходная переменная после переноса: 15.01.2024 10:30

Аргументы 2024, 2, 29 — год, месяц, день. Они заменили только календарную часть; 10:30 и UTC сохранились. Next передан в format, полученная строка включена в новое приглашение. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Отвергнуть невозможный день до изменения действующей встречиСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$year = 2023;
$month = 2;
$day = 29;
$normalized = (clone $date)->setDate($year, $month, $day);
echo 'Без проверки получилось бы: ', $normalized->format('d.m.Y H:i'), "\n";
if (!checkdate($month, $day, $year)) {
    echo 'Перенос отклонён; действующая встреча: ', $date->format('d.m.Y H:i'), "\n";
} else {
    $accepted = $date->setDate($year, $month, $day);
    echo 'Перенос принят: ', $accepted->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Без проверки получилось бы: 01.03.2023 10:30
Перенос отклонён; действующая встреча: 15.01.2024 10:30

2023 год не високосный: setDate на отдельной копии нормализует 29 февраля в 1 марта. Checkdate принимает другой порядок — месяц, день, год — и возвращает false для исходных частей. Знак ! превращает это в условие отказа. Рабочий date менялся бы только в else, поэтому ошибочный перенос не затронул действующую встречу. Демонстрационную копию normalized не нужно создавать при обычной валидации формы.

Рассчитать внутренний сдвиг номера месяцаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = $date->setDate(2024, 14, 3);
echo 'Внутренние компоненты: год 2024, месяц 14, день 3', "\n";
echo 'Нормализованная дата отчёта: ', $next->format('d.m.Y H:i'), "\n";
if (!checkdate(14, 3, 2024)) {
    echo "Такие компоненты от посетителя нужно отклонить\n";
}
Ожидаемый результат
Внутренние компоненты: год 2024, месяц 14, день 3
Нормализованная дата отчёта: 03.02.2025 10:30
Такие компоненты от посетителя нужно отклонить

После двенадцатого месяца счёт продолжается в следующем году: 14 означает февраль 2025. Возвращённая дата может пригодиться внутреннему расчёту, который сознательно допускает перенос. Для поля «месяц» это другое требование: checkdate не принимает 14 как календарный месяц. Нормализация и валидация отвечают на разные вопросы.

Сравните: checkdate, DateTimeImmutable::setTime, DateTimeImmutable::setISODate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_date_setПоставить конкретный год, месяц и день, сохранив время суток и зону.

PHP 5.2+ · date

function date_date_set(DateTime $object, int $year, int $month, int $day): DateTime

Поставить конкретный год, месяц и день, сохранив время суток и зону.

Часть вызоваДоговор
objectИменно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект.
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
year / month / dayТри целых числа календарной даты. Выход за диапазон нормализуется: это не автоматическая проверка поля формы.
ПроверкаДля пользовательских частей сначала checkdate(month, day, year); порядок аргументов у checkdate другой.
Путь даты в первом примере
  1. date:15 января 2024, 10:30 UTC
  2. setDate получает 2024, 2, 29
  3. next:29 февраля 2024, 10:30 UTC
  4. format возвращает строку нового приглашения

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setDate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Встреча уже назначена на 15 января в 10:30 UTC. Нужно перенести её на 29 февраля того же года и сохранить время. Передаём три целых числа в порядке год, месяц, день. В процедурной форме перед ними идёт сам изменяемый объект DateTime.

Перенести встречу, сохранив её времяСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = date_date_set($date, 2024, 2, 29);
echo 'Новое приглашение: ', $next->format('d.m.Y H:i P'), "\n";
echo 'Исходная переменная после переноса: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Новое приглашение: 29.02.2024 10:30 +00:00
Исходная переменная после переноса: 29.02.2024 10:30

Аргументы 2024, 2, 29 — год, месяц, день. Они заменили только календарную часть; 10:30 и UTC сохранились. Next передан в format, полученная строка включена в новое приглашение. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отвергнуть невозможный день до изменения действующей встречиСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$year = 2023;
$month = 2;
$day = 29;
$normalized = date_date_set((clone $date), $year, $month, $day);
echo 'Без проверки получилось бы: ', $normalized->format('d.m.Y H:i'), "\n";
if (!checkdate($month, $day, $year)) {
    echo 'Перенос отклонён; действующая встреча: ', $date->format('d.m.Y H:i'), "\n";
} else {
    $accepted = date_date_set($date, $year, $month, $day);
    echo 'Перенос принят: ', $accepted->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Без проверки получилось бы: 01.03.2023 10:30
Перенос отклонён; действующая встреча: 15.01.2024 10:30

2023 год не високосный: setDate на отдельной копии нормализует 29 февраля в 1 марта. Checkdate принимает другой порядок — месяц, день, год — и возвращает false для исходных частей. Знак ! превращает это в условие отказа. Рабочий date менялся бы только в else, поэтому ошибочный перенос не затронул действующую встречу. Демонстрационную копию normalized не нужно создавать при обычной валидации формы.

Рассчитать внутренний сдвиг номера месяцаСамостоятельный пример
<?php
$date = new DateTime('2024-01-15 10:30:00', new DateTimeZone('UTC'));
$next = date_date_set($date, 2024, 14, 3);
echo 'Внутренние компоненты: год 2024, месяц 14, день 3', "\n";
echo 'Нормализованная дата отчёта: ', $next->format('d.m.Y H:i'), "\n";
if (!checkdate(14, 3, 2024)) {
    echo "Такие компоненты от посетителя нужно отклонить\n";
}
Ожидаемый результат
Внутренние компоненты: год 2024, месяц 14, день 3
Нормализованная дата отчёта: 03.02.2025 10:30
Такие компоненты от посетителя нужно отклонить

После двенадцатого месяца счёт продолжается в следующем году: 14 означает февраль 2025. Возвращённая дата может пригодиться внутреннему расчёту, который сознательно допускает перенос. Для поля «месяц» это другое требование: checkdate не принимает 14 как календарный месяц. Нормализация и валидация отвечают на разные вопросы.

Сравните: checkdate, DateTimeImmutable::setTime, DateTimeImmutable::setISODate.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::setISODateНайти дату по ISO-году, номеру недели и дню недели.

PHP 5.2+ · date

public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime

Найти дату по ISO-году, номеру недели и дню недели.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
yearГод ISO-недели; около Нового года он может отличаться от календарного года даты.
weekНомер ISO-недели, обычно 1…52/53.
dayOfWeek1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы.
ЧасыВремя суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0).
Путь даты в первом примере
  1. Выбор:ISO-год 2025, неделя 1
  2. setISODate(2025,1) →понедельник 30.12.2024
  3. setTime(0,0) →начало дня
  4. format →граница недельного отчёта

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setISODate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Выбран отчёт за первую ISO-неделю 2025 года. Недели начинаются в понедельник; ISO-год недели и календарный год её понедельника могут различаться. Метод получает год недели, номер недели и необязательный день недели от 1 до 7.

Начать недельный отчёт с правильного понедельникаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$monday = $date->setISODate(2025, 1);
echo 'Понедельник после выбора недели: ', $monday->format('d.m.Y H:i'), "\n";
$start = $monday->setTime(0, 0);
echo 'Отчётная неделя: ', $start->format('o-\WW'), "\n";
echo 'Начало отбора событий: ', $start->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Понедельник после выбора недели: 30.12.2024 10:30
Отчётная неделя: 2025-W01
Начало отбора событий: 30.12.2024 00:00 +00:00

Первый аргумент — ISO-год 2025, второй — неделя 1. Третий не указан, поэтому dayOfWeek=1 выбирает понедельник. Он приходится на 30 декабря календарного 2024 года. SetISODate сохранил 10:30; отдельный setTime(0,0) задаёт полночь для границы отчёта. В format o связан с W, а обратный слеш перед первой W выводит буквальную букву. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отличить конец недели от переноса на следующуюСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$sunday = (clone $date)->setISODate(2025, 1, 7);
$nextMonday = (clone $date)->setISODate(2025, 1, 8);
echo 'Последний день недели: ', $sunday->format('d.m.Y'), "\n";
echo 'День 8, уже следующая неделя: ', $nextMonday->format('d.m.Y'), "\n";
Ожидаемый результат
Последний день недели: 05.01.2025
День 8, уже следующая неделя: 06.01.2025

7 выбирает воскресенье той же недели, 8 нормализуется в следующий понедельник. Копии делают два расчёта независимыми и для изменяемого DateTime. Для выбора дня недели посетителем ограничивайте ввод 1…7; нормализация не заменяет эту проверку. Для отбора событий удобна правая исключённая граница следующего понедельника, чтобы включить весь последний день.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::format, DateTimeImmutable::setTime.

Время, часовые пояса и календарь. Практические задачи с датами.

Проверить, существует ли выбранная неделя в указанном годуСамостоятельный пример
<?php
$year = 2021;
$week = 53;
$base = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$candidate = $base->setISODate($year, $week);
$requested = sprintf('%04d-%02d', $year, $week);
echo 'Запрошена ISO-неделя: ', $requested, "\n";
echo 'Метод построил: ', $candidate->format('o-W'), ' с ', $candidate->format('d.m.Y'), "\n";
if ($candidate->format('o-W') !== $requested) {
    echo "Отчёт не построен: в этом ISO-году нет такой недели\n";
}
Ожидаемый результат
Запрошена ISO-неделя: 2021-53
Метод построил: 2022-01 с 03.01.2022
Отчёт не построен: в этом ISO-году нет такой недели

Диапазон 1…53 необходим, но недостаточен: не каждый ISO-год имеет неделю 53. Sprintf строит ожидаемую строку с четырьмя цифрами года и двумя номера недели. Обратный format(o-W) выявляет нормализацию в 2022-01, поэтому программа не строит отчёт по другой неделе. Для произвольного ввода сначала отдельно проверяют допустимые для сервиса годы и числовые диапазоны.

DateTimeImmutable::setISODateНайти дату по ISO-году, номеру недели и дню недели.

PHP 5.5+ · date

public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable

Найти дату по ISO-году, номеру недели и дню недели.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
yearГод ISO-недели; около Нового года он может отличаться от календарного года даты.
weekНомер ISO-недели, обычно 1…52/53.
dayOfWeek1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы.
ЧасыВремя суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0).
Путь даты в первом примере
  1. Выбор:ISO-год 2025, неделя 1
  2. setISODate(2025,1) →понедельник 30.12.2024
  3. setTime(0,0) →начало дня
  4. format →граница недельного отчёта

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setISODate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Выбран отчёт за первую ISO-неделю 2025 года. Недели начинаются в понедельник; ISO-год недели и календарный год её понедельника могут различаться. Метод получает год недели, номер недели и необязательный день недели от 1 до 7.

Начать недельный отчёт с правильного понедельникаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$monday = $date->setISODate(2025, 1);
echo 'Понедельник после выбора недели: ', $monday->format('d.m.Y H:i'), "\n";
$start = $monday->setTime(0, 0);
echo 'Отчётная неделя: ', $start->format('o-\WW'), "\n";
echo 'Начало отбора событий: ', $start->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Понедельник после выбора недели: 30.12.2024 10:30
Отчётная неделя: 2025-W01
Начало отбора событий: 30.12.2024 00:00 +00:00

Первый аргумент — ISO-год 2025, второй — неделя 1. Третий не указан, поэтому dayOfWeek=1 выбирает понедельник. Он приходится на 30 декабря календарного 2024 года. SetISODate сохранил 10:30; отдельный setTime(0,0) задаёт полночь для границы отчёта. В format o связан с W, а обратный слеш перед первой W выводит буквальную букву. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Отличить конец недели от переноса на следующуюСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$sunday = (clone $date)->setISODate(2025, 1, 7);
$nextMonday = (clone $date)->setISODate(2025, 1, 8);
echo 'Последний день недели: ', $sunday->format('d.m.Y'), "\n";
echo 'День 8, уже следующая неделя: ', $nextMonday->format('d.m.Y'), "\n";
Ожидаемый результат
Последний день недели: 05.01.2025
День 8, уже следующая неделя: 06.01.2025

7 выбирает воскресенье той же недели, 8 нормализуется в следующий понедельник. Копии делают два расчёта независимыми и для изменяемого DateTime. Для выбора дня недели посетителем ограничивайте ввод 1…7; нормализация не заменяет эту проверку. Для отбора событий удобна правая исключённая граница следующего понедельника, чтобы включить весь последний день.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::format, DateTimeImmutable::setTime.

Время, часовые пояса и календарь. Практические задачи с датами.

Проверить, существует ли выбранная неделя в указанном годуСамостоятельный пример
<?php
$year = 2021;
$week = 53;
$base = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$candidate = $base->setISODate($year, $week);
$requested = sprintf('%04d-%02d', $year, $week);
echo 'Запрошена ISO-неделя: ', $requested, "\n";
echo 'Метод построил: ', $candidate->format('o-W'), ' с ', $candidate->format('d.m.Y'), "\n";
if ($candidate->format('o-W') !== $requested) {
    echo "Отчёт не построен: в этом ISO-году нет такой недели\n";
}
Ожидаемый результат
Запрошена ISO-неделя: 2021-53
Метод построил: 2022-01 с 03.01.2022
Отчёт не построен: в этом ISO-году нет такой недели

Диапазон 1…53 необходим, но недостаточен: не каждый ISO-год имеет неделю 53. Sprintf строит ожидаемую строку с четырьмя цифрами года и двумя номера недели. Обратный format(o-W) выявляет нормализацию в 2022-01, поэтому программа не строит отчёт по другой неделе. Для произвольного ввода сначала отдельно проверяют допустимые для сервиса годы и числовые диапазоны.

date_isodate_setНайти дату по ISO-году, номеру недели и дню недели.

PHP 5.2+ · date

function date_isodate_set(DateTime $object, int $year, int $week, int $dayOfWeek = 1): DateTime

Найти дату по ISO-году, номеру недели и дню недели.

Часть вызоваДоговор
objectИменно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект.
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
yearГод ISO-недели; около Нового года он может отличаться от календарного года даты.
weekНомер ISO-недели, обычно 1…52/53.
dayOfWeek1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы.
ЧасыВремя суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0).
Путь даты в первом примере
  1. Выбор:ISO-год 2025, неделя 1
  2. setISODate(2025,1) →понедельник 30.12.2024
  3. setTime(0,0) →начало дня
  4. format →граница недельного отчёта

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setISODate(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Выбран отчёт за первую ISO-неделю 2025 года. Недели начинаются в понедельник; ISO-год недели и календарный год её понедельника могут различаться. Метод получает год недели, номер недели и необязательный день недели от 1 до 7.

Начать недельный отчёт с правильного понедельникаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$monday = date_isodate_set($date, 2025, 1);
echo 'Понедельник после выбора недели: ', $monday->format('d.m.Y H:i'), "\n";
$start = $monday->setTime(0, 0);
echo 'Отчётная неделя: ', $start->format('o-\WW'), "\n";
echo 'Начало отбора событий: ', $start->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Понедельник после выбора недели: 30.12.2024 10:30
Отчётная неделя: 2025-W01
Начало отбора событий: 30.12.2024 00:00 +00:00

Первый аргумент — ISO-год 2025, второй — неделя 1. Третий не указан, поэтому dayOfWeek=1 выбирает понедельник. Он приходится на 30 декабря календарного 2024 года. SetISODate сохранил 10:30; отдельный setTime(0,0) задаёт полночь для границы отчёта. В format o связан с W, а обратный слеш перед первой W выводит буквальную букву. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отличить конец недели от переноса на следующуюСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$sunday = date_isodate_set((clone $date), 2025, 1, 7);
$nextMonday = date_isodate_set((clone $date), 2025, 1, 8);
echo 'Последний день недели: ', $sunday->format('d.m.Y'), "\n";
echo 'День 8, уже следующая неделя: ', $nextMonday->format('d.m.Y'), "\n";
Ожидаемый результат
Последний день недели: 05.01.2025
День 8, уже следующая неделя: 06.01.2025

7 выбирает воскресенье той же недели, 8 нормализуется в следующий понедельник. Копии делают два расчёта независимыми и для изменяемого DateTime. Для выбора дня недели посетителем ограничивайте ввод 1…7; нормализация не заменяет эту проверку. Для отбора событий удобна правая исключённая граница следующего понедельника, чтобы включить весь последний день.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::format, DateTimeImmutable::setTime.

Время, часовые пояса и календарь. Практические задачи с датами.

Проверить, существует ли выбранная неделя в указанном годуСамостоятельный пример
<?php
$year = 2021;
$week = 53;
$base = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$candidate = date_isodate_set($base, $year, $week);
$requested = sprintf('%04d-%02d', $year, $week);
echo 'Запрошена ISO-неделя: ', $requested, "\n";
echo 'Метод построил: ', $candidate->format('o-W'), ' с ', $candidate->format('d.m.Y'), "\n";
if ($candidate->format('o-W') !== $requested) {
    echo "Отчёт не построен: в этом ISO-году нет такой недели\n";
}
Ожидаемый результат
Запрошена ISO-неделя: 2021-53
Метод построил: 2022-01 с 03.01.2022
Отчёт не построен: в этом ISO-году нет такой недели

Диапазон 1…53 необходим, но недостаточен: не каждый ISO-год имеет неделю 53. Sprintf строит ожидаемую строку с четырьмя цифрами года и двумя номера недели. Обратный format(o-W) выявляет нормализацию в 2022-01, поэтому программа не строит отчёт по другой неделе. Для произвольного ввода сначала отдельно проверяют допустимые для сервиса годы и числовые диапазоны.

DateTime::setTimeНазначить часы, минуты, секунды и микросекунды на существующую дату.

PHP 5.2+; microsecond с 7.1 · date

public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTime

Назначить часы, минуты, секунды и микросекунды на существующую дату.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
hour / minuteЦелые часы и минуты.
second / microsecondПо умолчанию 0 и 0; добавление microsecond — PHP 7.1. Вызов с двумя аргументами сбрасывает прежние секунды и дробь.
Нормализация65 секунд переносятся в следующую минуту, 25 часов — в следующий день. Это не строгая валидация.
DSTС PHP 8.1 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно.
Путь даты в первом примере
  1. date:15 июня,10:30:45.123456 UTC
  2. setTime получает 14,20 и defaults 0,0
  3. next:15 июня,14:20:00.000000 UTC
  4. format возвращает точное время звонка

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTime(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Нужно оставить день встречи и поставить 14:20 ровно. У setTime два обязательных аргумента — час и минута; третий и четвёртый — секунды и микросекунды — по умолчанию равны нулю. Сам расчёт не делает телефонный звонок.

Назначить точное время звонкаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:45.123456', new DateTimeZone('UTC'));
$next = $date->setTime(14, 20);
echo 'Звонок назначен: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Исходная переменная: ', $date->format('H:i:s.u'), "\n";
Ожидаемый результат
Звонок назначен: 15.06.2024 14:20:00.000000 +00:00
Исходная переменная: 14:20:00.000000

14 и 20 попали в параметры hour/minute. Second и microsecond пропущены и получают 0, поэтому прежние 45.123456 секунды не остались в сроке. День и зона сохранены. Next форматируется в подтверждение звонка. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Увидеть результат лишних часов и секундСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$normalized = (clone $date)->setTime(25, 0, 65, 125000);
echo 'Получено из компонентов 25:00:65 и 125000 мкс: ', $normalized->format('d.m.Y H:i:s.u'), "\n";
$hour = 25;
if ($hour < 0 || $hour > 23) {
    echo 'Часы формы отклонены; прежняя встреча: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Получено из компонентов 25:00:65 и 125000 мкс: 16.06.2024 01:01:05.125000
Часы формы отклонены; прежняя встреча: 15.06.2024 10:30

25 часов превращаются в следующий день и 1 час; 65 секунд — в 1 минуту и 5 секунд. Доля 125000 микросекунд остаётся 0.125000 секунды. Для демонстрации использована копия, затем показана отдельная проверка часа формы. В полном вводе так же проверяют минуты/секунды 0…59 и микросекунды 0…999999 до вызова; нормализация допустима только по правилу вашей задачи.

Различить две встречи с одинаковыми местными часамиСамостоятельный пример
<?php
$zone = new DateTimeZone('America/New_York');
$date = new DateTime('2024-11-03 00:00:00', $zone);
$early = $date->setTime(1, 30);
$lateInput = new DateTimeImmutable('2024-11-03T01:30:00-05:00');
$late = $lateInput->setTimezone($zone);
echo 'Первое вхождение 01:30: ', $early->format('d.m.Y H:i P'), "\n";
echo 'Второе, выбранное явно: ', $late->format('d.m.Y H:i P'), "\n";
echo 'Между встречами секунд: ', $late->getTimestamp() - $early->getTimestamp(), "\n";
Ожидаемый результат
Первое вхождение 01:30: 03.11.2024 01:30 -04:00
Второе, выбранное явно: 03.11.2024 01:30 -05:00
Между встречами секунд: 3600

В эту ночь часы переводят назад, поэтому 01:30 наступает дважды. С PHP8.1 setTime выбирает раннее вхождение со смещением −04:00; старые версии выбирали позднее. Строка lateInput явно задаёт −05:00, после чего зона New York сохраняет выбранный момент. Одинаковая подпись без смещения скрывала бы разницу в час. При бронировании нужно согласовать выбор с пользователем, а не молча считать обе строки одной встречей.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::setMicrosecond, DateTimeZone::getTransitions.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::setTimeНазначить часы, минуты, секунды и микросекунды на существующую дату.

PHP 5.5+; microsecond с 7.1 · date

public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTimeImmutable

Назначить часы, минуты, секунды и микросекунды на существующую дату.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
hour / minuteЦелые часы и минуты.
second / microsecondПо умолчанию 0 и 0; добавление microsecond — PHP 7.1. Вызов с двумя аргументами сбрасывает прежние секунды и дробь.
Нормализация65 секунд переносятся в следующую минуту, 25 часов — в следующий день. Это не строгая валидация.
DSTС PHP 8.1 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно.
Путь даты в первом примере
  1. date:15 июня,10:30:45.123456 UTC
  2. setTime получает 14,20 и defaults 0,0
  3. next:15 июня,14:20:00.000000 UTC
  4. format возвращает точное время звонка

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTime(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Нужно оставить день встречи и поставить 14:20 ровно. У setTime два обязательных аргумента — час и минута; третий и четвёртый — секунды и микросекунды — по умолчанию равны нулю. Сам расчёт не делает телефонный звонок.

Назначить точное время звонкаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:45.123456', new DateTimeZone('UTC'));
$next = $date->setTime(14, 20);
echo 'Звонок назначен: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Исходная переменная: ', $date->format('H:i:s.u'), "\n";
Ожидаемый результат
Звонок назначен: 15.06.2024 14:20:00.000000 +00:00
Исходная переменная: 10:30:45.123456

14 и 20 попали в параметры hour/minute. Second и microsecond пропущены и получают 0, поэтому прежние 45.123456 секунды не остались в сроке. День и зона сохранены. Next форматируется в подтверждение звонка. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Увидеть результат лишних часов и секундСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$normalized = (clone $date)->setTime(25, 0, 65, 125000);
echo 'Получено из компонентов 25:00:65 и 125000 мкс: ', $normalized->format('d.m.Y H:i:s.u'), "\n";
$hour = 25;
if ($hour < 0 || $hour > 23) {
    echo 'Часы формы отклонены; прежняя встреча: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Получено из компонентов 25:00:65 и 125000 мкс: 16.06.2024 01:01:05.125000
Часы формы отклонены; прежняя встреча: 15.06.2024 10:30

25 часов превращаются в следующий день и 1 час; 65 секунд — в 1 минуту и 5 секунд. Доля 125000 микросекунд остаётся 0.125000 секунды. Для демонстрации использована копия, затем показана отдельная проверка часа формы. В полном вводе так же проверяют минуты/секунды 0…59 и микросекунды 0…999999 до вызова; нормализация допустима только по правилу вашей задачи.

Различить две встречи с одинаковыми местными часамиСамостоятельный пример
<?php
$zone = new DateTimeZone('America/New_York');
$date = new DateTimeImmutable('2024-11-03 00:00:00', $zone);
$early = $date->setTime(1, 30);
$lateInput = new DateTimeImmutable('2024-11-03T01:30:00-05:00');
$late = $lateInput->setTimezone($zone);
echo 'Первое вхождение 01:30: ', $early->format('d.m.Y H:i P'), "\n";
echo 'Второе, выбранное явно: ', $late->format('d.m.Y H:i P'), "\n";
echo 'Между встречами секунд: ', $late->getTimestamp() - $early->getTimestamp(), "\n";
Ожидаемый результат
Первое вхождение 01:30: 03.11.2024 01:30 -04:00
Второе, выбранное явно: 03.11.2024 01:30 -05:00
Между встречами секунд: 3600

В эту ночь часы переводят назад, поэтому 01:30 наступает дважды. С PHP8.1 setTime выбирает раннее вхождение со смещением −04:00; старые версии выбирали позднее. Строка lateInput явно задаёт −05:00, после чего зона New York сохраняет выбранный момент. Одинаковая подпись без смещения скрывала бы разницу в час. При бронировании нужно согласовать выбор с пользователем, а не молча считать обе строки одной встречей.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::setMicrosecond, DateTimeZone::getTransitions.

Время, часовые пояса и календарь. Практические задачи с датами.

date_time_setНазначить часы, минуты, секунды и микросекунды на существующую дату.

PHP 5.2+; microsecond с 7.1 · date

function date_time_set(DateTime $object, int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTime

Назначить часы, минуты, секунды и микросекунды на существующую дату.

Часть вызоваДоговор
objectИменно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект.
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
hour / minuteЦелые часы и минуты.
second / microsecondПо умолчанию 0 и 0; добавление microsecond — PHP 7.1. Вызов с двумя аргументами сбрасывает прежние секунды и дробь.
Нормализация65 секунд переносятся в следующую минуту, 25 часов — в следующий день. Это не строгая валидация.
DSTС PHP 8.1 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно.
Путь даты в первом примере
  1. date:15 июня,10:30:45.123456 UTC
  2. setTime получает 14,20 и defaults 0,0
  3. next:15 июня,14:20:00.000000 UTC
  4. format возвращает точное время звонка

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTime(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Нужно оставить день встречи и поставить 14:20 ровно. У setTime два обязательных аргумента — час и минута; третий и четвёртый — секунды и микросекунды — по умолчанию равны нулю. Сам расчёт не делает телефонный звонок.

Назначить точное время звонкаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:45.123456', new DateTimeZone('UTC'));
$next = date_time_set($date, 14, 20);
echo 'Звонок назначен: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Исходная переменная: ', $date->format('H:i:s.u'), "\n";
Ожидаемый результат
Звонок назначен: 15.06.2024 14:20:00.000000 +00:00
Исходная переменная: 14:20:00.000000

14 и 20 попали в параметры hour/minute. Second и microsecond пропущены и получают 0, поэтому прежние 45.123456 секунды не остались в сроке. День и зона сохранены. Next форматируется в подтверждение звонка. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Увидеть результат лишних часов и секундСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$normalized = date_time_set((clone $date), 25, 0, 65, 125000);
echo 'Получено из компонентов 25:00:65 и 125000 мкс: ', $normalized->format('d.m.Y H:i:s.u'), "\n";
$hour = 25;
if ($hour < 0 || $hour > 23) {
    echo 'Часы формы отклонены; прежняя встреча: ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Получено из компонентов 25:00:65 и 125000 мкс: 16.06.2024 01:01:05.125000
Часы формы отклонены; прежняя встреча: 15.06.2024 10:30

25 часов превращаются в следующий день и 1 час; 65 секунд — в 1 минуту и 5 секунд. Доля 125000 микросекунд остаётся 0.125000 секунды. Для демонстрации использована копия, затем показана отдельная проверка часа формы. В полном вводе так же проверяют минуты/секунды 0…59 и микросекунды 0…999999 до вызова; нормализация допустима только по правилу вашей задачи.

Различить две встречи с одинаковыми местными часамиСамостоятельный пример
<?php
$zone = new DateTimeZone('America/New_York');
$date = new DateTime('2024-11-03 00:00:00', $zone);
$early = date_time_set($date, 1, 30);
$lateInput = new DateTimeImmutable('2024-11-03T01:30:00-05:00');
$late = $lateInput->setTimezone($zone);
echo 'Первое вхождение 01:30: ', $early->format('d.m.Y H:i P'), "\n";
echo 'Второе, выбранное явно: ', $late->format('d.m.Y H:i P'), "\n";
echo 'Между встречами секунд: ', $late->getTimestamp() - $early->getTimestamp(), "\n";
Ожидаемый результат
Первое вхождение 01:30: 03.11.2024 01:30 -04:00
Второе, выбранное явно: 03.11.2024 01:30 -05:00
Между встречами секунд: 3600

В эту ночь часы переводят назад, поэтому 01:30 наступает дважды. С PHP8.1 setTime выбирает раннее вхождение со смещением −04:00; старые версии выбирали позднее. Строка lateInput явно задаёт −05:00, после чего зона New York сохраняет выбранный момент. Одинаковая подпись без смещения скрывала бы разницу в час. При бронировании нужно согласовать выбор с пользователем, а не молча считать обе строки одной встречей.

Сравните: DateTimeImmutable::setDate, DateTimeImmutable::setMicrosecond, DateTimeZone::getTransitions.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::setTimestampУстановить точный момент по целым секундам Unix, сохранив зону отображения.

PHP 5.3+ · date

public function setTimestamp(int $timestamp): DateTime

Установить точный момент по целым секундам Unix, сохранив зону отображения.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
timestampint секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int.
ДоляУстановка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC.
Большие значенияДля значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона.
Путь даты в первом примере
  1. Источник:1704067200 секунд Unix
  2. setTimestamp заменяет момент, сохраняя Europe/Moscow
  3. next:1 января 2024,03:00 +03:00
  4. format →строка события EVT-41

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.

Показать присланную Unix-метку в зоне журналаСамостоятельный пример
<?php
$timestamp = 1704067200;
$date = new DateTime('2024-06-15 10:30:00.125456', new DateTimeZone('Europe/Moscow'));
$next = $date->setTimestamp($timestamp);
echo 'Событие EVT-41: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Зона журнала: ', $next->getTimezone()->getName(), "\n";
Ожидаемый результат
Событие EVT-41: 01.01.2024 03:00:00.000000 +03:00
Зона журнала: Europe/Moscow

1704067200 означает полночь 1 января 2024 UTC. SetTimestamp заменил момент, оставив Europe/Moscow, поэтому подпись показывает 03:00. Переданы целые секунды — прежняя дробь 125456 обнулилась. Next используется для строки события; getTimezone/getName показывают сохранённое правило отображения. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Явно сообщить о потере миллисекунд при округлении к секундамСамостоятельный пример
<?php
$milliseconds = 1500;
$seconds = intdiv($milliseconds, 1000);
$discarded = $milliseconds % 1000;
$date = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$next = $date->setTimestamp($seconds);
echo 'Получено: ', $milliseconds, ' мс после начала Unix', "\n";
echo 'В журнале с точностью до секунды: ', $next->format('Y-m-d H:i:s.u'), "\n";
echo 'Отброшенная доля: ', $discarded, ' мс', "\n";
Ожидаемый результат
Получено: 1500 мс после начала Unix
В журнале с точностью до секунды: 1970-01-01 00:00:01.000000
Отброшенная доля: 500 мс

Intdiv(1500,1000) возвращает 1 целую секунду, % даёт остаток 500. SetTimestamp получает 1, а не 1500; строка результата честно показывает нулевую долю. Пример намеренно допускает потерю доли. Если договор требует миллисекунды, используйте дробную createFromTimestamp в PHP8.4+ или восстановите дробную часть отдельно. Этот простой расчёт ограничен неотрицательным вводом: для моментов до epoch нужно отдельно выбрать правила деления и остатка.

Сравните: DateTimeImmutable::getTimestamp, DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::setTimestampУстановить точный момент по целым секундам Unix, сохранив зону отображения.

PHP 5.5+ · date

public function setTimestamp(int $timestamp): DateTimeImmutable

Установить точный момент по целым секундам Unix, сохранив зону отображения.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
timestampint секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int.
ДоляУстановка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC.
Большие значенияДля значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона.
Путь даты в первом примере
  1. Источник:1704067200 секунд Unix
  2. setTimestamp заменяет момент, сохраняя Europe/Moscow
  3. next:1 января 2024,03:00 +03:00
  4. format →строка события EVT-41

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.

Показать присланную Unix-метку в зоне журналаСамостоятельный пример
<?php
$timestamp = 1704067200;
$date = new DateTimeImmutable('2024-06-15 10:30:00.125456', new DateTimeZone('Europe/Moscow'));
$next = $date->setTimestamp($timestamp);
echo 'Событие EVT-41: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Зона журнала: ', $next->getTimezone()->getName(), "\n";
Ожидаемый результат
Событие EVT-41: 01.01.2024 03:00:00.000000 +03:00
Зона журнала: Europe/Moscow

1704067200 означает полночь 1 января 2024 UTC. SetTimestamp заменил момент, оставив Europe/Moscow, поэтому подпись показывает 03:00. Переданы целые секунды — прежняя дробь 125456 обнулилась. Next используется для строки события; getTimezone/getName показывают сохранённое правило отображения. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Явно сообщить о потере миллисекунд при округлении к секундамСамостоятельный пример
<?php
$milliseconds = 1500;
$seconds = intdiv($milliseconds, 1000);
$discarded = $milliseconds % 1000;
$date = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$next = $date->setTimestamp($seconds);
echo 'Получено: ', $milliseconds, ' мс после начала Unix', "\n";
echo 'В журнале с точностью до секунды: ', $next->format('Y-m-d H:i:s.u'), "\n";
echo 'Отброшенная доля: ', $discarded, ' мс', "\n";
Ожидаемый результат
Получено: 1500 мс после начала Unix
В журнале с точностью до секунды: 1970-01-01 00:00:01.000000
Отброшенная доля: 500 мс

Intdiv(1500,1000) возвращает 1 целую секунду, % даёт остаток 500. SetTimestamp получает 1, а не 1500; строка результата честно показывает нулевую долю. Пример намеренно допускает потерю доли. Если договор требует миллисекунды, используйте дробную createFromTimestamp в PHP8.4+ или восстановите дробную часть отдельно. Этот простой расчёт ограничен неотрицательным вводом: для моментов до epoch нужно отдельно выбрать правила деления и остатка.

Сравните: DateTimeImmutable::getTimestamp, DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

date_timestamp_setУстановить точный момент по целым секундам Unix, сохранив зону отображения.

PHP 5.3+ · date

function date_timestamp_set(DateTime $object, int $timestamp): DateTime

Установить точный момент по целым секундам Unix, сохранив зону отображения.

Часть вызоваДоговор
objectИменно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект.
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
timestampint секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int.
ДоляУстановка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC.
Большие значенияДля значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона.
Путь даты в первом примере
  1. Источник:1704067200 секунд Unix
  2. setTimestamp заменяет момент, сохраняя Europe/Moscow
  3. next:1 января 2024,03:00 +03:00
  4. format →строка события EVT-41

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.

Показать присланную Unix-метку в зоне журналаСамостоятельный пример
<?php
$timestamp = 1704067200;
$date = new DateTime('2024-06-15 10:30:00.125456', new DateTimeZone('Europe/Moscow'));
$next = date_timestamp_set($date, $timestamp);
echo 'Событие EVT-41: ', $next->format('d.m.Y H:i:s.u P'), "\n";
echo 'Зона журнала: ', $next->getTimezone()->getName(), "\n";
Ожидаемый результат
Событие EVT-41: 01.01.2024 03:00:00.000000 +03:00
Зона журнала: Europe/Moscow

1704067200 означает полночь 1 января 2024 UTC. SetTimestamp заменил момент, оставив Europe/Moscow, поэтому подпись показывает 03:00. Переданы целые секунды — прежняя дробь 125456 обнулилась. Next используется для строки события; getTimezone/getName показывают сохранённое правило отображения. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Явно сообщить о потере миллисекунд при округлении к секундамСамостоятельный пример
<?php
$milliseconds = 1500;
$seconds = intdiv($milliseconds, 1000);
$discarded = $milliseconds % 1000;
$date = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$next = date_timestamp_set($date, $seconds);
echo 'Получено: ', $milliseconds, ' мс после начала Unix', "\n";
echo 'В журнале с точностью до секунды: ', $next->format('Y-m-d H:i:s.u'), "\n";
echo 'Отброшенная доля: ', $discarded, ' мс', "\n";
Ожидаемый результат
Получено: 1500 мс после начала Unix
В журнале с точностью до секунды: 1970-01-01 00:00:01.000000
Отброшенная доля: 500 мс

Intdiv(1500,1000) возвращает 1 целую секунду, % даёт остаток 500. SetTimestamp получает 1, а не 1500; строка результата честно показывает нулевую долю. Пример намеренно допускает потерю доли. Если договор требует миллисекунды, используйте дробную createFromTimestamp в PHP8.4+ или восстановите дробную часть отдельно. Этот простой расчёт ограничен неотрицательным вводом: для моментов до epoch нужно отдельно выбрать правила деления и остатка.

Сравните: DateTimeImmutable::getTimestamp, DateTimeImmutable::createFromTimestamp, DateTimeImmutable::setMicrosecond.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::setTimezoneПоказать тот же момент на часах другого региона.

PHP 5.2+ · date

public function setTimezone(DateTimeZone $timezone): DateTime

Показать тот же момент на часах другого региона.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
timezoneПодготовленный DateTimeZone с нужным идентификатором или смещением.
ИнвариантМомент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение.
ГраницаЭто не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной.
Путь даты в первом примере
  1. API:15 июня 07:00 UTC
  2. DateTimeZone(Europe/Moscow) →правила отображения
  3. setTimezone возвращает local:10:00 +03:00
  4. Подпись другая, прошедший сдвиг события =0 секунд

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimezone(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Время API известно в UTC:07:00. Посетитель выбрал Москву, поэтому нужен другой вид тех же часов. SetTimezone получает объект DateTimeZone; эта операция меняет представление, сохраняя момент.

Показать посетителю местное время одного событияСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 07:00:00', new DateTimeZone('UTC'));
$stamp = $date->getTimestamp();
echo 'Получено от API: ', $date->format('d.m.Y H:i P'), "\n";
$local = $date->setTimezone(new DateTimeZone('Europe/Moscow'));
echo 'Посетителю в Москве: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Сдвиг самого момента в секундах: ', $local->getTimestamp() - $stamp, "\n";
Ожидаемый результат
Получено от API: 15.06.2024 07:00 +00:00
Посетителю в Москве: 15.06.2024 10:00 +03:00
Сдвиг самого момента в секундах: 0

New DateTimeZone создаёт правила Moscow и передаёт их в setTimezone. Local содержит тот же момент, показанный как 10:00 +03:00. Разность timestamp равна 0: встречу не перенесли. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отнести событие к местному дню отчётаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 01:00:00', new DateTimeZone('UTC'));
echo 'День по API: ', $date->format('Y-m-d'), "\n";
$local = $date->setTimezone(new DateTimeZone('America/New_York'));
$reportDay = $local->format('Y-m-d');
echo 'Событие для офиса: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Имя местного отчёта: sales-', $reportDay, '.csv', "\n";
Ожидаемый результат
День по API: 2024-06-15
Событие для офиса: 14.06.2024 21:00 -04:00
Имя местного отчёта: sales-2024-06-14.csv

Отчёт относится к календарю New York, где это ещё 21:00 четырнадцатого июня. Поэтому сначала меняем зону, затем извлекаем Y-m-d и готовим имя sales-2024-06-14.csv. Файл не создаётся. Извлечение дня 15 из UTC до смены зоны поместило бы запись в другой отчёт.

Сравните: DateTimeZone::__construct, DateTimeImmutable::getTimezone, DateTimeImmutable::getOffset.

Время, часовые пояса и календарь. Практические задачи с датами.

Исправить смысл входной строки, а не только подпись зоныСамостоятельный пример
<?php
$input = '2024-06-15 10:00:00';
$wrong = new DateTime($input, new DateTimeZone('UTC'));
$displayed = $wrong->setTimezone(new DateTimeZone('Europe/Moscow'));
$correct = new DateTime($input, new DateTimeZone('Europe/Moscow'));
echo 'После неверного чтения как UTC: ', $displayed->format('d.m.Y H:i P'), "\n";
echo 'Если источник имел в виду Москву: ', $correct->format('d.m.Y H:i P'), "\n";
echo 'Разница ошибочного и верного момента: ', $displayed->getTimestamp() - $correct->getTimestamp(), ' секунд', "\n";
Ожидаемый результат
После неверного чтения как UTC: 15.06.2024 13:00 +03:00
Если источник имел в виду Москву: 15.06.2024 10:00 +03:00
Разница ошибочного и верного момента: 10800 секунд

Если источник назвал московские 10:00, создание объекта в UTC уже прочитало другой момент. SetTimezone сохранил ошибочный момент и честно показал его как 13:00 Москвы. Исправление — заново разобрать исходную строку с правильной зоной, что создаёт 10:00 +03:00. Этот вывод требует известного договора источника; выбирать его зону наугад нельзя.

DateTimeImmutable::setTimezoneПоказать тот же момент на часах другого региона.

PHP 5.5+ · date

public function setTimezone(DateTimeZone $timezone): DateTimeImmutable

Показать тот же момент на часах другого региона.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
timezoneПодготовленный DateTimeZone с нужным идентификатором или смещением.
ИнвариантМомент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение.
ГраницаЭто не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной.
Путь даты в первом примере
  1. API:15 июня 07:00 UTC
  2. DateTimeZone(Europe/Moscow) →правила отображения
  3. setTimezone возвращает local:10:00 +03:00
  4. Подпись другая, прошедший сдвиг события =0 секунд

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimezone(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Время API известно в UTC:07:00. Посетитель выбрал Москву, поэтому нужен другой вид тех же часов. SetTimezone получает объект DateTimeZone; эта операция меняет представление, сохраняя момент.

Показать посетителю местное время одного событияСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 07:00:00', new DateTimeZone('UTC'));
$stamp = $date->getTimestamp();
echo 'Получено от API: ', $date->format('d.m.Y H:i P'), "\n";
$local = $date->setTimezone(new DateTimeZone('Europe/Moscow'));
echo 'Посетителю в Москве: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Сдвиг самого момента в секундах: ', $local->getTimestamp() - $stamp, "\n";
Ожидаемый результат
Получено от API: 15.06.2024 07:00 +00:00
Посетителю в Москве: 15.06.2024 10:00 +03:00
Сдвиг самого момента в секундах: 0

New DateTimeZone создаёт правила Moscow и передаёт их в setTimezone. Local содержит тот же момент, показанный как 10:00 +03:00. Разность timestamp равна 0: встречу не перенесли. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Отнести событие к местному дню отчётаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 01:00:00', new DateTimeZone('UTC'));
echo 'День по API: ', $date->format('Y-m-d'), "\n";
$local = $date->setTimezone(new DateTimeZone('America/New_York'));
$reportDay = $local->format('Y-m-d');
echo 'Событие для офиса: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Имя местного отчёта: sales-', $reportDay, '.csv', "\n";
Ожидаемый результат
День по API: 2024-06-15
Событие для офиса: 14.06.2024 21:00 -04:00
Имя местного отчёта: sales-2024-06-14.csv

Отчёт относится к календарю New York, где это ещё 21:00 четырнадцатого июня. Поэтому сначала меняем зону, затем извлекаем Y-m-d и готовим имя sales-2024-06-14.csv. Файл не создаётся. Извлечение дня 15 из UTC до смены зоны поместило бы запись в другой отчёт.

Сравните: DateTimeZone::__construct, DateTimeImmutable::getTimezone, DateTimeImmutable::getOffset.

Время, часовые пояса и календарь. Практические задачи с датами.

Исправить смысл входной строки, а не только подпись зоныСамостоятельный пример
<?php
$input = '2024-06-15 10:00:00';
$wrong = new DateTimeImmutable($input, new DateTimeZone('UTC'));
$displayed = $wrong->setTimezone(new DateTimeZone('Europe/Moscow'));
$correct = new DateTimeImmutable($input, new DateTimeZone('Europe/Moscow'));
echo 'После неверного чтения как UTC: ', $displayed->format('d.m.Y H:i P'), "\n";
echo 'Если источник имел в виду Москву: ', $correct->format('d.m.Y H:i P'), "\n";
echo 'Разница ошибочного и верного момента: ', $displayed->getTimestamp() - $correct->getTimestamp(), ' секунд', "\n";
Ожидаемый результат
После неверного чтения как UTC: 15.06.2024 13:00 +03:00
Если источник имел в виду Москву: 15.06.2024 10:00 +03:00
Разница ошибочного и верного момента: 10800 секунд

Если источник назвал московские 10:00, создание объекта в UTC уже прочитало другой момент. SetTimezone сохранил ошибочный момент и честно показал его как 13:00 Москвы. Исправление — заново разобрать исходную строку с правильной зоной, что создаёт 10:00 +03:00. Этот вывод требует известного договора источника; выбирать его зону наугад нельзя.

date_timezone_setПоказать тот же момент на часах другого региона.

PHP 5.2+ · date

function date_timezone_set(DateTime $object, DateTimeZone $timezone): DateTime

Показать тот же момент на часах другого региона.

Часть вызоваДоговор
objectИменно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект.
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
timezoneПодготовленный DateTimeZone с нужным идентификатором или смещением.
ИнвариантМомент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение.
ГраницаЭто не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной.
Путь даты в первом примере
  1. API:15 июня 07:00 UTC
  2. DateTimeZone(Europe/Moscow) →правила отображения
  3. setTimezone возвращает local:10:00 +03:00
  4. Подпись другая, прошедший сдвиг события =0 секунд

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimezone(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Время API известно в UTC:07:00. Посетитель выбрал Москву, поэтому нужен другой вид тех же часов. SetTimezone получает объект DateTimeZone; эта операция меняет представление, сохраняя момент.

Показать посетителю местное время одного событияСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 07:00:00', new DateTimeZone('UTC'));
$stamp = $date->getTimestamp();
echo 'Получено от API: ', $date->format('d.m.Y H:i P'), "\n";
$local = date_timezone_set($date, new DateTimeZone('Europe/Moscow'));
echo 'Посетителю в Москве: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Сдвиг самого момента в секундах: ', $local->getTimestamp() - $stamp, "\n";
Ожидаемый результат
Получено от API: 15.06.2024 07:00 +00:00
Посетителю в Москве: 15.06.2024 10:00 +03:00
Сдвиг самого момента в секундах: 0

New DateTimeZone создаёт правила Moscow и передаёт их в setTimezone. Local содержит тот же момент, показанный как 10:00 +03:00. Разность timestamp равна 0: встречу не перенесли. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отнести событие к местному дню отчётаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 01:00:00', new DateTimeZone('UTC'));
echo 'День по API: ', $date->format('Y-m-d'), "\n";
$local = date_timezone_set($date, new DateTimeZone('America/New_York'));
$reportDay = $local->format('Y-m-d');
echo 'Событие для офиса: ', $local->format('d.m.Y H:i P'), "\n";
echo 'Имя местного отчёта: sales-', $reportDay, '.csv', "\n";
Ожидаемый результат
День по API: 2024-06-15
Событие для офиса: 14.06.2024 21:00 -04:00
Имя местного отчёта: sales-2024-06-14.csv

Отчёт относится к календарю New York, где это ещё 21:00 четырнадцатого июня. Поэтому сначала меняем зону, затем извлекаем Y-m-d и готовим имя sales-2024-06-14.csv. Файл не создаётся. Извлечение дня 15 из UTC до смены зоны поместило бы запись в другой отчёт.

Сравните: DateTimeZone::__construct, DateTimeImmutable::getTimezone, DateTimeImmutable::getOffset.

Время, часовые пояса и календарь. Практические задачи с датами.

Исправить смысл входной строки, а не только подпись зоныСамостоятельный пример
<?php
$input = '2024-06-15 10:00:00';
$wrong = new DateTime($input, new DateTimeZone('UTC'));
$displayed = date_timezone_set($wrong, new DateTimeZone('Europe/Moscow'));
$correct = new DateTime($input, new DateTimeZone('Europe/Moscow'));
echo 'После неверного чтения как UTC: ', $displayed->format('d.m.Y H:i P'), "\n";
echo 'Если источник имел в виду Москву: ', $correct->format('d.m.Y H:i P'), "\n";
echo 'Разница ошибочного и верного момента: ', $displayed->getTimestamp() - $correct->getTimestamp(), ' секунд', "\n";
Ожидаемый результат
После неверного чтения как UTC: 15.06.2024 13:00 +03:00
Если источник имел в виду Москву: 15.06.2024 10:00 +03:00
Разница ошибочного и верного момента: 10800 секунд

Если источник назвал московские 10:00, создание объекта в UTC уже прочитало другой момент. SetTimezone сохранил ошибочный момент и честно показал его как 13:00 Москвы. Исправление — заново разобрать исходную строку с правильной зоной, что создаёт 10:00 +03:00. Этот вывод требует известного договора источника; выбирать его зону наугад нельзя.

DateTime::setMicrosecondЗаменить только микросекундную часть, не меняя целые секунды.

PHP 8.4+ · date

public function setMicrosecond(int $microsecond): static

Заменить только микросекундную часть, не меняя целые секунды.

Часть вызоваДоговор
ВозвратТот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение.
microsecondint от 0 до 999999. Это число микросекунд, не миллисекунд.
ОшибкаDateRangeError при выходе за диапазон; в отличие от setTime, этот метод не нормализует 1000000 в следующую секунду.
ВерсияPHP 8.4+. Возврат static сохраняет класс объекта.
Путь даты в первом примере
  1. date:15 июня 10:30:00.123456 UTC
  2. setMicrosecond получает 250000
  3. next:та же секунда с долей.250000
  4. format →уточнённая строка EVT-41

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setMicrosecond(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Источник уточнил долю секунды отдельно от даты. SetMicrosecond получает целое число 0…999999; это микросекунды, а не весь timestamp и не миллисекунды. В отличие от setTime, выход за этот диапазон не нормализуется.

Добавить отдельную дробную часть событияСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00.123456', new DateTimeZone('UTC'));
$next = $date->setMicrosecond(250000);
echo 'Уточнённое событие EVT-41: ', $next->format('Y-m-d H:i:s.u P'), "\n";
echo 'Число микросекунд внутри секунды: ', $next->getMicrosecond(), "\n";
echo 'Исходная переменная: ', $date->format('H:i:s.u'), "\n";
Ожидаемый результат
Уточнённое событие EVT-41: 2024-06-15 10:30:00.250000 +00:00
Число микросекунд внутри секунды: 250000
Исходная переменная: 10:30:00.250000

PHP8.4+. Аргумент 250000 — четверть миллиона микросекунд, то есть 0.25 секунды. Метод заменил только дробь: дата,10:30 и целая секунда 00 сохранились. GetMicrosecond вернул число 250000, format(u) вывел шестизначную часть строки события. Вызов меняет сам date и возвращает этот же объект. Дальнейшее чтение date показывает изменённое значение. Присваивание другой переменной не создаёт копию; для неё нужен clone.

Отклонить недопустимую долю и сохранить событиеСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00.123456', new DateTimeZone('UTC'));
foreach ([-1, 1000000] as $part) {
    try {
        $next = $date->setMicrosecond($part);
        echo 'Принято: ', $next->format('H:i:s.u'), "\n";
    } catch (DateRangeError $error) {
        echo 'Доля ', $part, ' отклонена; событие остаётся ', $date->format('H:i:s.u'), "\n";
    }
}
Ожидаемый результат
Доля -1 отклонена; событие остаётся 10:30:00.123456
Доля 1000000 отклонена; событие остаётся 10:30:00.123456

PHP8.4+. Foreach проверяет два недопустимых значения по очереди. В каждом случае метод бросает DateRangeError до присваивания next, поэтому строка «Принято» не выполняется. Исходная доля 123456 сохранена. Если нужно именно прибавить секунду, это операция с длительностью; значение 1000000 нельзя передавать как поле микросекунд.

Сравните: DateTimeImmutable::getMicrosecond, DateTimeImmutable::setTime, DateTimeImmutable::createFromTimestamp.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::setMicrosecondЗаменить только микросекундную часть, не меняя целые секунды.

PHP 8.4+ · date

public function setMicrosecond(int $microsecond): static

Заменить только микросекундную часть, не меняя целые секунды.

Часть вызоваДоговор
ВозвратНовый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение.
microsecondint от 0 до 999999. Это число микросекунд, не миллисекунд.
ОшибкаDateRangeError при выходе за диапазон; в отличие от setTime, этот метод не нормализует 1000000 в следующую секунду.
ВерсияPHP 8.4+. Возврат static сохраняет класс объекта.
Путь даты в первом примере
  1. date:15 июня 10:30:00.123456 UTC
  2. setMicrosecond получает 250000
  3. next:та же секунда с долей.250000
  4. format →уточнённая строка EVT-41

У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setMicrosecond(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.

Источник уточнил долю секунды отдельно от даты. SetMicrosecond получает целое число 0…999999; это микросекунды, а не весь timestamp и не миллисекунды. В отличие от setTime, выход за этот диапазон не нормализуется.

Добавить отдельную дробную часть событияСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.123456', new DateTimeZone('UTC'));
$next = $date->setMicrosecond(250000);
echo 'Уточнённое событие EVT-41: ', $next->format('Y-m-d H:i:s.u P'), "\n";
echo 'Число микросекунд внутри секунды: ', $next->getMicrosecond(), "\n";
echo 'Исходная переменная: ', $date->format('H:i:s.u'), "\n";
Ожидаемый результат
Уточнённое событие EVT-41: 2024-06-15 10:30:00.250000 +00:00
Число микросекунд внутри секунды: 250000
Исходная переменная: 10:30:00.123456

PHP8.4+. Аргумент 250000 — четверть миллиона микросекунд, то есть 0.25 секунды. Метод заменил только дробь: дата,10:30 и целая секунда 00 сохранились. GetMicrosecond вернул число 250000, format(u) вывел шестизначную часть строки события. Возвращается новый объект; исходный date сохраняется. Результат нужно присвоить или сразу использовать, иначе выбранная дата потеряется.

Отклонить недопустимую долю и сохранить событиеСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.123456', new DateTimeZone('UTC'));
foreach ([-1, 1000000] as $part) {
    try {
        $next = $date->setMicrosecond($part);
        echo 'Принято: ', $next->format('H:i:s.u'), "\n";
    } catch (DateRangeError $error) {
        echo 'Доля ', $part, ' отклонена; событие остаётся ', $date->format('H:i:s.u'), "\n";
    }
}
Ожидаемый результат
Доля -1 отклонена; событие остаётся 10:30:00.123456
Доля 1000000 отклонена; событие остаётся 10:30:00.123456

PHP8.4+. Foreach проверяет два недопустимых значения по очереди. В каждом случае метод бросает DateRangeError до присваивания next, поэтому строка «Принято» не выполняется. Исходная доля 123456 сохранена. Если нужно именно прибавить секунду, это операция с длительностью; значение 1000000 нельзя передавать как поле микросекунд.

Сравните: DateTimeImmutable::getMicrosecond, DateTimeImmutable::setTime, DateTimeImmutable::createFromTimestamp.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».

PHP 5.2+ · date

public function modify(string $modifier): DateTime

Изменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».

Часть вызоваДоговор
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
modifierСтрока поддерживаемой грамматики: +1 day, next monday, last day of next month, абсолютная дата, время. Английская грамматика PHP не является распознаванием произвольного человеческого текста.
Ошибкиdate_modify: warning и false при неразбираемой строке. Методы с PHP 8.3 бросают DateMalformedStringException; раньше тоже warning/false.
Тип возвратаС 8.4 tentative return type методов больше не содержит false; процедурный date_modify продолжает возвращать DateTime|false.
МесяцыНесуществующий день после сдвига месяца переносится дальше; автоматического ограничения последним днём нет.
Путь даты в первом примере
  1. date:15.06.2024 10:30 UTC
  2. modify получает строку +1 day
  3. next:16.06.2024 10:30 UTC
  4. format → подпись нового окончания брони

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Бронь уже заканчивается 15 июня в 10:30 UTC. Нужно оставить часы и перенести окончание на следующий календарный день. Modify получает строку с правилом; сама строка не является PHP-выражением. Вызываем метод у конкретной даты через ->.

Продлить бронирование на один календарный деньСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
echo 'До продления: ', $date->format('d.m.Y H:i'), "\n";
$next = $date->modify('+1 day');
echo 'Новое окончание брони: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
До продления: 15.06.2024 10:30
Новое окончание брони: 16.06.2024 10:30
Дата в исходной переменной: 16.06.2024 10:30

В строке +1 day знак+ означает движение вперёд,1 — число дней, day — английское слово грамматики даты. Правило применяется к date; возвращённая дата попадает в next и затем в подпись нового срока. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения.

Сравнить перенос месяца и конец следующего месяцаСамостоятельный пример
<?php
$date = new DateTime('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$plain = (clone $date)->modify('+1 month');
$monthEnd = (clone $date)->modify('last day of next month');
echo 'Начало расчёта: ', $date->format('d.m.Y H:i'), "\n";
echo 'Обычное +1 month: ', $plain->format('d.m.Y H:i'), "\n";
echo 'По правилу конца месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Начало расчёта: 31.01.2023 10:30
Обычное +1 month: 03.03.2023 10:30
По правилу конца месяца: 28.02.2023 10:30

В первом расчёте январь заменяется февралём при дне 31; лишние три дня переносятся в март. Второе правило сразу выбирает последний день февраля. Clone перед каждым вызовом создаёт отдельную копию date: первый перенос не влияет на второй. Для подписки результат выбирает договор. «Сохранить номер дня, ограничив концом месяца» — ещё одно правило: для 15 января оно даст 15 февраля, а не 28-е.

Выбрать день встречи и затем времяСамостоятельный пример
<?php
$date = new DateTime('2024-01-01 00:00:00', new DateTimeZone('UTC'));
$day = $date->modify('2024-06-17');
$meeting = $day->modify('14:30');
echo 'Подтверждение: встреча ', $meeting->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Подтверждение: встреча 17.06.2024 14:30 +00:00

Первый modifier заменяет календарный день на 17 июня, второй — часы и минуты на 14:30. Каждый возврат используется следующим вызовом. Это управляемые автором строки; для поля ввода с обещанным форматом нужен createFromFormat и проверка warnings/errors, а не надежда на строгость гибкого парсера.

Сохранить прежнюю бронь при ошибочном правилеСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
try {
    $next = $date->modify('not a date at all');
    echo 'Продление подтверждено до ', $next->format('d.m.Y H:i'), "\n";
} catch (DateMalformedStringException $error) {
    echo "Продление отменено: правило не разобрано\n";
}
echo 'Действующая бронь до ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Продление отменено: правило не разобрано
Действующая бронь до 15.06.2024 10:30

PHP8.3+. Неверная грамматика бросает исключение до присваивания next, поэтому подтверждение внутри try не печатается. Catch выводит отказ; дальнейший код показывает прежний срок. Валидное, но не соответствующее договору +1 month не обязано бросать исключение: его календарный результат проверяют отдельно.

Сравните: DateTimeImmutable::add, DateTimeImmutable::sub, DateTimeImmutable::createFromFormat, strtotime.

Время, часовые пояса и календарь. Практические задачи с датами.

date_modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».

PHP 5.2+ · date

function date_modify(DateTime $object, string $modifier): DateTime|false

Изменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».

Часть вызоваДоговор
objectИзменяемый DateTime. Функция не принимает DateTimeImmutable.
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
modifierСтрока поддерживаемой грамматики: +1 day, next monday, last day of next month, абсолютная дата, время. Английская грамматика PHP не является распознаванием произвольного человеческого текста.
Ошибкиdate_modify: warning и false при неразбираемой строке. Методы с PHP 8.3 бросают DateMalformedStringException; раньше тоже warning/false.
Тип возвратаС 8.4 tentative return type методов больше не содержит false; процедурный date_modify продолжает возвращать DateTime|false.
МесяцыНесуществующий день после сдвига месяца переносится дальше; автоматического ограничения последним днём нет.
Путь даты в первом примере
  1. date:15.06.2024 10:30 UTC
  2. modify получает строку +1 day
  3. next:16.06.2024 10:30 UTC
  4. format → подпись нового окончания брони

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Бронь уже заканчивается 15 июня в 10:30 UTC. Нужно оставить часы и перенести окончание на следующий календарный день. Modify получает строку с правилом; сама строка не является PHP-выражением. В процедурной форме первым аргументом передаётся изменяемый DateTime, вторым — правило.

Продлить бронирование на один календарный деньСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
echo 'До продления: ', $date->format('d.m.Y H:i'), "\n";
$next = date_modify($date, '+1 day');
echo 'Новое окончание брони: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
До продления: 15.06.2024 10:30
Новое окончание брони: 16.06.2024 10:30
Дата в исходной переменной: 16.06.2024 10:30

В строке +1 day знак+ означает движение вперёд,1 — число дней, day — английское слово грамматики даты. Правило применяется к date; возвращённая дата попадает в next и затем в подпись нового срока. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения.

Сравнить перенос месяца и конец следующего месяцаСамостоятельный пример
<?php
$date = new DateTime('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$plain = date_modify((clone $date), '+1 month');
$monthEnd = date_modify((clone $date), 'last day of next month');
echo 'Начало расчёта: ', $date->format('d.m.Y H:i'), "\n";
echo 'Обычное +1 month: ', $plain->format('d.m.Y H:i'), "\n";
echo 'По правилу конца месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Начало расчёта: 31.01.2023 10:30
Обычное +1 month: 03.03.2023 10:30
По правилу конца месяца: 28.02.2023 10:30

В первом расчёте январь заменяется февралём при дне 31; лишние три дня переносятся в март. Второе правило сразу выбирает последний день февраля. Clone перед каждым вызовом создаёт отдельную копию date: первый перенос не влияет на второй. Для подписки результат выбирает договор. «Сохранить номер дня, ограничив концом месяца» — ещё одно правило: для 15 января оно даст 15 февраля, а не 28-е.

Выбрать день встречи и затем времяСамостоятельный пример
<?php
$date = new DateTime('2024-01-01 00:00:00', new DateTimeZone('UTC'));
$day = date_modify($date, '2024-06-17');
$meeting = date_modify($day, '14:30');
echo 'Подтверждение: встреча ', $meeting->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Подтверждение: встреча 17.06.2024 14:30 +00:00

Первый modifier заменяет календарный день на 17 июня, второй — часы и минуты на 14:30. Каждый возврат используется следующим вызовом. Это управляемые автором строки; для поля ввода с обещанным форматом нужен createFromFormat и проверка warnings/errors, а не надежда на строгость гибкого парсера.

Не подтвердить перенос после warning и falseСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
set_error_handler(static function (int $level): bool {
    if ($level !== E_WARNING) { return false; }
    echo "Диагностика: правило переноса не разобрано\n";
    return true;
});
try {
    $result = date_modify($date, 'not a date at all');
} finally {
    restore_error_handler();
}
if ($result === false) {
    echo 'Продление отменено; бронь по-прежнему до ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Диагностика: правило переноса не разобрано
Продление отменено; бронь по-прежнему до 15.06.2024 10:30

Date_modify выдаёт warning и возвращает false. Временная функция-обработчик получает уровень предупреждения в level, выводит понятную диагностическую категорию и возвращает true, сообщая PHP, что этот warning обработан. Finally восстанавливает прежний обработчик даже при сбое. После этого false выбирает отказ в продлении; дата осталась 15 июня. Такой перехват ограничен одним известным вызовом, а не всей программой.

Сравните: DateTimeImmutable::add, DateTimeImmutable::sub, DateTimeImmutable::createFromFormat, strtotime.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::addПрибавить подготовленный календарный интервал или прошедшую длительность.

PHP 5.3+ · date

public function add(DateInterval $interval): DateTime

Прибавить подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. add(interval) →next:17 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Исходная дата — отправка заказа15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Передаём интервал аргументом метода у даты; полученную дату сохраняем в next.

Рассчитать доставку через два дняСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = $date->add($interval);
echo 'Ожидаемая доставка: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Ожидаемая доставка: 17.06.2024 10:30
Дата в исходной переменной: 17.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Add получает этот объект и прибавляет два календарных дня из 15 июня; результат next используется в подписи. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTime('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = $date->add(new DateInterval('P1M'));
$monthEnd = $base->modify('last day of next month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После +P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.01.2023 10:30
После +P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После прибавления к январю возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Для дальнейшего ежемесячного расписания храните правило, а не угадывайте его по полученной дате.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = $date->add($interval);
echo 'Правило: add и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: add и интервал минус два дня
Полученная дата действия: 13.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Прибавить отрицательные два дня — значит получить 13 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Разделить суточный таймер и встречу на следующий деньСамостоятельный пример
<?php
$date = new DateTime('2024-11-03 00:00:00', new DateTimeZone('America/New_York'));
$hours = (clone $date)->add(new DateInterval('PT24H'));
$day = (clone $date)->add(new DateInterval('P1D'));
echo 'Начало: ', $date->format('d.m.Y H:i P'), "\n";
echo 'Таймер через 24 часа: ', $hours->format('d.m.Y H:i P'), "\n";
echo 'Встреча на следующий день: ', $day->format('d.m.Y H:i P'), "\n";
echo 'До таймера секунд: ', $hours->getTimestamp() - $date->getTimestamp(), "\n";
echo 'До встречи секунд: ', $day->getTimestamp() - $date->getTimestamp(), "\n";
Ожидаемый результат
Начало: 03.11.2024 00:00 -04:00
Таймер через 24 часа: 03.11.2024 23:00 -05:00
Встреча на следующий день: 04.11.2024 00:00 -05:00
До таймера секунд: 86400
До встречи секунд: 90000

T отделяет временную часть, H означает часы: PT24H —24 прошедших часа. P1D — следующий календарный день в те же местные часы. В эту ночь New York переводит часы назад, поэтому календарные сутки длятся 25 часов,90000 секунд. Clone даёт две независимые копии: интервалы не применяются друг за другом. Такой расчёт только создаёт срок; запуск задачи по сроку требует отдельного планировщика.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::addПрибавить подготовленный календарный интервал или прошедшую длительность.

PHP 5.5+ · date

public function add(DateInterval $interval): DateTimeImmutable

Прибавить подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
ВозвратНовый объект; исходный не меняется. Сохраните результат.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. add(interval) →next:17 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь каждый результат сохраняется в отдельную переменную: immutable не изменяет исходник.

Исходная дата — отправка заказа15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Передаём интервал аргументом метода у даты; полученную дату сохраняем в next.

Рассчитать доставку через два дняСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = $date->add($interval);
echo 'Ожидаемая доставка: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Ожидаемая доставка: 17.06.2024 10:30
Дата в исходной переменной: 15.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Add получает этот объект и прибавляет два календарных дня из 15 июня; результат next используется в подписи. Исходный date остаётся 15 июня; next — новый объект. Результат Immutable надо сохранить: в PHP8.5 это дополнительно отмечено атрибутом NoDiscard. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTimeImmutable('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = $date->add(new DateInterval('P1M'));
$monthEnd = $base->modify('last day of next month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После +P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.01.2023 10:30
После +P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После прибавления к январю возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Для дальнейшего ежемесячного расписания храните правило, а не угадывайте его по полученной дате.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = $date->add($interval);
echo 'Правило: add и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: add и интервал минус два дня
Полученная дата действия: 13.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Прибавить отрицательные два дня — значит получить 13 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Разделить суточный таймер и встречу на следующий деньСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-11-03 00:00:00', new DateTimeZone('America/New_York'));
$hours = $date->add(new DateInterval('PT24H'));
$day = $date->add(new DateInterval('P1D'));
echo 'Начало: ', $date->format('d.m.Y H:i P'), "\n";
echo 'Таймер через 24 часа: ', $hours->format('d.m.Y H:i P'), "\n";
echo 'Встреча на следующий день: ', $day->format('d.m.Y H:i P'), "\n";
echo 'До таймера секунд: ', $hours->getTimestamp() - $date->getTimestamp(), "\n";
echo 'До встречи секунд: ', $day->getTimestamp() - $date->getTimestamp(), "\n";
Ожидаемый результат
Начало: 03.11.2024 00:00 -04:00
Таймер через 24 часа: 03.11.2024 23:00 -05:00
Встреча на следующий день: 04.11.2024 00:00 -05:00
До таймера секунд: 86400
До встречи секунд: 90000

T отделяет временную часть, H означает часы: PT24H —24 прошедших часа. P1D — следующий календарный день в те же местные часы. В эту ночь New York переводит часы назад, поэтому календарные сутки длятся 25 часов,90000 секунд. Immutable позволяет независимо применить оба правила к date. Такой расчёт только создаёт срок; запуск задачи по сроку требует отдельного планировщика.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

date_addПрибавить подготовленный календарный интервал или прошедшую длительность.

PHP 5.3+ · date

function date_add(DateTime $object, DateInterval $interval): DateTime

Прибавить подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
objectИзменяемый DateTime. Функция не принимает DateTimeImmutable.
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. add(interval) →next:17 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Исходная дата — отправка заказа15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Date_add/date_sub принимают изменяемый DateTime первым аргументом и DateInterval вторым.

Рассчитать доставку через два дняСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = date_add($date, $interval);
echo 'Ожидаемая доставка: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Ожидаемая доставка: 17.06.2024 10:30
Дата в исходной переменной: 17.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Add получает этот объект и прибавляет два календарных дня из 15 июня; результат next используется в подписи. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTime('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = date_add($date, new DateInterval('P1M'));
$monthEnd = $base->modify('last day of next month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После +P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.01.2023 10:30
После +P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После прибавления к январю возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Для дальнейшего ежемесячного расписания храните правило, а не угадывайте его по полученной дате.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = date_add($date, $interval);
echo 'Правило: add и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: add и интервал минус два дня
Полученная дата действия: 13.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Прибавить отрицательные два дня — значит получить 13 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Разделить суточный таймер и встречу на следующий деньСамостоятельный пример
<?php
$date = new DateTime('2024-11-03 00:00:00', new DateTimeZone('America/New_York'));
$hours = date_add((clone $date), new DateInterval('PT24H'));
$day = date_add((clone $date), new DateInterval('P1D'));
echo 'Начало: ', $date->format('d.m.Y H:i P'), "\n";
echo 'Таймер через 24 часа: ', $hours->format('d.m.Y H:i P'), "\n";
echo 'Встреча на следующий день: ', $day->format('d.m.Y H:i P'), "\n";
echo 'До таймера секунд: ', $hours->getTimestamp() - $date->getTimestamp(), "\n";
echo 'До встречи секунд: ', $day->getTimestamp() - $date->getTimestamp(), "\n";
Ожидаемый результат
Начало: 03.11.2024 00:00 -04:00
Таймер через 24 часа: 03.11.2024 23:00 -05:00
Встреча на следующий день: 04.11.2024 00:00 -05:00
До таймера секунд: 86400
До встречи секунд: 90000

T отделяет временную часть, H означает часы: PT24H —24 прошедших часа. P1D — следующий календарный день в те же местные часы. В эту ночь New York переводит часы назад, поэтому календарные сутки длятся 25 часов,90000 секунд. Clone даёт две независимые копии: интервалы не применяются друг за другом. Такой расчёт только создаёт срок; запуск задачи по сроку требует отдельного планировщика.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::subВычесть подготовленный календарный интервал или прошедшую длительность.

PHP 5.3+ · date

public function sub(DateInterval $interval): DateTime

Вычесть подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Неподдерживаемый интервалОтносительное next weekday нельзя обратить вычитанием. Методы с 8.3 бросают DateInvalidOperationException; раньше warning. date_sub сохраняет warning и возвращает объект без такого изменения.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. sub(interval) →next:13 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Исходная дата — встреча15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Передаём интервал аргументом метода у даты; полученную дату сохраняем в next.

Рассчитать напоминание за два дняСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = $date->sub($interval);
echo 'Напоминание отправить: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Напоминание отправить: 13.06.2024 10:30
Дата в исходной переменной: 13.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Sub получает этот объект и вычитает два календарных дня из 15 июня; результат next используется в подписи. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTime('2023-03-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = $date->sub(new DateInterval('P1M'));
$monthEnd = $base->modify('last day of previous month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После -P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.03.2023 10:30
После -P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После вычитания из марта возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Таким образом, даже минус месяц может остаться в марте; обратная операция не обязана восстанавливать дату до предыдущей нормализации.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = $date->sub($interval);
echo 'Правило: sub и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: sub и интервал минус два дня
Полученная дата действия: 17.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Вычесть отрицательные два дня — значит получить 17 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Сформулировать предыдущий будний день вместо обратной грамматикиСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = DateInterval::createFromDateString('next weekday');
try {
    $next = $date->sub($interval);
    echo 'Перенос подтверждён: ', $next->format('d.m.Y H:i'), "\n";
} catch (DateInvalidOperationException $error) {
    echo "Перенос отменён: правило нельзя вычесть\n";
}
echo 'Исходная дата: ', $date->format('d.m.Y H:i'), "\n";
$previousDay = (clone $date)->modify('previous weekday');
$previous = $previousDay->setTime(10, 30);
echo 'Предыдущий будний день: ', $previous->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перенос отменён: правило нельзя вычесть
Исходная дата: 15.06.2024 10:30
Предыдущий будний день: 14.06.2024 10:30

PHP8.3+. Фабрика создала допустимый текстовый интервал next weekday, но sub не может обратить такое правило и бросает DateInvalidOperationException. Строка подтверждения пропущена, дата не изменилась. Previous weekday выбирает пятницу 14 июня вместо субботы 15-го и сбрасывает часы в 00:00. Поэтому setTime(10,30) отдельно устанавливает время действия, а возвращённая дата сохраняется в previous. Clone сохраняет исходную дату и для DateTime; региональные праздники этот парсер не знает.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::subВычесть подготовленный календарный интервал или прошедшую длительность.

PHP 5.5+ · date

public function sub(DateInterval $interval): DateTimeImmutable

Вычесть подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
ВозвратНовый объект; исходный не меняется. Сохраните результат.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Неподдерживаемый интервалОтносительное next weekday нельзя обратить вычитанием. Методы с 8.3 бросают DateInvalidOperationException; раньше warning. date_sub сохраняет warning и возвращает объект без такого изменения.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. sub(interval) →next:13 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь каждый результат сохраняется в отдельную переменную: immutable не изменяет исходник.

Исходная дата — встреча15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Передаём интервал аргументом метода у даты; полученную дату сохраняем в next.

Рассчитать напоминание за два дняСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = $date->sub($interval);
echo 'Напоминание отправить: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Напоминание отправить: 13.06.2024 10:30
Дата в исходной переменной: 15.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Sub получает этот объект и вычитает два календарных дня из 15 июня; результат next используется в подписи. Исходный date остаётся 15 июня; next — новый объект. Результат Immutable надо сохранить: в PHP8.5 это дополнительно отмечено атрибутом NoDiscard. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTimeImmutable('2023-03-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = $date->sub(new DateInterval('P1M'));
$monthEnd = $base->modify('last day of previous month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После -P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.03.2023 10:30
После -P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После вычитания из марта возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Таким образом, даже минус месяц может остаться в марте; обратная операция не обязана восстанавливать дату до предыдущей нормализации.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = $date->sub($interval);
echo 'Правило: sub и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: sub и интервал минус два дня
Полученная дата действия: 17.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Вычесть отрицательные два дня — значит получить 17 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Сформулировать предыдущий будний день вместо обратной грамматикиСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = DateInterval::createFromDateString('next weekday');
try {
    $next = $date->sub($interval);
    echo 'Перенос подтверждён: ', $next->format('d.m.Y H:i'), "\n";
} catch (DateInvalidOperationException $error) {
    echo "Перенос отменён: правило нельзя вычесть\n";
}
echo 'Исходная дата: ', $date->format('d.m.Y H:i'), "\n";
$previousDay = (clone $date)->modify('previous weekday');
$previous = $previousDay->setTime(10, 30);
echo 'Предыдущий будний день: ', $previous->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перенос отменён: правило нельзя вычесть
Исходная дата: 15.06.2024 10:30
Предыдущий будний день: 14.06.2024 10:30

PHP8.3+. Фабрика создала допустимый текстовый интервал next weekday, но sub не может обратить такое правило и бросает DateInvalidOperationException. Строка подтверждения пропущена, дата не изменилась. Previous weekday выбирает пятницу 14 июня вместо субботы 15-го и сбрасывает часы в 00:00. Поэтому setTime(10,30) отдельно устанавливает время действия, а возвращённая дата сохраняется в previous. Clone сохраняет исходную дату и для DateTime; региональные праздники этот парсер не знает.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

date_subВычесть подготовленный календарный интервал или прошедшую длительность.

PHP 5.3+ · date

function date_sub(DateTime $object, DateInterval $interval): DateTime

Вычесть подготовленный календарный интервал или прошедшую длительность.

Часть вызоваДоговор
objectИзменяемый DateTime. Функция не принимает DateTimeImmutable.
ВозвратТот же DateTime после изменения; вторая переменная с этим объектом увидит изменения.
intervalDateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику.
МесяцP1M — календарный месяц, не постоянное число секунд и не ограничение последним днём.
Текстовое правилоcreateFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов.
Неподдерживаемый интервалОтносительное next weekday нельзя обратить вычитанием. Методы с 8.3 бросают DateInvalidOperationException; раньше warning. date_sub сохраняет warning и возвращает объект без такого изменения.
Путь даты в первом примере
  1. date:15 июня 10:30 UTC
  2. new DateInterval(P2D) →interval:два дня
  3. sub(interval) →next:13 июня 10:30
  4. next->format →подпись для действия

Сначала определите правило задачи: «календарный день», «ровно 24 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь меняется сам объект. Чтобы сохранить исходный изменяемый вариант, заранее сделайте clone.

Исходная дата — встреча15 июня в 10:30 UTC. Интервал хранит величину сдвига отдельно от даты: P начинает запись периода,2 — количество, D — дни. Date_add/date_sub принимают изменяемый DateTime первым аргументом и DateInterval вторым.

Рассчитать напоминание за два дняСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$next = date_sub($date, $interval);
echo 'Напоминание отправить: ', $next->format('d.m.Y H:i'), "\n";
echo 'Дата в исходной переменной: ', $date->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Напоминание отправить: 13.06.2024 10:30
Дата в исходной переменной: 13.06.2024 10:30

New DateInterval возвращает объект с правилом двух дней в interval. Sub получает этот объект и вычитает два календарных дня из 15 июня; результат next используется в подписи. Изменился сам объект date. Next обозначает этот же объект, поэтому обе переменные теперь показывают новый день. Для независимого исходника нужна копия clone до изменения. Пример только рассчитывает время, сообщение не отправляет. Выходные и праздники здесь не исключаются.

Проверить месячный перенос до включения в расписаниеСамостоятельный пример
<?php
$date = new DateTime('2023-03-31 10:30:00', new DateTimeZone('UTC'));
$base = DateTimeImmutable::createFromInterface($date);
$plain = date_sub($date, new DateInterval('P1M'));
$monthEnd = $base->modify('last day of previous month');
echo 'Исходный срок: ', $base->format('d.m.Y H:i'), "\n";
echo 'После -P1M: ', $plain->format('d.m.Y H:i'), "\n";
echo 'Последний день выбранного месяца: ', $monthEnd->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Исходный срок: 31.03.2023 10:30
После -P1M: 03.03.2023 10:30
Последний день выбранного месяца: 28.02.2023 10:30

P1M задаёт календарный месяц; M до T означает месяцы. После вычитания из марта возникает день 31 в феврале 2023, которого нет. Нормализация даёт 3 марта, тогда как отдельное правило конца февраля даёт 28-е. Base заранее сохраняет независимый исходник для сравнения. Таким образом, даже минус месяц может остаться в марте; обратная операция не обязана восстанавливать дату до предыдущей нормализации.

Учитывать знак подготовленного интервалаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = new DateInterval('P2D');
$interval->invert = 1;
$next = date_sub($date, $interval);
echo 'Правило: sub и интервал минус два дня', "\n";
echo 'Полученная дата действия: ', $next->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Правило: sub и интервал минус два дня
Полученная дата действия: 17.06.2024 10:30

Присваивание interval->invert =1 меняет направление самого интервала. Вычесть отрицательные два дня — значит получить 17 июня. Возвращённый next снова используется как дата действия. Проверяйте invert, когда интервал пришёл из diff: знак уже может присутствовать, отдельная операция sub перевернёт его ещё раз.

Не считать возвращённый объект успешным вычитаниемСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
$interval = DateInterval::createFromDateString('next weekday');
$failed = false;
set_error_handler(static function (int $level) use (&$failed): bool {
    if ($level !== E_WARNING) { return false; }
    $failed = true;
    return true;
});
try {
    $result = date_sub($date, $interval);
} finally {
    restore_error_handler();
}
if ($failed) {
    echo 'Перенос отменён; полученный объект по-прежнему содержит ', $result->format('d.m.Y H:i'), "\n";
}
$previousDay = (clone $date)->modify('previous weekday');
$previous = $previousDay->setTime(10, 30);
echo 'По отдельному правилу предыдущего буднего дня: ', $previous->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Перенос отменён; полученный объект по-прежнему содержит 15.06.2024 10:30
По отдельному правилу предыдущего буднего дня: 14.06.2024 10:30

Next weekday означает следующий будний день, а не фиксированное количество дней; date_sub не умеет обратить это правило. Оно выдаёт warning и возвращает прежний объект. Use (&$failed) позволяет обработчику пометить внешний failed, return true сообщает об обработке warning, finally восстанавливает прежний обработчик. Решение об отмене основано на диагностике. Previous weekday выбирает пятницу 14 июня и сбрасывает часы в 00:00, поэтому отдельный setTime(10,30) возвращает нужное время напоминания. Его результат сохраняется в previous. Праздники парсер не учитывает.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateTimeImmutable::modify, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::diffПолучить календарный интервал, переводящий исходную дату в целевую.

PHP 5.5+ · date

public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval

Получить календарный интервал, переводящий исходную дату в целевую.

Часть вызоваДоговор
targetObjectЦелевая дата через общий DateTimeInterface.
absoluteПо умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд.
ВозвратDateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются.
DST и обратимостьРазность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима.
Путь даты в первом примере
  1. checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
  2. diff возвращает DateInterval в stay
  3. stay->days =3 →nights
  4. 3 ×3000 =9000 →сумма бронирования

Не путайте format даты с format интервала: у DateInterval используются %a, %d, %h и другие обозначения. %a — полные дни из diff; d — остаточная дневная часть после лет/месяцев. DateInterval::format.

Для известного правильного диапазона «заезд 15 июня, выезд 18 июня» нужно рассчитать три ночи. Diff получает конечную дату и возвращает объект интервала. Запись stay->days читает его поле, а не вызывает метод: скобок после days нет. Для пользовательского ввода сначала проверяют порядок дат; ниже такой выбор показан отдельно.

Получить число ночей из дат заезда и выездаСамостоятельный пример
<?php
$checkIn = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$checkOut = new DateTimeImmutable('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$stay = $checkIn->diff($checkOut);
$nights = $stay->days;
$total = $nights * 3000;
echo 'Заезд: ', $checkIn->format('d.m.Y'), '; выезд: ', $checkOut->format('d.m.Y'), "\n";
echo 'Ночей: ', $nights, '; сумма: ', $total, ' руб.', "\n";
Ожидаемый результат
Заезд: 15.06.2024; выезд: 18.06.2024
Ночей: 3; сумма: 9000 руб.

Diff возвращает DateInterval в stay; его свойство days для результата diff содержит общее число полных дней. Между полуночами 15 и 18 июня их 3; nights умножается на цену 3000 и даёт 9000. Это правило бронирования календарных ночей, выезд 18-го не добавляет четвёртую ночь. UTC здесь носитель выбранных календарных дней; реальные местные часы заезда/выезда задают отдельно. Ни одна исходная дата не изменилась.

Сообщить просрочку, сохранив направление интервалаСамостоятельный пример
<?php
$now = new DateTimeImmutable('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$deadline = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$signed = $now->diff($deadline);
$positive = $now->diff($deadline, true);
if ($signed->invert === 1) {
    echo 'Срок пропущен на ', $signed->days, ' дня', "\n";
}
echo 'Интервал от проверки к сроку: ', $signed->format('%R%a'), "\n";
echo 'Тот же интервал с absolute=true: ', $positive->format('%R%a'), "\n";
Ожидаемый результат
Срок пропущен на 3 дня
Интервал от проверки к сроку: -3
Тот же интервал с absolute=true: +3

От 18 июня к 15-му нужно двигаться назад: invert=1, days=3. Эта пара позволяет вывести понятную просрочку. У DateInterval шаблон %R печатает обязательный знак, %a — общее число дней. Absolute=true сбрасывает направление, поэтому получится+3, но срок от этого не стал будущим. Флаг не превращает календарный интервал в абсолютные прошедшие секунды.

Отличить ежедневное расписание от прошедшего времениСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-03-09 12:00:00', new DateTimeZone('America/New_York'));
$end = new DateTimeImmutable('2024-03-10 12:00:00', new DateTimeZone('America/New_York'));
$calendar = $start->diff($end);
$utcStart = DateTimeImmutable::createFromInterface($start)->setTimezone(new DateTimeZone('UTC'));
$utcEnd = $end->setTimezone(new DateTimeZone('UTC'));
$elapsed = $utcStart->diff($utcEnd);
echo 'По местному расписанию: ', $calendar->format('%a дн. %h ч.'), "\n";
echo 'Фактически прошло: ', $elapsed->format('%a дн. %h ч.'), "\n";
echo 'Для расчёта секунд: ', $end->getTimestamp() - $start->getTimestamp(), "\n";
Ожидаемый результат
По местному расписанию: 1 дн. 0 ч.
Фактически прошло: 0 дн. 23 ч.
Для расчёта секунд: 82800

Две встречи идут в полдень соседних местных дней. Весенний перевод часов сократил прошедшее время до 23 часов. Первый diff описывает местный календарный путь; после перевода обеих дат в UTC второй diff показывает прошедший интервал без перехода зоны. Разность timestamp даёт 82800 секунд. Для замера скорости программы используют монотонные часы hrtime: календарные часы системы могут корректироваться.

Не потерять месяц в расчёте оплаты по днямСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-01-01 00:00:00', new DateTimeZone('UTC'));
$end = new DateTimeImmutable('2024-02-03 00:00:00', new DateTimeZone('UTC'));
if ($end <= $start) {
    echo "Период аренды должен заканчиваться позже начала\n";
} else {
    $period = $start->diff($end);
    $total = $period->days * 100;
    echo 'Календарное описание: ', $period->format('%m мес. %d дн.'), "\n";
    echo 'Оплачиваемых дней: ', $period->days, "\n";
    echo 'При 100 руб. за день: ', $total, ' руб.', "\n";
}
Ожидаемый результат
Календарное описание: 1 мес. 2 дн.
Оплачиваемых дней: 33
При 100 руб. за день: 3300 руб.

Оператор <= сравнивает сами даты и не допускает обратный или нулевой период. В интервале m=1 и d=2 — компоненты после выделения полного месяца; days=33 — весь промежуток. Поэтому счёт равен 3300, а не 200 и не 3200. Месяц нельзя автоматически заменить 30 днями. %d у DateInterval означает остаток дней, %a — полный счёт из diff; это отдельный язык формата относительно DateTime::format.

Сравните: DateInterval::format, DateTimeImmutable::add, DateTimeImmutable::sub, DateTimeImmutable::getTimestamp.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::diffПолучить календарный интервал, переводящий исходную дату в целевую.

PHP 5.3+ · date

public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval

Получить календарный интервал, переводящий исходную дату в целевую.

Часть вызоваДоговор
targetObjectЦелевая дата через общий DateTimeInterface.
absoluteПо умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд.
ВозвратDateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются.
DST и обратимостьРазность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима.
Путь даты в первом примере
  1. checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
  2. diff возвращает DateInterval в stay
  3. stay->days =3 →nights
  4. 3 ×3000 =9000 →сумма бронирования

Не путайте format даты с format интервала: у DateInterval используются %a, %d, %h и другие обозначения. %a — полные дни из diff; d — остаточная дневная часть после лет/месяцев. DateInterval::format.

Для известного правильного диапазона «заезд 15 июня, выезд 18 июня» нужно рассчитать три ночи. Diff получает конечную дату и возвращает объект интервала. Запись stay->days читает его поле, а не вызывает метод: скобок после days нет. Для пользовательского ввода сначала проверяют порядок дат; ниже такой выбор показан отдельно.

Получить число ночей из дат заезда и выездаСамостоятельный пример
<?php
$checkIn = new DateTime('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$checkOut = new DateTimeImmutable('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$stay = $checkIn->diff($checkOut);
$nights = $stay->days;
$total = $nights * 3000;
echo 'Заезд: ', $checkIn->format('d.m.Y'), '; выезд: ', $checkOut->format('d.m.Y'), "\n";
echo 'Ночей: ', $nights, '; сумма: ', $total, ' руб.', "\n";
Ожидаемый результат
Заезд: 15.06.2024; выезд: 18.06.2024
Ночей: 3; сумма: 9000 руб.

Diff возвращает DateInterval в stay; его свойство days для результата diff содержит общее число полных дней. Между полуночами 15 и 18 июня их 3; nights умножается на цену 3000 и даёт 9000. Это правило бронирования календарных ночей, выезд 18-го не добавляет четвёртую ночь. UTC здесь носитель выбранных календарных дней; реальные местные часы заезда/выезда задают отдельно. Ни одна исходная дата не изменилась.

Сообщить просрочку, сохранив направление интервалаСамостоятельный пример
<?php
$now = new DateTime('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$deadline = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$signed = $now->diff($deadline);
$positive = $now->diff($deadline, true);
if ($signed->invert === 1) {
    echo 'Срок пропущен на ', $signed->days, ' дня', "\n";
}
echo 'Интервал от проверки к сроку: ', $signed->format('%R%a'), "\n";
echo 'Тот же интервал с absolute=true: ', $positive->format('%R%a'), "\n";
Ожидаемый результат
Срок пропущен на 3 дня
Интервал от проверки к сроку: -3
Тот же интервал с absolute=true: +3

От 18 июня к 15-му нужно двигаться назад: invert=1, days=3. Эта пара позволяет вывести понятную просрочку. У DateInterval шаблон %R печатает обязательный знак, %a — общее число дней. Absolute=true сбрасывает направление, поэтому получится+3, но срок от этого не стал будущим. Флаг не превращает календарный интервал в абсолютные прошедшие секунды.

Отличить ежедневное расписание от прошедшего времениСамостоятельный пример
<?php
$start = new DateTime('2024-03-09 12:00:00', new DateTimeZone('America/New_York'));
$end = new DateTimeImmutable('2024-03-10 12:00:00', new DateTimeZone('America/New_York'));
$calendar = $start->diff($end);
$utcStart = DateTimeImmutable::createFromInterface($start)->setTimezone(new DateTimeZone('UTC'));
$utcEnd = $end->setTimezone(new DateTimeZone('UTC'));
$elapsed = $utcStart->diff($utcEnd);
echo 'По местному расписанию: ', $calendar->format('%a дн. %h ч.'), "\n";
echo 'Фактически прошло: ', $elapsed->format('%a дн. %h ч.'), "\n";
echo 'Для расчёта секунд: ', $end->getTimestamp() - $start->getTimestamp(), "\n";
Ожидаемый результат
По местному расписанию: 1 дн. 0 ч.
Фактически прошло: 0 дн. 23 ч.
Для расчёта секунд: 82800

Две встречи идут в полдень соседних местных дней. Весенний перевод часов сократил прошедшее время до 23 часов. Первый diff описывает местный календарный путь; после перевода обеих дат в UTC второй diff показывает прошедший интервал без перехода зоны. Разность timestamp даёт 82800 секунд. Для замера скорости программы используют монотонные часы hrtime: календарные часы системы могут корректироваться.

Не потерять месяц в расчёте оплаты по днямСамостоятельный пример
<?php
$start = new DateTime('2024-01-01 00:00:00', new DateTimeZone('UTC'));
$end = new DateTimeImmutable('2024-02-03 00:00:00', new DateTimeZone('UTC'));
if ($end <= $start) {
    echo "Период аренды должен заканчиваться позже начала\n";
} else {
    $period = $start->diff($end);
    $total = $period->days * 100;
    echo 'Календарное описание: ', $period->format('%m мес. %d дн.'), "\n";
    echo 'Оплачиваемых дней: ', $period->days, "\n";
    echo 'При 100 руб. за день: ', $total, ' руб.', "\n";
}
Ожидаемый результат
Календарное описание: 1 мес. 2 дн.
Оплачиваемых дней: 33
При 100 руб. за день: 3300 руб.

Оператор <= сравнивает сами даты и не допускает обратный или нулевой период. В интервале m=1 и d=2 — компоненты после выделения полного месяца; days=33 — весь промежуток. Поэтому счёт равен 3300, а не 200 и не 3200. Месяц нельзя автоматически заменить 30 днями. %d у DateInterval означает остаток дней, %a — полный счёт из diff; это отдельный язык формата относительно DateTime::format.

Сравните: DateInterval::format, DateTimeImmutable::add, DateTimeImmutable::sub, DateTimeImmutable::getTimestamp.

Время, часовые пояса и календарь. Практические задачи с датами.

date_diffПолучить календарный интервал, переводящий исходную дату в целевую.

PHP 5.3+ · date

function date_diff(DateTimeInterface $baseObject, DateTimeInterface $targetObject, bool $absolute = false): DateInterval

Получить календарный интервал, переводящий исходную дату в целевую.

Часть вызоваДоговор
baseObjectИсходный DateTime или DateTimeImmutable.
targetObjectЦелевая дата через общий DateTimeInterface.
absoluteПо умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд.
ВозвратDateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются.
DST и обратимостьРазность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима.
Путь даты в первом примере
  1. checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
  2. diff возвращает DateInterval в stay
  3. stay->days =3 →nights
  4. 3 ×3000 =9000 →сумма бронирования

Не путайте format даты с format интервала: у DateInterval используются %a, %d, %h и другие обозначения. %a — полные дни из diff; d — остаточная дневная часть после лет/месяцев. DateInterval::format.

Для известного правильного диапазона «заезд 15 июня, выезд 18 июня» нужно рассчитать три ночи. Diff получает конечную дату и возвращает объект интервала. Запись stay->days читает его поле, а не вызывает метод: скобок после days нет. Для пользовательского ввода сначала проверяют порядок дат; ниже такой выбор показан отдельно.

Получить число ночей из дат заезда и выездаСамостоятельный пример
<?php
$checkIn = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$checkOut = new DateTimeImmutable('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$stay = date_diff($checkIn, $checkOut);
$nights = $stay->days;
$total = $nights * 3000;
echo 'Заезд: ', $checkIn->format('d.m.Y'), '; выезд: ', $checkOut->format('d.m.Y'), "\n";
echo 'Ночей: ', $nights, '; сумма: ', $total, ' руб.', "\n";
Ожидаемый результат
Заезд: 15.06.2024; выезд: 18.06.2024
Ночей: 3; сумма: 9000 руб.

Diff возвращает DateInterval в stay; его свойство days для результата diff содержит общее число полных дней. Между полуночами 15 и 18 июня их 3; nights умножается на цену 3000 и даёт 9000. Это правило бронирования календарных ночей, выезд 18-го не добавляет четвёртую ночь. UTC здесь носитель выбранных календарных дней; реальные местные часы заезда/выезда задают отдельно. Ни одна исходная дата не изменилась.

Сообщить просрочку, сохранив направление интервалаСамостоятельный пример
<?php
$now = new DateTimeImmutable('2024-06-18 00:00:00', new DateTimeZone('UTC'));
$deadline = new DateTimeImmutable('2024-06-15 00:00:00', new DateTimeZone('UTC'));
$signed = date_diff($now, $deadline);
$positive = date_diff($now, $deadline, true);
if ($signed->invert === 1) {
    echo 'Срок пропущен на ', $signed->days, ' дня', "\n";
}
echo 'Интервал от проверки к сроку: ', $signed->format('%R%a'), "\n";
echo 'Тот же интервал с absolute=true: ', $positive->format('%R%a'), "\n";
Ожидаемый результат
Срок пропущен на 3 дня
Интервал от проверки к сроку: -3
Тот же интервал с absolute=true: +3

От 18 июня к 15-му нужно двигаться назад: invert=1, days=3. Эта пара позволяет вывести понятную просрочку. У DateInterval шаблон %R печатает обязательный знак, %a — общее число дней. Absolute=true сбрасывает направление, поэтому получится+3, но срок от этого не стал будущим. Флаг не превращает календарный интервал в абсолютные прошедшие секунды.

Отличить ежедневное расписание от прошедшего времениСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-03-09 12:00:00', new DateTimeZone('America/New_York'));
$end = new DateTimeImmutable('2024-03-10 12:00:00', new DateTimeZone('America/New_York'));
$calendar = date_diff($start, $end);
$utcStart = DateTimeImmutable::createFromInterface($start)->setTimezone(new DateTimeZone('UTC'));
$utcEnd = $end->setTimezone(new DateTimeZone('UTC'));
$elapsed = date_diff($utcStart, $utcEnd);
echo 'По местному расписанию: ', $calendar->format('%a дн. %h ч.'), "\n";
echo 'Фактически прошло: ', $elapsed->format('%a дн. %h ч.'), "\n";
echo 'Для расчёта секунд: ', $end->getTimestamp() - $start->getTimestamp(), "\n";
Ожидаемый результат
По местному расписанию: 1 дн. 0 ч.
Фактически прошло: 0 дн. 23 ч.
Для расчёта секунд: 82800

Две встречи идут в полдень соседних местных дней. Весенний перевод часов сократил прошедшее время до 23 часов. Первый diff описывает местный календарный путь; после перевода обеих дат в UTC второй diff показывает прошедший интервал без перехода зоны. Разность timestamp даёт 82800 секунд. Для замера скорости программы используют монотонные часы hrtime: календарные часы системы могут корректироваться.

Не потерять месяц в расчёте оплаты по днямСамостоятельный пример
<?php
$start = new DateTimeImmutable('2024-01-01 00:00:00', new DateTimeZone('UTC'));
$end = new DateTimeImmutable('2024-02-03 00:00:00', new DateTimeZone('UTC'));
if ($end <= $start) {
    echo "Период аренды должен заканчиваться позже начала\n";
} else {
    $period = date_diff($start, $end);
    $total = $period->days * 100;
    echo 'Календарное описание: ', $period->format('%m мес. %d дн.'), "\n";
    echo 'Оплачиваемых дней: ', $period->days, "\n";
    echo 'При 100 руб. за день: ', $total, ' руб.', "\n";
}
Ожидаемый результат
Календарное описание: 1 мес. 2 дн.
Оплачиваемых дней: 33
При 100 руб. за день: 3300 руб.

Оператор <= сравнивает сами даты и не допускает обратный или нулевой период. В интервале m=1 и d=2 — компоненты после выделения полного месяца; days=33 — весь промежуток. Поэтому счёт равен 3300, а не 200 и не 3200. Месяц нельзя автоматически заменить 30 днями. %d у DateInterval означает остаток дней, %a — полный счёт из diff; это отдельный язык формата относительно DateTime::format.

Сравните: DateInterval::format, DateTimeImmutable::add, DateTimeImmutable::sub, DateTimeImmutable::getTimestamp.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::__serializeПодготовить внутренний массив даты для механизма serialize.

PHP 8.2+ · date

public function __serialize(): array

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

Часть вызоваДоговор
ПараметрыНет. Обычно вызов делает PHP при serialize($date).
Возвратarray состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление.
ОшибкаНеинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения.
РасширениеЕсли подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override.
Путь даты в первом примере
  1. appointment: 15 июня 10:30:00.125456 Europe/Moscow
  2. __serialize() → массив state
  3. date / timezone / timezone_type читаются по ключам
  4. Исходная дата всё ещё даёт подпись записи

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Приложение может временно сохранить собственную запись на приём и затем продолжить работу с той же датой. Обычно serialize получает объект и сам вызывает __serialize. Сначала посмотрим промежуточный результат: __serialize без аргументов отдаёт массив полей; готовую строку из него создаёт уже serialize. Двойное подчёркивание помечает служебный метод.

Посмотреть, какие данные сохраняются у записи на приёмСамостоятельный пример
<?php
$appointment = new DateTime('2024-06-15 10:30:00.125456 Europe/Moscow');
$state = $appointment->__serialize();
echo 'Локальные часы в снимке: ', $state['date'], "\n";
echo 'Идентификатор зоны: ', $state['timezone'], "\n";
echo 'Способ задания зоны: ', $state['timezone_type'], " (регион)\n";
echo 'Запись клиента: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Локальные часы в снимке: 2024-06-15 10:30:00.125456
Идентификатор зоны: Europe/Moscow
Способ задания зоны: 3 (регион)
Запись клиента: 15.06.2024 10:30 +03:00

Прямой вызов возвращает массив в state; квадратные скобки читают отдельные поля. В нём сохранены микросекунды и регион, а не только подпись 15.06.2024. Значение timezone_type=3 обозначает регион; 1 означает фиксированное смещение, 2 — сокращение. Appointment остаётся пригодным для format. Это внутренний снимок, а не наша стабильная схема API.

Восстановить свою запись и рассчитать напоминаниеСамостоятельный пример
<?php
$appointment = new DateTime('2024-06-15 10:30:00.125456 Europe/Moscow');
$snapshot = serialize($appointment);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTime::class]]);
if (!$restored instanceof DateTime) { throw new RuntimeException('Wrong own snapshot type'); }
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Восстановленная запись: ', $restored->format('d.m.Y H:i:s.u P'), "\n";
echo 'Напоминание: ', $reminder->format('d.m.Y H:i P'), "\n";
echo 'Исходная запись: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Восстановленная запись: 15.06.2024 10:30:00.125456 +03:00
Напоминание: 15.06.2024 10:00 +03:00
Исходная запись: 15.06.2024 10:30 +03:00

Serialize вернул строку snapshot. Unserialize прочитал эту только что созданную строку, восстановил отдельный объект и вернул его в restored. После проверки типа clone даёт независимую основу расчёта; sub(PT30M) возвращает время напоминания. Скобки вокруг clone разрешают сразу вызвать метод у копии. Для Immutable копия здесь необязательна, но позволяет показать одинаковый безопасный расчёт для двух классов. Allowed_classes ограничивает классы собственного снимка и не делает внешнюю сериализацию безопасной.

Сравните: DateTime::__unserialize, serialize, unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.

PHP 8.2+ · date

public function __unserialize(array $data): void

Восстановить внутреннее состояние даты из согласованного массива сериализации.

Часть вызоваДоговор
dataarray штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка.
Возвратvoid. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода.
ОшибкиПовреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление.
ImmutableПрямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде.
Путь даты в первом примере
  1. source → state с датой 15 июня
  2. target пока содержит 1 января 2000 года
  3. target->__unserialize(state) меняет target
  4. target->format показывает 15 июня; result равен null

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.

Понять изменение получателя и отсутствие даты в returnСамостоятельный пример
<?php
$source = new DateTime('2024-06-15 10:30:00 UTC');
$state = $source->__serialize();
$target = new DateTime('2000-01-01T00:00:00Z');
echo 'До восстановления: ', $target->format('d.m.Y H:i'), "\n";
$result = $target->__unserialize($state);
echo 'После восстановления: ', $target->format('d.m.Y H:i'), "\n";
if ($result === null) { echo "Дата находится в target; возвращённого объекта нет\n"; }
Ожидаемый результат
До восстановления: 01.01.2000 00:00
После восстановления: 15.06.2024 10:30
Дата находится в target; возвращённого объекта нет

Аргумент state попадает в параметр data служебного метода. Метод переинициализирует target: после вызова он содержит июнь 2024 года. Return объявлен void, поэтому присваивание результата даёт null и не создаёт новую дату. Этот диагностический прямой вызов действует даже у Immutable; в обычном коде его не используют как редактирование. Стандартный unserialize создаёт получателя и вызывает hook автоматически.

Отказаться от повреждённого снимка и восстановить известную записьСамостоятельный пример
<?php
$target = new DateTime('2000-01-01T00:00:00Z');
try {
    $target->__unserialize([]);
    echo 'Снимок принят: ', $target->format('d.m.Y H:i'), "\n";
} catch (Error $error) {
    echo "Снимок повреждён; получатель больше не используется\n";
    $target = new DateTime('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    echo 'Восстановлено из проверенной записи: ', $target->format('d.m.Y H:i'), " UTC\n";
}
Ожидаемый результат
Снимок повреждён; получатель больше не используется
Восстановлено из проверенной записи: 15.06.2024 10:30 UTC

Пустой массив не содержит штатной схемы. Error переводит выполнение в catch, строка «Снимок принят» не достигается. Новый конструктор использует отдельно известные данные, а не частично восстановленный target. Здесь фиксированная дата играет роль проверенной исходной записи; если такой записи нет, нужно сообщить об отказе, а не подставлять сегодняшнее число.

Следующий пример меняет именно схему собственного снимка. BookingDate наследует дату; public разрешает PHP вызвать два служебных метода. В __serialize ключи local и zone получают строки из текущего объекта через $this. Return отдаёт этот массив механизму serialize. При восстановлении __unserialize получает тот же массив в локальный data, проверяет наличие и строковые типы полей, затем parent::__construct инициализирует дату. Void означает, что объект уже заполняется на месте. Внешний вызов unserialize возвращает готовый BookingDate в restored, и format создаёт строку для пользователя. Для обычного сохранения даты наследование не нужно. Наследование и parent.

Согласовать собственный формат сохранения и восстановленияСамостоятельный пример
<?php
declare(strict_types=1);

class BookingDate extends DateTime
{
    public function __serialize(): array
    {
        return ['local' => $this->format('Y-m-d H:i:s.u'),
            'zone' => $this->getTimezone()->getName()];
    }

    public function __unserialize(array $data): void
    {
        if (!isset($data['local'], $data['zone'])
            || !is_string($data['local']) || !is_string($data['zone'])) {
            throw new UnexpectedValueException('Broken own date snapshot');
        }
        parent::__construct($data['local'], new DateTimeZone($data['zone']));
    }
}
$own = serialize(new BookingDate('2024-06-15 10:30:00.125456 Europe/Moscow'));
$restored = unserialize($own, ['allowed_classes' => [BookingDate::class]]);
if (!$restored instanceof BookingDate) { throw new RuntimeException('Wrong type'); }
echo $restored->format('Y-m-d H:i:s.u e'), "\n";
Ожидаемый результат
2024-06-15 10:30:00.125456 Europe/Moscow

Это пример пары hooks для своего снимка, а не универсальная валидация формы или долговременный формат. local без смещения и отдельная zone сохраняют регион; неоднозначные местные часы при DST потребовали бы дополнительного момента/смещения. Для обычного DateTime собственные hooks не нужны.

Сравните: DateTime::__serialize, unserialize, DateTime::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.

PHP 5.3+ · date

public function __wakeup(): void

Распознать устаревший обработчик восстановления даты в старом коде.

Часть вызоваДоговор
Параметры / возвратНет параметров, void; исторически инициализировал дату из восстановленных свойств.
СтатусDeprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату.
ГраницаПрямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error.
Как PHP восстанавливает дату
  1. Старый код содержит __wakeup
  2. PHP 8.5 помечает метод устаревшим
  3. Современный объект имеет __serialize/__unserialize
  4. Приложение пользуется согласованным маршрутом сохранения

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Увидеть устаревание без запуска старого hookСамостоятельный пример
<?php
declare(strict_types=1);

$method = new ReflectionMethod(DateTime::class, '__wakeup');
$attributes = $method->getAttributes(Deprecated::class);
echo count($attributes), "\n";
echo $attributes[0]->newInstance()->since, "\n";
Ожидаемый результат
1
8.5

Reflection здесь читает метаданные PHP 8.5, не вызывает hook и не подавляет предупреждение. На раннем PHP этот атрибут отсутствует; проверяйте версию при анализе старого проекта.

Продолжить запись клиента без ручного вызова устаревшего hookСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00 Europe/Moscow');
$snapshot = serialize($date);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTime::class]]);
if (!$restored instanceof DateTime) { throw new RuntimeException('Wrong own snapshot'); }
echo 'Запись после восстановления: ', $restored->format('d.m.Y H:i P'), "\n";
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Напомнить в ', $reminder->format('H:i'), "\n";
Ожидаемый результат
Запись после восстановления: 15.06.2024 10:30 +03:00
Напомнить в 10:00

После обычного unserialize готовая дата уже вернулась в restored. Дополнительный __wakeup не нужен: PHP использовал современные __serialize/__unserialize. Расчёт напоминания показывает, что восстановленное значение можно сразу применять. Первый опыт карточки оставлен для анализа версии: Reflection читает атрибут устаревания PHP 8.5, не вызывает старый метод.

Сравните: DateTime::__serialize, DateTime::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::__serializeПодготовить внутренний массив даты для механизма serialize.

PHP 8.2+ · date

public function __serialize(): array

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

Часть вызоваДоговор
ПараметрыНет. Обычно вызов делает PHP при serialize($date).
Возвратarray состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление.
ОшибкаНеинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения.
РасширениеЕсли подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override.
Путь даты в первом примере
  1. appointment: 15 июня 10:30:00.125456 Europe/Moscow
  2. __serialize() → массив state
  3. date / timezone / timezone_type читаются по ключам
  4. Исходная дата всё ещё даёт подпись записи

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Приложение может временно сохранить собственную запись на приём и затем продолжить работу с той же датой. Обычно serialize получает объект и сам вызывает __serialize. Сначала посмотрим промежуточный результат: __serialize без аргументов отдаёт массив полей; готовую строку из него создаёт уже serialize. Двойное подчёркивание помечает служебный метод.

Посмотреть, какие данные сохраняются у записи на приёмСамостоятельный пример
<?php
$appointment = new DateTimeImmutable('2024-06-15 10:30:00.125456 Europe/Moscow');
$state = $appointment->__serialize();
echo 'Локальные часы в снимке: ', $state['date'], "\n";
echo 'Идентификатор зоны: ', $state['timezone'], "\n";
echo 'Способ задания зоны: ', $state['timezone_type'], " (регион)\n";
echo 'Запись клиента: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Локальные часы в снимке: 2024-06-15 10:30:00.125456
Идентификатор зоны: Europe/Moscow
Способ задания зоны: 3 (регион)
Запись клиента: 15.06.2024 10:30 +03:00

Прямой вызов возвращает массив в state; квадратные скобки читают отдельные поля. В нём сохранены микросекунды и регион, а не только подпись 15.06.2024. Значение timezone_type=3 обозначает регион; 1 означает фиксированное смещение, 2 — сокращение. Appointment остаётся пригодным для format. Это внутренний снимок, а не наша стабильная схема API.

Восстановить свою запись и рассчитать напоминаниеСамостоятельный пример
<?php
$appointment = new DateTimeImmutable('2024-06-15 10:30:00.125456 Europe/Moscow');
$snapshot = serialize($appointment);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTimeImmutable::class]]);
if (!$restored instanceof DateTimeImmutable) { throw new RuntimeException('Wrong own snapshot type'); }
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Восстановленная запись: ', $restored->format('d.m.Y H:i:s.u P'), "\n";
echo 'Напоминание: ', $reminder->format('d.m.Y H:i P'), "\n";
echo 'Исходная запись: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Восстановленная запись: 15.06.2024 10:30:00.125456 +03:00
Напоминание: 15.06.2024 10:00 +03:00
Исходная запись: 15.06.2024 10:30 +03:00

Serialize вернул строку snapshot. Unserialize прочитал эту только что созданную строку, восстановил отдельный объект и вернул его в restored. После проверки типа clone даёт независимую основу расчёта; sub(PT30M) возвращает время напоминания. Скобки вокруг clone разрешают сразу вызвать метод у копии. Для Immutable копия здесь необязательна, но позволяет показать одинаковый безопасный расчёт для двух классов. Allowed_classes ограничивает классы собственного снимка и не делает внешнюю сериализацию безопасной.

Сравните: DateTimeImmutable::__unserialize, serialize, unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.

PHP 8.2+ · date

public function __unserialize(array $data): void

Восстановить внутреннее состояние даты из согласованного массива сериализации.

Часть вызоваДоговор
dataarray штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка.
Возвратvoid. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода.
ОшибкиПовреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление.
ImmutableПрямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде.
Путь даты в первом примере
  1. source → state с датой 15 июня
  2. target пока содержит 1 января 2000 года
  3. target->__unserialize(state) меняет target
  4. target->format показывает 15 июня; result равен null

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.

Понять изменение получателя и отсутствие даты в returnСамостоятельный пример
<?php
$source = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
$state = $source->__serialize();
$target = new DateTimeImmutable('2000-01-01T00:00:00Z');
echo 'До восстановления: ', $target->format('d.m.Y H:i'), "\n";
$result = $target->__unserialize($state);
echo 'После восстановления: ', $target->format('d.m.Y H:i'), "\n";
if ($result === null) { echo "Дата находится в target; возвращённого объекта нет\n"; }
Ожидаемый результат
До восстановления: 01.01.2000 00:00
После восстановления: 15.06.2024 10:30
Дата находится в target; возвращённого объекта нет

Аргумент state попадает в параметр data служебного метода. Метод переинициализирует target: после вызова он содержит июнь 2024 года. Return объявлен void, поэтому присваивание результата даёт null и не создаёт новую дату. Этот диагностический прямой вызов действует даже у Immutable; в обычном коде его не используют как редактирование. Стандартный unserialize создаёт получателя и вызывает hook автоматически.

Отказаться от повреждённого снимка и восстановить известную записьСамостоятельный пример
<?php
$target = new DateTimeImmutable('2000-01-01T00:00:00Z');
try {
    $target->__unserialize([]);
    echo 'Снимок принят: ', $target->format('d.m.Y H:i'), "\n";
} catch (Error $error) {
    echo "Снимок повреждён; получатель больше не используется\n";
    $target = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    echo 'Восстановлено из проверенной записи: ', $target->format('d.m.Y H:i'), " UTC\n";
}
Ожидаемый результат
Снимок повреждён; получатель больше не используется
Восстановлено из проверенной записи: 15.06.2024 10:30 UTC

Пустой массив не содержит штатной схемы. Error переводит выполнение в catch, строка «Снимок принят» не достигается. Новый конструктор использует отдельно известные данные, а не частично восстановленный target. Здесь фиксированная дата играет роль проверенной исходной записи; если такой записи нет, нужно сообщить об отказе, а не подставлять сегодняшнее число.

Следующий пример меняет именно схему собственного снимка. BookingDate наследует дату; public разрешает PHP вызвать два служебных метода. В __serialize ключи local и zone получают строки из текущего объекта через $this. Return отдаёт этот массив механизму serialize. При восстановлении __unserialize получает тот же массив в локальный data, проверяет наличие и строковые типы полей, затем parent::__construct инициализирует дату. Void означает, что объект уже заполняется на месте. Внешний вызов unserialize возвращает готовый BookingDate в restored, и format создаёт строку для пользователя. Для обычного сохранения даты наследование не нужно. Наследование и parent.

Согласовать собственный формат сохранения и восстановленияСамостоятельный пример
<?php
declare(strict_types=1);

class BookingDate extends DateTimeImmutable
{
    public function __serialize(): array
    {
        return ['local' => $this->format('Y-m-d H:i:s.u'),
            'zone' => $this->getTimezone()->getName()];
    }

    public function __unserialize(array $data): void
    {
        if (!isset($data['local'], $data['zone'])
            || !is_string($data['local']) || !is_string($data['zone'])) {
            throw new UnexpectedValueException('Broken own date snapshot');
        }
        parent::__construct($data['local'], new DateTimeZone($data['zone']));
    }
}
$own = serialize(new BookingDate('2024-06-15 10:30:00.125456 Europe/Moscow'));
$restored = unserialize($own, ['allowed_classes' => [BookingDate::class]]);
if (!$restored instanceof BookingDate) { throw new RuntimeException('Wrong type'); }
echo $restored->format('Y-m-d H:i:s.u e'), "\n";
Ожидаемый результат
2024-06-15 10:30:00.125456 Europe/Moscow

Это пример пары hooks для своего снимка, а не универсальная валидация формы или долговременный формат. local без смещения и отдельная zone сохраняют регион; неоднозначные местные часы при DST потребовали бы дополнительного момента/смещения. Для обычного DateTime собственные hooks не нужны.

Сравните: DateTimeImmutable::__serialize, unserialize, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.

PHP 5.5+ · date

public function __wakeup(): void

Распознать устаревший обработчик восстановления даты в старом коде.

Часть вызоваДоговор
Параметры / возвратНет параметров, void; исторически инициализировал дату из восстановленных свойств.
СтатусDeprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату.
ГраницаПрямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error.
Как PHP восстанавливает дату
  1. Старый код содержит __wakeup
  2. PHP 8.5 помечает метод устаревшим
  3. Современный объект имеет __serialize/__unserialize
  4. Приложение пользуется согласованным маршрутом сохранения

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Увидеть устаревание без запуска старого hookСамостоятельный пример
<?php
declare(strict_types=1);

$method = new ReflectionMethod(DateTimeImmutable::class, '__wakeup');
$attributes = $method->getAttributes(Deprecated::class);
echo count($attributes), "\n";
echo $attributes[0]->newInstance()->since, "\n";
Ожидаемый результат
1
8.5

Reflection здесь читает метаданные PHP 8.5, не вызывает hook и не подавляет предупреждение. На раннем PHP этот атрибут отсутствует; проверяйте версию при анализе старого проекта.

Продолжить запись клиента без ручного вызова устаревшего hookСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00 Europe/Moscow');
$snapshot = serialize($date);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTimeImmutable::class]]);
if (!$restored instanceof DateTimeImmutable) { throw new RuntimeException('Wrong own snapshot'); }
echo 'Запись после восстановления: ', $restored->format('d.m.Y H:i P'), "\n";
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Напомнить в ', $reminder->format('H:i'), "\n";
Ожидаемый результат
Запись после восстановления: 15.06.2024 10:30 +03:00
Напомнить в 10:00

После обычного unserialize готовая дата уже вернулась в restored. Дополнительный __wakeup не нужен: PHP использовал современные __serialize/__unserialize. Расчёт напоминания показывает, что восстановленное значение можно сразу применять. Первый опыт карточки оставлен для анализа версии: Reflection читает атрибут устаревания PHP 8.5, не вызывает старый метод.

Сравните: DateTimeImmutable::__serialize, DateTimeImmutable::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::__serializeПодготовить внутренний массив даты для механизма serialize.

PHP 8.2+ · date

public function __serialize(): array

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

Часть вызоваДоговор
ПараметрыНет. Обычно вызов делает PHP при serialize($date).
Возвратarray состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление.
ОшибкаНеинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения.
РасширениеЕсли подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override.
Путь даты в первом примере
  1. appointment: 15 июня 10:30:00.125456 Europe/Moscow
  2. __serialize() → массив state
  3. date / timezone / timezone_type читаются по ключам
  4. Исходная дата всё ещё даёт подпись записи

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Приложение может временно сохранить собственную запись на приём и затем продолжить работу с той же датой. Обычно serialize получает объект и сам вызывает __serialize. Сначала посмотрим промежуточный результат: __serialize без аргументов отдаёт массив полей; готовую строку из него создаёт уже serialize. Двойное подчёркивание помечает служебный метод. Сам DateTimeInterface описывает договор, экземпляр интерфейса создать нельзя: здесь его реализует DateTimeImmutable.

Посмотреть, какие данные сохраняются у записи на приёмСамостоятельный пример
<?php
$appointment = new DateTimeImmutable('2024-06-15 10:30:00.125456 Europe/Moscow');
$state = $appointment->__serialize();
echo 'Локальные часы в снимке: ', $state['date'], "\n";
echo 'Идентификатор зоны: ', $state['timezone'], "\n";
echo 'Способ задания зоны: ', $state['timezone_type'], " (регион)\n";
echo 'Запись клиента: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Локальные часы в снимке: 2024-06-15 10:30:00.125456
Идентификатор зоны: Europe/Moscow
Способ задания зоны: 3 (регион)
Запись клиента: 15.06.2024 10:30 +03:00

Прямой вызов возвращает массив в state; квадратные скобки читают отдельные поля. В нём сохранены микросекунды и регион, а не только подпись 15.06.2024. Значение timezone_type=3 обозначает регион; 1 означает фиксированное смещение, 2 — сокращение. Appointment остаётся пригодным для format. Это внутренний снимок, а не наша стабильная схема API. Сам DateTimeInterface описывает договор, экземпляр интерфейса создать нельзя: здесь его реализует DateTimeImmutable.

Восстановить свою запись и рассчитать напоминаниеСамостоятельный пример
<?php
$appointment = new DateTimeImmutable('2024-06-15 10:30:00.125456 Europe/Moscow');
$snapshot = serialize($appointment);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTimeImmutable::class]]);
if (!$restored instanceof DateTimeImmutable) { throw new RuntimeException('Wrong own snapshot type'); }
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Восстановленная запись: ', $restored->format('d.m.Y H:i:s.u P'), "\n";
echo 'Напоминание: ', $reminder->format('d.m.Y H:i P'), "\n";
echo 'Исходная запись: ', $appointment->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Восстановленная запись: 15.06.2024 10:30:00.125456 +03:00
Напоминание: 15.06.2024 10:00 +03:00
Исходная запись: 15.06.2024 10:30 +03:00

Serialize вернул строку snapshot. Unserialize прочитал эту только что созданную строку, восстановил отдельный объект и вернул его в restored. После проверки типа clone даёт независимую основу расчёта; sub(PT30M) возвращает время напоминания. Скобки вокруг clone разрешают сразу вызвать метод у копии. Для Immutable копия здесь необязательна, но позволяет показать одинаковый безопасный расчёт для двух классов. Allowed_classes ограничивает классы собственного снимка и не делает внешнюю сериализацию безопасной.

Сравните: DateTimeInterface::__unserialize, serialize, unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::__unserializeВосстановить внутреннее состояние даты из согласованного массива сериализации.

PHP 8.2+ · date

public function __unserialize(array $data): void

Восстановить внутреннее состояние даты из согласованного массива сериализации.

Часть вызоваДоговор
dataarray штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка.
Возвратvoid. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода.
ОшибкиПовреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление.
ImmutableПрямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде.
Путь даты в первом примере
  1. source → state с датой 15 июня
  2. target пока содержит 1 января 2000 года
  3. target->__unserialize(state) меняет target
  4. target->format показывает 15 июня; result равен null

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.

Понять изменение получателя и отсутствие даты в returnСамостоятельный пример
<?php
$source = new DateTimeImmutable('2024-06-15 10:30:00 UTC');
$state = $source->__serialize();
$target = new DateTimeImmutable('2000-01-01T00:00:00Z');
echo 'До восстановления: ', $target->format('d.m.Y H:i'), "\n";
$result = $target->__unserialize($state);
echo 'После восстановления: ', $target->format('d.m.Y H:i'), "\n";
if ($result === null) { echo "Дата находится в target; возвращённого объекта нет\n"; }
Ожидаемый результат
До восстановления: 01.01.2000 00:00
После восстановления: 15.06.2024 10:30
Дата находится в target; возвращённого объекта нет

Аргумент state попадает в параметр data служебного метода. Метод переинициализирует target: после вызова он содержит июнь 2024 года. Return объявлен void, поэтому присваивание результата даёт null и не создаёт новую дату. Этот диагностический прямой вызов действует даже у Immutable; в обычном коде его не используют как редактирование. Стандартный unserialize создаёт получателя и вызывает hook автоматически. Сам DateTimeInterface описывает договор, экземпляр интерфейса создать нельзя: здесь его реализует DateTimeImmutable.

Отказаться от повреждённого снимка и восстановить известную записьСамостоятельный пример
<?php
$target = new DateTimeImmutable('2000-01-01T00:00:00Z');
try {
    $target->__unserialize([]);
    echo 'Снимок принят: ', $target->format('d.m.Y H:i'), "\n";
} catch (Error $error) {
    echo "Снимок повреждён; получатель больше не используется\n";
    $target = new DateTimeImmutable('2024-06-15 10:30:00', new DateTimeZone('UTC'));
    echo 'Восстановлено из проверенной записи: ', $target->format('d.m.Y H:i'), " UTC\n";
}
Ожидаемый результат
Снимок повреждён; получатель больше не используется
Восстановлено из проверенной записи: 15.06.2024 10:30 UTC

Пустой массив не содержит штатной схемы. Error переводит выполнение в catch, строка «Снимок принят» не достигается. Новый конструктор использует отдельно известные данные, а не частично восстановленный target. Здесь фиксированная дата играет роль проверенной исходной записи; если такой записи нет, нужно сообщить об отказе, а не подставлять сегодняшнее число.

Следующий пример меняет именно схему собственного снимка. BookingDate наследует дату; public разрешает PHP вызвать два служебных метода. В __serialize ключи local и zone получают строки из текущего объекта через $this. Return отдаёт этот массив механизму serialize. При восстановлении __unserialize получает тот же массив в локальный data, проверяет наличие и строковые типы полей, затем parent::__construct инициализирует дату. Void означает, что объект уже заполняется на месте. Внешний вызов unserialize возвращает готовый BookingDate в restored, и format создаёт строку для пользователя. Для обычного сохранения даты наследование не нужно. Наследование и parent.

Согласовать собственный формат сохранения и восстановленияСамостоятельный пример
<?php
declare(strict_types=1);

class BookingDate extends DateTimeImmutable
{
    public function __serialize(): array
    {
        return ['local' => $this->format('Y-m-d H:i:s.u'),
            'zone' => $this->getTimezone()->getName()];
    }

    public function __unserialize(array $data): void
    {
        if (!isset($data['local'], $data['zone'])
            || !is_string($data['local']) || !is_string($data['zone'])) {
            throw new UnexpectedValueException('Broken own date snapshot');
        }
        parent::__construct($data['local'], new DateTimeZone($data['zone']));
    }
}
$own = serialize(new BookingDate('2024-06-15 10:30:00.125456 Europe/Moscow'));
$restored = unserialize($own, ['allowed_classes' => [BookingDate::class]]);
if (!$restored instanceof BookingDate) { throw new RuntimeException('Wrong type'); }
echo $restored->format('Y-m-d H:i:s.u e'), "\n";
Ожидаемый результат
2024-06-15 10:30:00.125456 Europe/Moscow

Это пример пары hooks для своего снимка, а не универсальная валидация формы или долговременный формат. local без смещения и отдельная zone сохраняют регион; неоднозначные местные часы при DST потребовали бы дополнительного момента/смещения. Для обычного DateTime собственные hooks не нужны.

Сравните: DateTimeInterface::__serialize, unserialize, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeInterface::__wakeupРаспознать устаревший обработчик восстановления даты в старом коде.

PHP 5.5+ · date

public function __wakeup(): void

Распознать устаревший обработчик восстановления даты в старом коде.

Часть вызоваДоговор
Параметры / возвратНет параметров, void; исторически инициализировал дату из восстановленных свойств.
СтатусDeprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату.
ГраницаПрямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error.
Как PHP восстанавливает дату
  1. Старый код содержит __wakeup
  2. PHP 8.5 помечает метод устаревшим
  3. Современный объект имеет __serialize/__unserialize
  4. Приложение пользуется согласованным маршрутом сохранения

Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.

Увидеть устаревание без запуска старого hookСамостоятельный пример
<?php
declare(strict_types=1);

$method = new ReflectionMethod(DateTimeImmutable::class, '__wakeup');
$attributes = $method->getAttributes(Deprecated::class);
echo count($attributes), "\n";
echo $attributes[0]->newInstance()->since, "\n";
Ожидаемый результат
1
8.5

Reflection здесь читает метаданные PHP 8.5, не вызывает hook и не подавляет предупреждение. На раннем PHP этот атрибут отсутствует; проверяйте версию при анализе старого проекта.

Продолжить запись клиента без ручного вызова устаревшего hookСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00 Europe/Moscow');
$snapshot = serialize($date);
$restored = unserialize($snapshot, ['allowed_classes' => [DateTimeImmutable::class]]);
if (!$restored instanceof DateTimeImmutable) { throw new RuntimeException('Wrong own snapshot'); }
echo 'Запись после восстановления: ', $restored->format('d.m.Y H:i P'), "\n";
$reminder = (clone $restored)->sub(new DateInterval('PT30M'));
echo 'Напомнить в ', $reminder->format('H:i'), "\n";
Ожидаемый результат
Запись после восстановления: 15.06.2024 10:30 +03:00
Напомнить в 10:00

После обычного unserialize готовая дата уже вернулась в restored. Дополнительный __wakeup не нужен: PHP использовал современные __serialize/__unserialize. Расчёт напоминания показывает, что восстановленное значение можно сразу применять. Первый опыт карточки оставлен для анализа версии: Reflection читает атрибут устаревания PHP 8.5, не вызывает старый метод.

Сравните: DateTimeInterface::__serialize, DateTimeInterface::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTime::__set_stateСоздать дату из массива состояния, используемого экспортом var_export.

PHP 5.3+ · date

public static function __set_state(array $array): DateTime

Создать дату из массива состояния, используемого экспортом var_export.

Часть вызоваДоговор
arrayМассив инициализации даты. Форму для экспорта готовит PHP; это не свободная структура формы/API.
ВозвратНовый объект соответствующего класса. Старый объект не меняется.
ОшибкиПовреждённая штатная схема вызывает Error; аргумент, который не является массивом, — TypeError.
Применениеvar_export($date, true) создаёт PHP-представление с вызовом __set_state. Не выполняйте внешний текст через eval; для обмена используйте явные данные.
Путь даты в первом примере
  1. date содержит дату и регион
  2. var_export(date,true) → строка PHP
  3. Строка содержит вызов DateTime::__set_state
  4. echo показывает текст; он пока не исполнялся

При чтении экспортированной PHP-конфигурации можно встретить Class::__set_state(array(...)). Это вызов фабрики: :: обращается к статическому методу, array(...) задаёт массив полей, результатом будет новый объект даты. Сначала выведем текст, затем вызовем фабрику с собственным массивом без исполнения строки.

Увидеть весь PHP-текст экспорта собственной датыСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00 Europe/Moscow');
$phpRepresentation = var_export($date, true);
echo $phpRepresentation, "\n";
Ожидаемый результат
\DateTime::__set_state(array(
   'date' => '2024-06-15 10:30:00.000000',
   'timezone_type' => 3,
   'timezone' => 'Europe/Moscow',
))

Второй аргумент true заставляет var_export вернуть строку в phpRepresentation. Echo показывает её целиком: имя класса, статический __set_state, array с датой и зоной. Начальная обратная косая черта обозначает полное имя класса из глобального пространства имён. Этот текст не выполняется и не является обычной подписью для клиента. Если экспортированное представление используется как доверенный PHP-код конфигурации, его вызов фабрики создаёт объект из этих полей; формат такой конфигурации привязан к среде.

Создать отдельную дату и подготовить предложение переносаСамостоятельный пример
<?php
$date = new DateTime('2024-06-15 10:30:00.125456 Europe/Moscow');
$copy = DateTime::__set_state($date->__serialize());
$proposal = $copy->modify('+1 day');
echo 'Предложенный перенос: ', $proposal->format('d.m.Y H:i:s.u P'), "\n";
echo 'Исходная запись: ', $date->format('d.m.Y H:i:s.u P'), "\n";
Ожидаемый результат
Предложенный перенос: 16.06.2024 10:30:00.125456 +03:00
Исходная запись: 15.06.2024 10:30:00.125456 +03:00

В текущем PHP массив собственного __serialize подходит __set_state. Фабрика возвращает отдельную дату в copy. Modify вычисляет следующий календарный день для предложения: у DateTime меняется copy, у Immutable возвращается ещё один объект. Исходная запись date осталась 15 июня с прежними микросекундами. Для повседневной копии DateTime проще clone; служебная фабрика нужна механизму экспорта.

Сравните: DateTime::__serialize, var_export, DateTimeImmutable::createFromInterface.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeImmutable::__set_stateСоздать дату из массива состояния, используемого экспортом var_export.

PHP 5.5+ · date

public static function __set_state(array $array): DateTimeImmutable

Создать дату из массива состояния, используемого экспортом var_export.

Часть вызоваДоговор
arrayМассив инициализации даты. Форму для экспорта готовит PHP; это не свободная структура формы/API.
ВозвратНовый объект соответствующего класса. Старый объект не меняется.
ОшибкиПовреждённая штатная схема вызывает Error; аргумент, который не является массивом, — TypeError.
Применениеvar_export($date, true) создаёт PHP-представление с вызовом __set_state. Не выполняйте внешний текст через eval; для обмена используйте явные данные.
Путь даты в первом примере
  1. date содержит дату и регион
  2. var_export(date,true) → строка PHP
  3. Строка содержит вызов DateTimeImmutable::__set_state
  4. echo показывает текст; он пока не исполнялся

При чтении экспортированной PHP-конфигурации можно встретить Class::__set_state(array(...)). Это вызов фабрики: :: обращается к статическому методу, array(...) задаёт массив полей, результатом будет новый объект даты. Сначала выведем текст, затем вызовем фабрику с собственным массивом без исполнения строки.

Увидеть весь PHP-текст экспорта собственной датыСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00 Europe/Moscow');
$phpRepresentation = var_export($date, true);
echo $phpRepresentation, "\n";
Ожидаемый результат
\DateTimeImmutable::__set_state(array(
   'date' => '2024-06-15 10:30:00.000000',
   'timezone_type' => 3,
   'timezone' => 'Europe/Moscow',
))

Второй аргумент true заставляет var_export вернуть строку в phpRepresentation. Echo показывает её целиком: имя класса, статический __set_state, array с датой и зоной. Начальная обратная косая черта обозначает полное имя класса из глобального пространства имён. Этот текст не выполняется и не является обычной подписью для клиента. Если экспортированное представление используется как доверенный PHP-код конфигурации, его вызов фабрики создаёт объект из этих полей; формат такой конфигурации привязан к среде.

Создать отдельную дату и подготовить предложение переносаСамостоятельный пример
<?php
$date = new DateTimeImmutable('2024-06-15 10:30:00.125456 Europe/Moscow');
$copy = DateTimeImmutable::__set_state($date->__serialize());
$proposal = $copy->modify('+1 day');
echo 'Предложенный перенос: ', $proposal->format('d.m.Y H:i:s.u P'), "\n";
echo 'Исходная запись: ', $date->format('d.m.Y H:i:s.u P'), "\n";
Ожидаемый результат
Предложенный перенос: 16.06.2024 10:30:00.125456 +03:00
Исходная запись: 15.06.2024 10:30:00.125456 +03:00

В текущем PHP массив собственного __serialize подходит __set_state. Фабрика возвращает отдельную дату в copy. Modify вычисляет следующий календарный день для предложения: у DateTime меняется copy, у Immutable возвращается ещё один объект. Исходная запись date осталась 15 июня с прежними микросекундами. Для повседневной копии DateTime проще clone; служебная фабрика нужна механизму экспорта.

Сравните: DateTimeImmutable::__serialize, var_export, DateTimeImmutable::createFromInterface.

Время, часовые пояса и календарь. Практические задачи с датами.

DateIntervalХранить правило изменения даты отдельно от конкретной даты доставки или встречи.

PHP 5.3+ · date

class DateInterval

Хранить правило изменения даты отдельно от конкретной даты доставки или встречи.

Часть вызоваДоговор
y / m / dЦелые компоненты лет / месяцев / дней. m означает месяцы.
h / i / sЧасы / минуты / секунды; минуты обозначены i, не m.
fДоля секунды float, например 0.125 означает 125000 микросекунд; свойство добавлено в PHP7.1.
invert1 означает отрицательное направление, 0 — положительное.
daysЧисло полных дней для результата DateTime/DateTimeImmutable::diff; для созданного конструктором интервала false. Не подменять компонентом d.
from_string / date_stringС PHP8.2 для createFromDateString в представлении свойств видимы from_string=true и исходный текст date_string. Для конструктора видимы числовые компоненты, invert, days и from_string=false. Для диагностики читаем get_object_vars: на проверенном8.5.11 прямое чтение from_string/date_string до формирования представления способно выдать Undefined property. Числовые d/i у относительного объекта читаются, хотя не перечислены в его дампе. Не строим прикладной контракт на скрытом представлении парсера.
ДоговорИнструкция между датами может быть необратимой. Дата-компоненты всегда меняют календарные поля. Временная часть из конструктора описывает прошедшее время, из relative/diff — сдвиг компонентов, что различается у перехода часового пояса.
Методы__construct, createFromDateString, format; diff даты также создаёт DateInterval. Дополнительные служебные hooks из реального API разобраны отдельно.
Путь даты в первом примере
  1. ordered:10 апреля 2026,09:00 UTC
  2. new DateInterval(P3D) →step:три дня
  3. ordered->add(step) →delivery:13 апреля 09:00
  4. Два format →подписи заказа и доставки

Доставка занимает три календарных дня. Дата заказа различается, а правило одинаково: его удобно хранить в DateInterval. P начинает запись периода, 3 задаёт число, D обозначает дни. Интервал похож на инструкцию для календаря; без исходной даты он не знает день доставки.

Сохранить правило доставки отдельно от заказаСамостоятельный пример
<?php
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$step = new DateInterval('P3D');
$delivery = $ordered->add($step);
echo 'Заказ принят: ', $ordered->format('d.m.Y H:i'), " UTC\n";
echo 'Ожидаемая доставка: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Заказ принят: 10.04.2026 09:00 UTC
Ожидаемая доставка: 13.04.2026 09:00 UTC

New DateInterval создал правило в step, но не вычислил дату. Ordered->add(step) применил три календарных дня к 10 апреля и вернул 13 апреля в delivery. Format каждого объекта возвращает строку для отдельной подписи. Заказ сохранил прежнее время, шаг можно использовать для следующего заказа. P3D не исключает выходные или праздники.

Сравнить два тарифа по их срокам на выбранную датуСамостоятельный пример
<?php
$month = new DateInterval('P1M');
$thirtyDays = new DateInterval('P30D');
foreach (['2025-02-01', '2025-03-01'] as $day) {
    $start = new DateTimeImmutable($day, new DateTimeZone('UTC'));
    $monthlyEnd = $start->add($month);
    $dailyEnd = $start->add($thirtyDays);
    echo 'Старт ', $start->format('d.m.Y'), ': месяц до ', $monthlyEnd->format('d.m.Y'),
        '; 30 дней до ', $dailyEnd->format('d.m.Y'), "\n";
    echo $monthlyEnd < $dailyEnd ? "Месячный срок закончится раньше\n" : "Срок 30 дней закончится раньше\n";
}
Ожидаемый результат
Старт 01.02.2025: месяц до 01.03.2025; 30 дней до 03.03.2025
Месячный срок закончится раньше
Старт 01.03.2025: месяц до 01.04.2025; 30 дней до 31.03.2025
Срок 30 дней закончится раньше

Один и тот же P1M короче 30 дней от начала февраля и длиннее от начала марта. Поэтому сравниваем полученные даты monthlyEnd/dailyEnd для конкретного start; сами DateInterval не являются сравнимыми фиксированными длительностями. В этом наборе сроки не равны; общий интерфейс сравнения должен предусмотреть также равенство. Foreach поочерёдно проверяет два независимых начала.

С PHP 7.4 DateInterval несравнимы операторами величины/нестрогого равенства; попытка может выдать предупреждение. Раньше разные интервалы считались равными, что также не определяло длительность. === проверяет идентичность объекта, а не равенство длительностей. Поля следует рассматривать как readonly по договору PHP.net; это рекомендация модели, а не обещание запрета любой записи движком.

Сравните: DateInterval::__construct, DateInterval::createFromDateString, DateInterval::format, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::__constructСоздать шаг по чёткой машинной записи ISO8601.

PHP 5.3+ · date

new DateInterval(string $duration)

Создать шаг по чёткой машинной записи ISO8601.

Часть вызоваДоговор
durationСтрока P…[T…] с целыми значениями и обозначениями единиц, в правильном порядке.
Результат newОбъект DateInterval; конструктор не возвращает дату.
ОшибкаDateMalformedIntervalStringException с PHP8.3, прежде Exception. Некорректный тип аргумента может дать TypeError.
Путь даты в первом примере
  1. start:10 апреля 09:00 UTC
  2. P1M /PT15M /P1W3DT2H →три объекта правил
  3. Каждое правило независимо передаётся start->add
  4. Полученные даты используются для трёх подписей
ОбозначениеЕдиница
PНачало спецификации.
Y / M / D / W до TГоды / месяцы / дни / недели. W переводится в дни; W вместе с D разрешено с PHP8.0.
TНачало временной части; без неё M означает месяцы.
H / M / S после TЧасы / минуты / секунды. Значения здесь — целые.
ПорядокОт старшей единицы к младшей; P1Y2D, а не P2D1Y.
Альтернативная формаНапример P0001-00-02T00:00:00; компоненты ограничены своим переносом, поэтому 25 часов в такой форме запрещены.

В настройках трёх действий нужны разные единицы. New DateInterval получает одну машинную строку: сначала P, затем целые значения с буквами единиц; T отделяет время от даты. Полученный объект — правило, которое позже передают add или sub у даты.

Разделить месячный срок, минутную паузу и составную доставкуСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-10T09:00:00Z');
$month = new DateInterval('P1M');
$pause = new DateInterval('PT15M');
$delivery = new DateInterval('P1W3DT2H');
echo 'Подписка до: ', $start->add($month)->format('d.m.Y H:i'), "\n";
echo 'Следующая попытка: ', $start->add($pause)->format('d.m.Y H:i'), "\n";
echo 'Составной срок доставки: ', $start->add($delivery)->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Подписка до: 10.05.2026 09:00
Следующая попытка: 10.04.2026 09:15
Составной срок доставки: 20.04.2026 11:00

P1M содержит месяц до буквы T, PT15M —15 минут после неё. В P1W3DT2H неделя W и три дня D дают 10 календарных дней, затем прибавляются 2 часа H. Каждый add возвращает отдельную дату, которую format превращает в строку для конкретного действия. Запуск следующей попытки или доставка не выполняются автоматически: здесь получены их сроки.

Отклонить настройку шага до построения расписанияСамостоятельный пример
<?php
foreach (['P2D1Y', 'PT0.5S', '-P1D'] as $text) {
    try {
        $step = new DateInterval($text);
        echo 'Шаг принят: ', $step->format('%d дн.'), "\n";
    } catch (Exception $error) {
        echo 'Расписание не создано: неверный шаг ', $text, "\n";
    }
}
Ожидаемый результат
Расписание не создано: неверный шаг P2D1Y
Расписание не создано: неверный шаг PT0.5S
Расписание не создано: неверный шаг -P1D

В P2D1Y нарушен порядок от старших единиц к младшим; в PT0.5S использована неподдерживаемая дробь; -P1D не является записью отрицательного интервала конструктора. New не вернул step, строка принятия пропущена, catch даёт конкретный отказ для настройки. Для отрицательного направления можно применить sub с P1D; для дроби — получить интервал через diff точных дат. С PHP8.3 конкретный класс ошибки DateMalformedIntervalStringException, базовый Exception здесь ловит и более ранние версии.

Это поддерживаемое подмножество ISO8601, а не любой математически осмысленный текст периода. Произвольные месяцы нельзя заранее заменить фиксированным числом секунд. Ошибку конфигурации лучше обнаружить при её загрузке, а не в каждой итерации расписания.

Сравните: DateInterval, DateInterval::format, DateInterval::createFromDateString.

Время, часовые пояса и календарь. Практические задачи с датами.

Прочитать альтернативную машинную запись периодаСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-10T09:00:00Z');
$step = new DateInterval('P0001-00-02T00:00:00');
$expires = $start->add($step);
echo 'Срок хранения год и два дня: ', $expires->format('d.m.Y H:i'), "\n";
try {
    $invalid = new DateInterval('P0000-00-00T25:00:00');
    echo 'Принят шаг: ', $invalid->format('%H ч.'), "\n";
} catch (Exception $error) {
    echo "Настройка с 25 часами в альтернативной записи отклонена\n";
}
Ожидаемый результат
Срок хранения год и два дня: 12.04.2027 09:00
Настройка с 25 часами в альтернативной записи отклонена

P0001-00-02T00:00:00 записывает один год, ноль месяцев и два дня, затем нулевое время. У этой формы компоненты ограничены границами переноса:25 часов запрещены, хотя PT25H в обычной записи допустимо. Это поддерживаемое PHP подмножество ISO8601. Пример вычисляет дату окончания хранения, ничего не удаляет.

DateInterval::createFromDateStringСоздать относительное календарное правило, например переход к следующему понедельнику.

PHP 5.3+ · date

DateInterval::createFromDateString(string $datetime): DateInterval

Создать относительное календарное правило, например переход к следующему понедельнику.

Часть вызоваДоговор
datetimeОтносительные части строки парсера DateTimeImmutable/DateTime/strtotime. Не ISO8601 P…; язык и грамматика парсера не являются произвольным русским текстом.
ВозвратDateInterval; доPHP8.3 при отказе мог возвращаться false.
ОтказООП с8.3 бросает DateMalformedIntervalStringException; до8.3 предупреждение+false. Процедурная функция ведёт себя по старому договору.
Путь даты в первом примере
  1. next monday →статическая фабрика
  2. Возврат DateInterval →rule
  3. 15 января 2026 +rule →19 января 2026
  4. format →день следующей встречи

Нужен следующий понедельник относительно даты заявки. Это не постоянное число дней: от четверга и от воскресенья шаг разный. CreateFromDateString читает поддерживаемую английскую относительную грамматику и возвращает правило для add. Строка P… относится к другому входу — конструктору DateInterval.

Применить календарное правило следующего понедельникаСамостоятельный пример
<?php
$rule = DateInterval::createFromDateString('next monday');
$start = new DateTimeImmutable('2026-01-15T09:00:00Z');
$meeting = $start->add($rule);
echo 'Следующий день встречи: ', $meeting->format('d.m.Y'), "\n";
$properties = get_object_vars($rule);
if ($properties['from_string']) {
    echo 'В диагностике сохранено текстовое правило: ', $properties['date_string'], "\n";
}
Ожидаемый результат
Следующий день встречи: 19.01.2026
В диагностике сохранено текстовое правило: next monday

Статический вызов через :: получает next monday и возвращает DateInterval в rule. Add применяет его к четвергу 15 января и выбирает понедельник 19-го. GetObjectVars здесь вызывается только для изучения представления: он возвращает массив видимых свойств, из которого выводится исходный текст. Для расчёта использован сам rule, не внутренние диагностические поля. Договор времени конкретной встречи задавайте явно; день и точные часы — разные настройки.

Рассчитать составное правило и отвергнуть чужую грамматикуСамостоятельный пример
<?php
$rule = DateInterval::createFromDateString('1 year - 2 days + 75 minutes');
$start = new DateTimeImmutable('2026-04-10T09:00:00Z');
$review = $start->add($rule);
echo 'Проверку назначить: ', $review->format('d.m.Y H:i'), "\n";
echo 'Компоненты настройки: ', $rule->format('%y г., %d дн., %i мин.'), "\n";
foreach (['P2D', '2026-01-01'] as $text) {
    try {
        $parsed = DateInterval::createFromDateString($text);
        echo 'Правило принято: ', $parsed->format('%d дн.'), "\n";
    } catch (Exception $error) {
        echo 'Относительная настройка отклонена: ', $text, "\n";
    }
}
Ожидаемый результат
Проверку назначить: 08.04.2027 10:15
Компоненты настройки: 1 г., -2 дн., 75 мин.
Относительная настройка отклонена: P2D
Относительная настройка отклонена: 2026-01-01

Для известных данных сначала прибавляется год, затем учитывается отрицательная дневная часть и 75 минут; результат —8 апреля 2027,10:15. Сам format интервала оставляет 75 минут, нормализация происходит при применении к дате. P2D — ISO-запись конструктора,2026-01-01 — абсолютная дата, поэтому относительная фабрика их отвергает. Обработка ошибок этого опыта рассчитана на PHP8.3+: раньше неверная строка давала warning/false.

Статический метод возвращает DateInterval. С PHP8.3 неверный текст выбрасывает DateMalformedIntervalStringException; ранее были предупреждение и false. С8.4 tentative return type уточнён до DateInterval (важно при наследовании/переопределении). Процедурный date_interval_create_from_date_string сохраняет предупреждение + false: это важное различие двух стилей. По дампу PHP8.2+ видны только from_string/date_string; не считайте d/i универсально недоступными, но не полагайтесь на скрытое представление сложного относительного правила.

Временная часть relative-правила меняет компоненты часов/минут. У new DateInterval("PT3H") временная часть задаёт прошедшие часы; на переходе DST это разные результаты. Полный опыт на переходе времени.

Не потерять правило последнего дня при выборе APIСамостоятельный пример
<?php
$date = new DateTimeImmutable('2026-01-15T09:00:00Z');
$text = 'last day of next month';
$rule = DateInterval::createFromDateString($text);
$viaInterval = $date->add($rule);
$viaModifier = $date->modify($text);
echo 'Через сохранённый интервал и add: ', $viaInterval->format('d.m.Y'), "\n";
echo 'Для договорённого конца месяца через modify: ', $viaModifier->format('d.m.Y'), "\n";
Ожидаемый результат
Через сохранённый интервал и add: 15.02.2026
Для договорённого конца месяца через modify: 28.02.2026

В проверенной версии PHP8.5.11 add применил месячную часть интервала, но не специальное намерение «последний день»: получилось 15 февраля. Modify с исходным правилом дал 28 февраля. Если договор подписки требует конец месяца, используем второй путь. Сохранённый date_string и успешное создание объекта не доказывают одинаковую интерпретацию каждого специального компонента всеми API; этот опыт не обещает поведение всех версий.

Сравните: DateInterval::__construct, date_interval_create_from_date_string, DateInterval::format, DateTimeImmutable::modify.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::formatСделать понятную подпись интервала, сохранив разницу между компонентом и общим числом дней.

PHP 5.3+ · date

DateInterval::format(string $format): string

Сделать понятную подпись интервала, сохранив разницу между компонентом и общим числом дней.

Часть вызоваДоговор
formatШаблон из буквального текста и %-спецификаторов ниже; регистр важен.
ВозвратФорматированная строка. Сам интервал не изменён.
ГраницаНи нормализации компонентов, ни универсального преобразования месяца в дни/секунды.
Путь даты в первом примере
  1. step хранит 32 дня и 90 минут
  2. format(%D дн.; %I мин.) →label
  3. echo показывает настройку без переносов
  4. start->add(step) отдельно вычисляет дату срока
СпецификаторРезультат
%%Буквальный %
%Y / %yГоды: минимум2 цифры с ведущим0 / без дополнения
%M / %mМесяцы: минимум2 цифры / без дополнения
%D / %dКомпонент дней: минимум2 цифры / без дополнения
%aПолные дни из diff, иначе строка (unknown)
%H / %hКомпонент часов: минимум2 цифры / без дополнения
%I / %iКомпонент минут: минимум2 цифры / без дополнения
%S / %sКомпонент секунд: минимум2 цифры / без дополнения
%F / %fМикросекунды: минимум6 цифр / без дополнения; это целое число микросекунд в выводе, не свойство f-доля
%R / %rЗнак + или − / только − для отрицательного, пусто для положительного

Нужно показать оператору сохранённый шаг, а затем вычислить срок одной заявки. У DateInterval каждая управляющая буква начинается с процента: %D — дни минимум двумя цифрами, %I — минуты; русские слова и знаки остаются текстом. Это другой шаблон, чем Y-m-d у даты.

Показать настройку так, чтобы не обещать неизвестное число месяцевСамостоятельный пример
<?php
$step = new DateInterval('P32DT90M');
$label = $step->format('%D дн.; %I мин.');
echo 'Настроенный срок: ', $label, "\n";
echo 'Полных дней из diff: ', $step->format('%a'), "\n";
$start = new DateTimeImmutable('2026-04-10T09:00:00Z');
$due = $start->add($step);
echo 'Для этой заявки срок наступит: ', $due->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Настроенный срок: 32 дн.; 90 мин.
Полных дней из diff: (unknown)
Для этой заявки срок наступит: 12.05.2026 10:30

Format получает шаблон, возвращает строку в label и не меняет step:32 дня и 90 минут остались компонентами. Для конструктора нет результата diff, поэтому %a даёт(unknown). Только после применения к конкретному 10 апреля получилось 12 мая 10:30. Ни %D, ни %I не переводят компоненты в месяцы или часы.

Подписать период аренды и количество оплачиваемых днейСамостоятельный пример
<?php
$from = new DateTimeImmutable('2026-01-01T00:00:00Z');
$to = new DateTimeImmutable('2026-02-03T00:00:00Z');
$period = $from->diff($to);
echo 'Период: ', $period->format('%m мес. %d дн.'), "\n";
echo 'Дней для оплаты: ', $period->format('%a'), "\n";
echo 'Движение к окончанию: ', $period->format('%R%a дн.'), "\n";
echo 'Движение к началу: ', $to->diff($from)->format('%r%a дн.'), "\n";
Ожидаемый результат
Период: 1 мес. 2 дн.
Дней для оплаты: 33
Движение к окончанию: +33 дн.
Движение к началу: -33 дн.

Январь даёт 31 день, ещё 2 дня до 3 февраля — всего 33. %m/%d показывают разложение на месяц и остаток; %a — полный счёт days, известный благодаря diff. Знак добавляется отдельно: %R всегда печатает плюс или минус, %r только минус. Это строки для подписи; для арифметики оплаты читают числовое свойство days. Дни в местной зоне не следует безусловно умножать на 24 для получения прошедших часов.

Вывести задержку с микросекундами и направлениемСамостоятельный пример
<?php
$sent = new DateTimeImmutable('2026-01-01T00:00:00.000000Z');
$received = new DateTimeImmutable('2026-01-01T00:00:00.007500Z');
$delay = $sent->diff($received);
echo 'Задержка доставки: ', $delay->format('%S.%F'), ' с', "\n";
echo 'Микросекундная часть числом: ', $delay->format('%f'), "\n";
echo 'Обратное направление: ', $received->diff($sent)->format('%R%S.%F'), ' с', "\n";
Ожидаемый результат
Задержка доставки: 00.007500 с
Микросекундная часть числом: 7500
Обратное направление: -00.007500 с

Здесь обе даты внутри одной секунды, поэтому %S.%F удобно показывает всю малую задержку. %F дополняет 7500 до 007500, %f возвращает 7500 без нулей; свойство f внутри объекта — дробь 0.0075. С PHP7.2.12 цифры F/f положительны, знак берут отдельно R/r. Для произвольного интервала %S всё равно только компонент секунд:1 час не превратится в 3600 автоматически.

Метод возвращает строку, не меняет интервал и не пересчитывает переносы. В отличие от DateTime::format, каждый управляющий символ требует %. Здесь нет универсального total-hours/total-seconds: при необходимости измеряйте конкретные моменты с учётом исходной даты, зоны и требуемой точности.

Сравните: DateInterval, date_interval_format, DateTimeImmutable::diff.

Время, часовые пояса и календарь. Практические задачи с датами.

date_interval_create_from_date_stringПроцедурный вход в парсер относительного интервала со своей политикой ошибки.

PHP 5.3+ · date

date_interval_create_from_date_string(string $datetime): DateInterval|false

Процедурный вход в парсер относительного интервала со своей политикой ошибки.

Часть вызоваДоговор
datetimeОтносительный текст того же парсера, что у статического метода; ISO P… сюда не передаётся.
ВозвратDateInterval при успехе; false при отказе.
ОшибкаПри некорректной строке предупреждение сохраняется; изменение ООП-метода в PHP8.3 эту функцию не изменило.
Путь даты в первом примере
  1. Строка 2 weeks +1 day →функция
  2. Возврат DateInterval →step; false означает отказ
  3. 1 апреля 09:00 +15 дней →review
  4. format →16 апреля 09:00 для проверки

Здесь правило приходит как текст настройки. Обычная функция получает строку аргументом; её возврат бывает DateInterval или false. Поэтому сначала проверяем результат, потом передаём успешный step в add. Это тот же относительный язык, что у статического метода, но политика ошибки отличается.

Получить дату повторной проверки из текстового шагаСамостоятельный пример
<?php
$step = date_interval_create_from_date_string('2 weeks + 1 day');
if ($step === false) {
    echo "Проверка не назначена: неверный шаг\n";
} else {
    $start = new DateTimeImmutable('2026-04-01T09:00:00Z');
    $review = $start->add($step);
    echo 'Шаг проверки: ', date_interval_format($step, '%d дн.'), "\n";
    echo 'Проверку назначить: ', $review->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Шаг проверки: 15 дн.
Проверку назначить: 16.04.2026 09:00

Функция получила относительную строку и вернула DateInterval в step:две недели плюс день дают 15 дней. False проверяется до использования объекта. В else формат интервала даёт подпись шага, add возвращает дату 16 апреля, format даты — подпись проверки. Процедурные функции и методы совместимы, когда их параметры ожидают один вид объекта.

Следующий опыт специально наблюдает предупреждение без @: set_error_handler(function (...) { ...; return true; }, E_WARNING) временно устанавливает обработчик предупреждений; finally возвращает прежний через restore_error_handler. Это учебный захват известной диагностики, не политика скрывать все ошибки приложения. Предупреждения и обработчики.

Остановить назначение после предупреждения парсераСамостоятельный пример
<?php
set_error_handler(static function (int $level): bool {
    echo "Диагностика: текст шага не разобран\n";
    return true;
}, E_WARNING);
try {
    $step = date_interval_create_from_date_string('NOT-AN-INTERVAL');
} finally {
    restore_error_handler();
}
if ($step === false) {
    echo "Повторная проверка не назначена; исправьте настройку шага\n";
}
Ожидаемый результат
Диагностика: текст шага не разобран
Повторная проверка не назначена; исправьте настройку шага

Второй аргумент E_WARNING ограничивает временный обработчик предупреждениями. PHP вызывает переданную функцию, она выводит категорию ошибки и возвращает true, сообщая об обработке. Finally восстанавливает прежний обработчик. В step затем находится false, поэтому программа не пытается передать его в add. В отличие от статического метода с PHP8.3, эта функция сохраняет договор warning+false.

Сравните: DateInterval::createFromDateString, date_interval_format.

Время, часовые пояса и календарь. Практические задачи с датами.

date_interval_formatФорматировать DateInterval обычной функцией с явным аргументом-объектом.

PHP 5.3+ · date

date_interval_format(DateInterval $object, string $format): string

Форматировать DateInterval обычной функцией с явным аргументом-объектом.

Часть вызоваДоговор
objectУже созданный DateInterval; строка P… не заменяет объект и даст TypeError.
formatПолный набор %-спецификаторов DateInterval::format, с теми же правилами знака/микросекунд/%a.
ВозвратСтрока; компоненты не нормализуются, исходник не меняется.
Путь даты в первом примере
  1. step:2 часа 15 минут
  2. Функция получает step и %H:%I
  3. Возврат строки 02:15 →label
  4. echo подписывает окно; add отдельно считает окончание

В карточке обслуживания нужна подпись «длится 02:15». Date_interval_format сама не прибавляет время и не возвращает дату: результат — строка для вывода. Сначала создадим интервал PT2H15M:два часа и пятнадцать минут.

Подписать длительность окна обслуживанияСамостоятельный пример
<?php
$step = new DateInterval('PT2H15M');
$label = date_interval_format($step, '%H:%I');
echo 'Окно обслуживания длится ', $label, "\n";
$start = new DateTimeImmutable('2026-04-10T09:00:00Z');
$end = $start->add($step);
echo 'Работы закончить к ', $end->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Окно обслуживания длится 02:15
Работы закончить к 10.04.2026 11:15 UTC

Date_interval_format получает объект первым аргументом, шаблон вторым и возвращает строку 02:15 в label. H/I с процентами — компоненты часов/минут, дополненные нулями. Отдельный add вычисляет окончание 11:15. Метод step->format с тем же шаблоном дал бы ту же строку, но объект в нём стоит слева от стрелки.

Показать продолжительность известного периода отчётаСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-05-01T00:00:00Z');
$end = new DateTimeImmutable('2026-05-06T12:00:00Z');
$difference = $start->diff($end);
$label = date_interval_format($difference, '%a дн. %h ч.');
echo 'Период отчёта: ', $label, "\n";
echo 'Полных часов для этого UTC-периода: ', intdiv($end->getTimestamp() - $start->getTimestamp(), 3600), "\n";
Ожидаемый результат
Период отчёта: 5 дн. 12 ч.
Полных часов для этого UTC-периода: 132

Diff связывает две конкретные даты, поэтому %a известно и равно 5. %h — остаток 12 часов, а не 132 полных часа. Для общего числа в этом целосекундном UTC-периоде разность timestamp делится на 3600 функцией intdiv. Если есть неполный час, intdiv его отбросит; правило округления следует выбрать по задаче.

Сравните: DateInterval::format, date_interval_create_from_date_string.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriodХранить ограниченную последовательность повторяющихся дат: дни отчёта, окна бронирования или календарь обслуживания.

PHP 5.3+ · date

class DatePeriod implements IteratorAggregate

Хранить ограниченную последовательность повторяющихся дат: дни отчёта, окна бронирования или календарь обслуживания.

Часть вызоваДоговор
EXCLUDE_START_DATEint-константа: исключить начальную дату из выдачи.
INCLUDE_END_DATEint-константа с PHP8.2: разрешить точку, равную конечной границе; не добавляет произвольную дату, не совпавшую с шагом. При форме с recurrences увеличивает минимальный счёт выдачи ещё на1.
start / end / intervalreadonly nullable-свойства с исходной датой, конечной границей (null при счёте повторов) и объектом DateInterval шага. Обычный успешно созданный период имеет начало и шаг.
currentreadonly ?DateTimeInterface: состояние даты при итерации; до обхода null. Не таблица всех дат и не гарантированная последняя дата после завершения.
include_start_date / include_end_datereadonly bool-флаги включения границ; include_end_date добавлен в8.2.
recurrencesreadonly int: минимум выдаваемых точек. При заданном n: n +1 за включённое начало +1 за включённый конец. При форме end: 0 + те же добавки, а не подсчитанная длина всего диапазона.
Методы и версииКонструктор, createFromISO8601String, getStartDate/getEndDate/getDateInterval/getRecurrences. Типизированные константы с8.4; это уточнение объявления, не смена смысла флагов.
Путь даты в первом примере
  1. start:1 апреля 09:00 UTC; step:7 дней
  2. new DatePeriod(start,step,2) →period
  3. foreach выдаёт 1,8,15 апреля
  4. Каждый date->format →подписанная строка отчёта

Нужен отчёт 1 апреля в 09:00, затем ещё два с интервалом в неделю. DatePeriod хранит такое ограниченное расписание. Старт —объект даты, шаг P7D —объект интервала, число 2 —дополнительные повторы. Foreach по очереди получает даты из периода в переменную date, как ранее получал значения массива.

Подготовить даты трёх еженедельных отчётовСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-01T09:00:00Z');
$step = new DateInterval('P7D');
$period = new DatePeriod($start, $step, 2);
$number = 1;
foreach ($period as $date) {
    echo 'Отчёт ', $number, ': ', $date->format('d.m.Y H:i'), " UTC\n";
    $number++;
}
Ожидаемый результат
Отчёт 1: 01.04.2026 09:00 UTC
Отчёт 2: 08.04.2026 09:00 UTC
Отчёт 3: 15.04.2026 09:00 UTC

New DatePeriod получает старт, шаг и 2 повтора после старта. Старт включён, поэтому foreach выдаёт три даты. На каждом шаге date содержит отдельную дату для format, number —номер отчёта. PHP перечислил весь план сразу:он не ждёт апреля и не запускает задачи в указанные дни. Для выполнения плана нужен отдельный планировщик.

Сохранить последнюю выданную дату внутри обходаСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T00:00:00Z'), new DateInterval('P1D'), 2);
if ($period->current === null) {
    echo "До обхода текущая дата ещё не выбрана\n";
}
echo $period->include_start_date ? "Старт включён\n" : "Старт исключён\n";
echo $period->include_end_date ? "Конечная граница включена\n" : "Конечная граница исключена\n";
echo 'Минимум точек по настройке: ', $period->recurrences, "\n";
$lastIssued = null;
foreach ($period as $date) {
    echo 'Проверка: ', $date->format('d.m.Y'), "\n";
    $lastIssued = $date;
}
echo 'Последняя назначенная проверка: ', $lastIssued->format('d.m.Y'), "\n";
echo 'Служебная current после обхода: ', $period->current->format('d.m.Y'), "\n";
Ожидаемый результат
До обхода текущая дата ещё не выбрана
Старт включён
Конечная граница исключена
Минимум точек по настройке: 3
Проверка: 01.04.2026
Проверка: 02.04.2026
Проверка: 03.04.2026
Последняя назначенная проверка: 03.04.2026
Служебная current после обхода: 04.04.2026

Current до начала равен null, затем меняется внутри реализации обхода. В проверенном PHP8.5.11 после завершения он продвинут к 4 апреля, хотя эта дата не выдавалась. Поэтому lastIssued сохраняется внутри foreach и правильно даёт 3 апреля. В этом заведомо непустом периоде доступ после цикла допустим; при произвольном диапазоне сначала проверяют lastIssued на null. Флаги показывают настройки, а не заменяют сам список выданных дат.

С PHP8.0 класс реализует IteratorAggregate (раньше Traversable), поэтому foreach получает отдельный итератор. Начало, конец, шаг и флаги доступны для чтения; readonly ограничивает прямую запись в свойство, но не запрещает внутреннее изменение current при обходе или восстановление служебным hook. Не изменяйте поля периода в попытке перестроить уже созданное расписание: для другого договора создайте новый период. Слишком большой диапазон требует ограничения: ленивый обход не означает, что миллионы повторений не стоят времени; iterator_to_array материализует их в памяти.

Сравните: DatePeriod::__construct, DatePeriod::getRecurrences, DatePeriod::getIterator.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::__constructСоздать расписание либо по числу повторений, либо до конечной даты, явно выбрав границы.

PHP 5.3+ · date

new DatePeriod(DateTimeInterface $start, DateInterval $interval, int $recurrences, int $options = 0)
new DatePeriod(DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, int $options = 0)

Создать расписание либо по числу повторений, либо до конечной даты, явно выбрав границы.

Часть вызоваДоговор
startНачальная дата; по умолчанию включена. Публичные документированные перегрузки называют третий аргумент recurrences или end. Здесь позиционный третий аргумент выбирает форму.
intervalОбъект шага между повторениями; не строка P… .
recurrences (форма1)int больше0: число повторений после старта, а не весь размер списка.
end (форма2)Дата-граница, по умолчанию исключённая.
optionsБитовая маска EXCLUDE_START_DATE и/или INCLUDE_END_DATE; 0 сохраняет значения по умолчанию.
ОтказНекорректная спецификация ISO с8.3 даёт DateMalformedPeriodStringException (раньше Exception); недопустимый счёт тоже отклоняется. Неверный тип аргумента — ошибка вызова.
Путь даты в первом примере
  1. start:1 апреля; step:2 дня; end:5 апреля
  2. Конструктор получает выбранные flags
  3. foreach выдаёт допустимые точки 1/3/5
  4. Массив подписей →одна строка календаря обслуживания

После простого расписания по числу повторов уточним диапазон:обслуживание каждые два дня от 1 до 5 апреля. Четвёртый аргумент конструктора задаёт флаги. DatePeriod::INCLUDE_END_DATE —именованное значение через ::, оператор | объединяет два разрешённых флага. 0 сохраняет стандартную политику:начало включено, конец исключён.

Выбрать, какие границы входят в дни обслуживанияСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-01T00:00:00Z');
$end = new DateTimeImmutable('2026-04-05T00:00:00Z');
$step = new DateInterval('P2D');
$policies = [
    'По умолчанию' => 0,
    'Включить конец' => DatePeriod::INCLUDE_END_DATE,
    'Исключить старт, включить конец' => DatePeriod::EXCLUDE_START_DATE | DatePeriod::INCLUDE_END_DATE,
];
foreach ($policies as $label => $flags) {
    $period = new DatePeriod($start, $step, $end, $flags);
    $days = [];
    foreach ($period as $date) {
        $days[] = $date->format('d.m.Y');
    }
    echo $label, ': ', implode(', ', $days), "\n";
}
Ожидаемый результат
По умолчанию: 01.04.2026, 03.04.2026
Включить конец: 01.04.2026, 03.04.2026, 05.04.2026
Исключить старт, включить конец: 03.04.2026, 05.04.2026

Третий аргумент —объект end, поэтому выбрана форма «до границы». По умолчанию 5 апреля не выдаётся. IncludeEnd разрешает его, а сочетание с ExcludeStart убирает 1 апреля. Внешний foreach выбирает подписанную политику, внутренний получает даты. Запись days[] добавляет подпись к массиву; implode соединяет её с остальными. Ни один флаг не меняет шаг 2 дня.

Увидеть даты при разном включении границ счётного периодаСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-01T00:00:00Z');
$step = new DateInterval('P1D');
foreach (['Обычно' => 0, 'Без старта' => DatePeriod::EXCLUDE_START_DATE, 'С включённым концом' => DatePeriod::INCLUDE_END_DATE] as $label => $flags) {
    $period = new DatePeriod($start, $step, 2, $flags);
    $days = [];
    foreach ($period as $date) { $days[] = $date->format('d.m.Y'); }
    echo $label, ': ', implode(', ', $days), '; всего ', count($days), "\n";
}
try {
    $empty = new DatePeriod($start, $step, 0);
    echo 'Создан период с повторами: ', $empty->getRecurrences(), "\n";
} catch (Exception $error) {
    echo "Ноль повторов отклонён; пустой список задаётся отдельно\n";
}
Ожидаемый результат
Обычно: 01.04.2026, 02.04.2026, 03.04.2026; всего 3
Без старта: 02.04.2026, 03.04.2026; всего 2
С включённым концом: 01.04.2026, 02.04.2026, 03.04.2026, 04.04.2026; всего 4
Ноль повторов отклонён; пустой список задаётся отдельно

Третий аргумент 2 —число повторов, не готовая длина выдачи. Обычное включённое начало добавляет ещё одну дату; INCLUDE_END_DATE при этой форме увеличивает счёт ещё на 1 даже без отдельного end-объекта. Count считает уже собранные реальные подписи. Значение 0 недопустимо, поэтому отсутствие запланированных дат лучше представить пустым массивом по явному правилу приложения, а не таким конструктором.

Возвращаемые при обходе даты имеют базовый тип DateTimeImmutable или DateTime согласно типу start; пользовательский подкласс старта не обещан для каждого результата. Форма start/interval/end учитывает полный момент (включая часы/секунды), а не только написанный день. Пустая/обратная граница и нестандартный шаг требуют явного бизнес-правила; не предполагайте автоматическое расписание назад.

Третья историческая форма new DatePeriod($isostr, $options) deprecated сPHP8.4; для строкового ISO-периода используйте createFromISO8601String сPHP8.3. Поддерживается подмножество ISO: нет бесконечного R/…, R0/… и смещений, отличных от UTC Z. null вместо конечной даты не означает бесконечный период. Требование recurrences>0 действует с7.2.19/7.3.6/7.4.0.

Сравните: DatePeriod::createFromISO8601String, DatePeriod::getRecurrences, DatePeriod::getEndDate.

Время, часовые пояса и календарь. Практические задачи с датами.

Не ожидать, что флаг добавит дату вне шагаСамостоятельный пример
<?php
$period = new DatePeriod(
    new DateTimeImmutable('2026-04-01T09:00:00Z'),
    new DateInterval('P2D'),
    new DateTimeImmutable('2026-04-06T09:00:00Z'),
    DatePeriod::INCLUDE_END_DATE
);
foreach ($period as $date) {
    echo 'Плановая проверка: ', $date->format('d.m.Y H:i'), "\n";
}
echo 'Разрешённая конечная граница: ', $period->getEndDate()->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Плановая проверка: 01.04.2026 09:00
Плановая проверка: 03.04.2026 09:00
Плановая проверка: 05.04.2026 09:00
Разрешённая конечная граница: 06.04.2026 09:00

Шаг 2 дня создаёт 1,3,5,7 апреля. Граница 6 апреля допускается флагом, но сама не принадлежит этому ряду;7-е уже позже границы. Поэтому последняя плановая проверка 5-го. Условие учитывает точный момент с часами, а не только написанный день. Если нужна отдельная финальная проверка 6-го, её добавляют осмысленно как другое правило.

DatePeriod::createFromISO8601StringПрочитать ограниченное ISO8601-расписание из машинной настройки и получить период.

PHP 8.3+ · date

DatePeriod::createFromISO8601String(string $specification, int $options = 0): static

Прочитать ограниченное ISO8601-расписание из машинной настройки и получить период.

Часть вызоваДоговор
specificationПоддерживаемая форма Rn/start/P…; Rn означает повторы. Поддерживается UTC Z, не произвольное +02:00.
optionsМаска границ как у конструктора.
ВозвратНовый DatePeriod/вызываемый подкласс; итерация выдаёт DateTimeImmutable.
ОшибкаDateMalformedPeriodStringException при неподдерживаемой/неверной строке; R0 и бесконечный R не поддержаны.
Путь даты в первом примере
  1. R2 /1 апреля 09:00 UTC /P7D
  2. Фабрика возвращает period
  3. foreach выдаёт начало и два повтора
  4. format →три даты отчётов

Настройка расписания может храниться одной машинной строкой:повторы/начало/шаг. Используем статическую фабрику createFromISO8601String через ::; устаревшую строковую форму new DatePeriod в новом коде не выбираем. Поддерживается ограниченное подмножество ISO8601, которое уточняется ниже.

Прочитать настройку повторных отчётов в UTCСамостоятельный пример
<?php
$specification = 'R2/2026-04-01T09:00:00Z/P7D';
$period = DatePeriod::createFromISO8601String($specification);
foreach ($period as $date) {
    echo 'Отчёт подготовить: ', $date->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Отчёт подготовить: 01.04.2026 09:00 +00:00
Отчёт подготовить: 08.04.2026 09:00 +00:00
Отчёт подготовить: 15.04.2026 09:00 +00:00

PHP8.3+. Фабрика разбирает строку и возвращает DatePeriod в period. R2 —два повтора после старта, косые черты разделяют части, Z задаёт UTC, P7D —шаг 7 дней. Старт включён, поэтому видны три строки. Foreach выдаёт DateTimeImmutable; каждый format превращает дату в задание отчёта. Код составляет план, но не создаёт отчёты в будущем автоматически.

Отклонить неподдерживаемую строку до планированияСамостоятельный пример
<?php
$settings = [
    'Ноль повторов' => 'R0/2026-04-01T00:00:00Z/P1D',
    'Без ограничения' => 'R/2026-04-01T00:00:00Z/P1D',
    'Смещение вместо Z' => 'R2/2026-04-01T00:00:00+02:00/P1D',
];
foreach ($settings as $label => $text) {
    try {
        $period = DatePeriod::createFromISO8601String($text);
        echo 'Расписание принято с начала ', $period->getStartDate()->format('d.m.Y'), "\n";
    } catch (DateMalformedPeriodStringException $error) {
        echo $label, ': расписание отклонено', "\n";
    }
}
Ожидаемый результат
Ноль повторов: расписание отклонено
Без ограничения: расписание отклонено
Смещение вместо Z: расписание отклонено

PHP8.3+. Ни одна строка не вернула period, поэтому подтверждение не печатается. API не принимает R0, бесконечное R или смещение+02:00 в этой форме. Для регионального расписания создают start с DateTimeZone и используют обычный конструктор с количеством или end. Простая замена+02:00 на Z изменила бы момент, если сначала не пересчитать часы.

Возврат static означает экземпляр вызываемого класса (важно для наследников), а не статический глобальный объект. options имеет ту же маску границ. Не заменяйте часы локальной зоны суффиксом Z без пересчёта момента: это изменит значение времени. Часовые пояса.

Сравните: DatePeriod::__construct, DatePeriod::getStartDate, DateInterval::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::getDateIntervalПолучить объект шага периода для подписи или следующего расчёта.

PHP 5.6.5+ · date

DatePeriod::getDateInterval(): DateInterval

Получить объект шага периода для подписи или следующего расчёта.

Часть вызоваДоговор
ПараметрыНет.
ВозвратDateInterval, представляющий шаг.
ГраницаЭто не суммарная длительность периода и не количество повторений. Поля DateInterval рассматривают как readonly по договору.
Путь даты в первом примере
  1. period хранит шаг P7D
  2. getDateInterval() →step
  3. step->format(%d) →строка 7
  4. echo включает её в подпись «каждые 7 дней»

Нужно открыть уже сохранённые настройки отчёта и показать частоту. GetDateInterval читает правило между соседними точками, а не сам список точек. После возврата у step можно вызвать методы DateInterval, например format.

Показать пользователю шаг сохранённого расписанияСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P7D'), 2);
$step = $period->getDateInterval();
echo 'Отчёт повторяется каждые ', $step->format('%d'), ' дней', "\n";
echo 'Первый отчёт: ', $period->getStartDate()->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Отчёт повторяется каждые 7 дней
Первый отчёт: 01.04.2026 09:00

GetDateInterval без аргументов возвращает объект DateInterval в step. Его format(%d) возвращает строку 7 для подписи настройки. Метод не перечислял все даты и не вычислял полную длину расписания:шаг 7 дней и три даты —разные величины. Эта конкретная подпись подходит чисто дневному шагу; для смешанного интервала нужно отобразить остальные компоненты.

Применить полученный шаг к отдельной заявкеСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P2D'), 2);
$step = $period->getDateInterval();
$other = new DateTimeImmutable('2026-05-10T09:00:00Z');
$nextCheck = $other->add($step);
echo 'Проверка другой заявки: ', $nextCheck->format('d.m.Y H:i'), "\n";
echo 'Начало исходного расписания: ', $period->getStartDate()->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Проверка другой заявки: 12.05.2026 09:00
Начало исходного расписания: 01.04.2026 09:00

Step —объект правила двух дней. Add применяет его к другой дате 10 мая и возвращает 12 мая в nextCheck. Исходный DatePeriod не переносится на май и по-прежнему начинается 1 апреля. Для другого полноценного расписания создают новый DatePeriod с нужным стартом и тем же шагом.

Сравните: DateInterval::format, DatePeriod::getStartDate, DatePeriod::getEndDate.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::getEndDateПрочитать заданную границу периода, не перепутав её с последней выданной датой.

PHP 5.6.5+ · date

DatePeriod::getEndDate(): ?DateTimeInterface

Прочитать заданную границу периода, не перепутав её с последней выданной датой.

Часть вызоваДоговор
ПараметрыНет.
Возвратnull при отсутствии end, например в форме recurrences или ISO без end. Если end был DateTimeImmutable — соответствующий immutable-объект; иначе клон DateTime.
Вопросительный знак?DateTimeInterface разрешает null; перед ->format проверьте его, если форма создания неизвестна.
ГраницаНе вычисляет последний элемент; включение end и совпадение с шагом проверяются отдельно.
Путь даты в первом примере
  1. Период по 2 повторам →end не задавался
  2. getEndDate() возвращает null
  3. Период с границей 4 апреля →возвращает объект end
  4. Граница 4 апреля отдельно от выданных 1,2,3

Карточка расписания должна показать его ограничение. GetEndDate без аргументов возвращает сохранённую дату end либо null, если период задан количеством. Вопросительный знак в ?DateTimeInterface отражает этот допустимый null.

Отличить заданную границу от последнего заданияСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-01T00:00:00Z');
$step = new DateInterval('P1D');
$byCount = new DatePeriod($start, $step, 2);
if ($byCount->getEndDate() === null) {
    echo "Первое расписание ограничено повторами; граница end не задавалась\n";
}
$byEnd = new DatePeriod($start, $step, new DateTimeImmutable('2026-04-04T00:00:00Z'));
$end = $byEnd->getEndDate();
echo 'Граница второго расписания: ', $end->format('d.m.Y'), "\n";
foreach ($byEnd as $date) { echo 'Задание: ', $date->format('d.m.Y'), "\n"; }
Ожидаемый результат
Первое расписание ограничено повторами; граница end не задавалась
Граница второго расписания: 04.04.2026
Задание: 01.04.2026
Задание: 02.04.2026
Задание: 03.04.2026

Первый возврат null означает отсутствие аргумента end, а не бесконечность:число повторов всё равно ограничивает выдачу. Во втором периоде end —дата 4 апреля, но она по умолчанию исключена, поэтому последняя строка задания 3 апреля. Когда форма создания неизвестна, сначала проверьте возвращённое значение на null и только затем вызывайте format.

Подготовить предложенную границу без изменения действующего планаСамостоятельный пример
<?php
$period = new DatePeriod(new DateTime('2026-04-01T00:00:00Z'), new DateInterval('P1D'), new DateTime('2026-04-04T00:00:00Z'));
$proposal = $period->getEndDate();
$proposal->modify('+1 day');
echo 'Предлагаемая граница: ', $proposal->format('d.m.Y'), "\n";
echo 'Действующая граница: ', $period->getEndDate()->format('d.m.Y'), "\n";
foreach ($period as $date) { echo 'Действующее задание: ', $date->format('d.m.Y'), "\n"; }
Ожидаемый результат
Предлагаемая граница: 05.04.2026
Действующая граница: 04.04.2026
Действующее задание: 01.04.2026
Действующее задание: 02.04.2026
Действующее задание: 03.04.2026

Для изменяемой конечной даты getter вернул отдельный DateTime. Modify изменил предложение до 5 апреля, а сохранённый план остался с границей 4-го и заданиями 1,2,3. Чтобы принять предложение, нужно явно создать новое расписание с новой границей; изменение возвращённой копии не меняет настройки прежнего объекта.

Сравните: DatePeriod::getStartDate, DatePeriod::getRecurrences, DatePeriod::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::getRecurrencesОтличить настройку числа повторов от размера последовательности.

PHP 7.2.17 / 7.3.4+ · date

DatePeriod::getRecurrences(): ?int

Отличить настройку числа повторов от размера последовательности.

Часть вызоваДоговор
ПараметрыНет.
ВозвратЧисло повторений, заданное при создании; если период создан через конечную дату — null.
Не путатьНи свойство recurrences, ни getRecurrences не являются универсальным count всех выдаваемых дат.
ВерсияДобавлен в ветках PHP7.2.17/7.3.4.
Путь даты в первом примере
  1. Конструктору передано 2
  2. getRecurrences() возвращает 2 в repeats
  3. Флаги влияют на число выдаваемых дат
  4. В подписи отдельно настройка, минимум и реальные даты

Нужно восстановить в форме именно число повторов, которое передали конструктору. GetRecurrences возвращает его без добавки за начало/конец. Метод может вернуть null для другой формы конструктора; этот случай показан следующим.

Показать сохранённые повторы рядом с фактическими датамиСамостоятельный пример
<?php
$start = new DateTimeImmutable('2026-04-01T00:00:00Z');
$step = new DateInterval('P1D');
foreach (['Обычный план' => 0, 'Без старта' => DatePeriod::EXCLUDE_START_DATE, 'С концом' => DatePeriod::INCLUDE_END_DATE] as $label => $flags) {
    $period = new DatePeriod($start, $step, 2, $flags);
    $repeats = $period->getRecurrences();
    $days = [];
    foreach ($period as $date) { $days[] = $date->format('d.m.Y'); }
    echo $label, ': повторов ', $repeats, '; минимум точек ', $period->recurrences,
        '; даты ', implode(', ', $days), "\n";
}
Ожидаемый результат
Обычный план: повторов 2; минимум точек 3; даты 01.04.2026, 02.04.2026, 03.04.2026
Без старта: повторов 2; минимум точек 2; даты 02.04.2026, 03.04.2026
С концом: повторов 2; минимум точек 4; даты 01.04.2026, 02.04.2026, 03.04.2026, 04.04.2026

Метод возвращает исходную настройку 2, одинаковую во всех трёх планах. Свойство recurrences учитывает включённые границы, а список демонстрирует фактическую выдачу. Не заменяйте одним словом «количество» эти разные величины. Здесь getter нужен для поля настройки, а даты —для календаря пользователя.

Показать план, заданный конечной датойСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T00:00:00Z'), new DateInterval('P1D'), new DateTimeImmutable('2026-04-04T00:00:00Z'));
$repeats = $period->getRecurrences();
if ($repeats === null) { echo "Настройка: до указанной границы, число повторов не задавалось\n"; }
$days = [];
foreach ($period as $date) { $days[] = $date->format('d.m.Y'); }
echo 'Назначенные даты: ', implode(', ', $days), "\n";
echo 'Фактически заданий: ', count($days), '; свойство recurrences: ', $period->recurrences, "\n";
Ожидаемый результат
Настройка: до указанной границы, число повторов не задавалось
Назначенные даты: 01.04.2026, 02.04.2026, 03.04.2026
Фактически заданий: 3; свойство recurrences: 1

Null сообщает о способе создания. Свойство 1 отражает минимальную добавку за включённое начало и не знает полной длины диапазона. Чтобы показать число 3, программа действительно перечислила даты и посчитала полученные подписи. Для огромного диапазона такое построение массива требует времени и памяти; getter не является бесплатной заменой подсчёта.

Сравните: DatePeriod, DatePeriod::__construct, DatePeriod::getEndDate.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::getStartDateПолучить исходную точку периода независимо от флага её включения.

PHP 5.6.5+ · date

DatePeriod::getStartDate(): DateTimeInterface

Получить исходную точку периода независимо от флага её включения.

Часть вызоваДоговор
ПараметрыНет.
ВозвратDateTimeImmutable для immutable-старта; DateTime в другом случае.
ГраницаНачало настройки не обязано быть первой выданной датой при EXCLUDE_START_DATE.
Путь даты в первом примере
  1. period создан со стартом 1 апреля 09:00
  2. getStartDate() →start
  3. start->sub(P1D) →reminder:31 марта 09:00
  4. echo показывает оба назначения дат

Перед началом известного плана нужно напомнить за один календарный день. GetStartDate без аргументов возвращает дату, с которой создан период; её можно форматировать или использовать в другом расчёте.

Подготовить напоминание перед началом расписанияСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$start = $period->getStartDate();
$reminder = $start->sub(new DateInterval('P1D'));
echo 'Расписание начинается: ', $start->format('d.m.Y H:i'), "\n";
echo 'Напомнить за день: ', $reminder->format('d.m.Y H:i'), "\n";
echo 'Тип полученной даты: ', get_class($start), "\n";
Ожидаемый результат
Расписание начинается: 01.04.2026 09:00
Напомнить за день: 31.03.2026 09:00
Тип полученной даты: DateTimeImmutable

Getter вернул DateTimeImmutable, потому что такой тип был у исходного start. Sub вернул отдельную дату 31 марта в reminder, которую можно передать планировщику напоминаний. Сам start остался 1 апреля. Для DateTime-старта возвращается DateTime:при независимом расчёте учитывайте его изменяемость и создавайте копию.

Отличить точку отсчёта от первого заданияСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2, DatePeriod::EXCLUDE_START_DATE);
echo 'Точка отсчёта в настройке: ', $period->getStartDate()->format('d.m.Y H:i'), "\n";
foreach ($period as $date) { echo 'Повторная проверка: ', $date->format('d.m.Y H:i'), "\n"; }
Ожидаемый результат
Точка отсчёта в настройке: 01.04.2026 09:00
Повторная проверка: 02.04.2026 09:00
Повторная проверка: 03.04.2026 09:00

Первичная проверка 1 апреля уже выполнена, поэтому из повторных заданий её исключили флагом. GetStartDate всё равно возвращает 1 апреля как сохранённую точку отсчёта. Первым выданным заданием становится 2 апреля; чтобы узнать именно его, нужно читать последовательность, а не переименовывать start.

Сравните: DatePeriod::getEndDate, DatePeriod::getDateInterval, DatePeriod::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::__serializeПолучить внутренний массив состояния для PHP-сериализации объекта.

Проверено PHP 8.5.11; служебный API · date

public function __serialize(): array

Получить внутренний массив состояния для PHP-сериализации объекта.

Часть вызоваДоговор
ПараметрыНет.
РезультатМассив данных; serialize затем создаёт из него строковое представление. Состояние шага/периода сохраняется, объект не перечисляет даты ради сериализации.
Ошибки и ограниченияОбъект должен быть корректно инициализирован. Не считать формат массива универсальным переносимым договором стороннего сервиса.
Источник APIДополнительный реальный метод из точного php-8.5.11 stub и реализации; отдельной страницы в frozen manual нет. Двойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация.
Путь даты в первом примере
  1. object хранит P2DT3H
  2. __serialize() → массив state
  3. Чтение конкретных полей по ключам
  4. Исходное правило даёт доставку 12 апреля 12:00

Нужно временно сохранить собственное правило доставки, затем применить его к новому заказу. __serialize без аргументов возвращает массив состояния; serialize вызывает этот метод и кодирует массив в строку. Сначала посмотрим только нужные нам поля и их связь с полезным результатом.

Сопоставить сохраняемые поля с действующим правиломСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
$state = $object->__serialize();
echo 'Сохраняемое правило: ', $state['d'], ' дня и ', $state['h'], " часа\n";
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
echo 'Пока действует доставка: ', $ordered->add($object)->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Сохраняемое правило: 2 дня и 3 часа
Пока действует доставка: 12.04.2026 12:00

Массив state содержит компоненты d=2 и h=3. Применённый к 10 апреля 09:00 исходный object даёт доставку 12 апреля 12:00. Сам __serialize не прибавлял дни: он только вернул массив. Это наблюдение внутренней схемы PHP 8.5.11, не универсальный формат чужого API.

Применить восстановленный объект в работеСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
$payload = serialize($object);
$restored = unserialize($payload, ['allowed_classes' => [DateInterval::class]]);
if (!$restored instanceof DateInterval) { throw new RuntimeException('Wrong own snapshot'); }
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Доставка после восстановления правила: 12.04.2026 12:00 UTC

Payload —строка, созданная здесь же из собственного объекта. Unserialize восстанавливает объект, PHP вызывает __unserialize для его состояния, и готовый результат попадает в restored. Add получает восстановленное правило и возвращает доставку. Проверка instanceof обнаруживает неожиданную форму собственного снимка. Для внешнего ввода нужен отдельный явный формат данных; список классов не устраняет риски чужой сериализации.

Сравните: DateInterval::__unserialize, DateInterval::__set_state.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::__unserializeВосстановить объект из собственного массива состояния; это служебная операция изменения получателя.

Проверено PHP 8.5.11; служебный API · date

public function __unserialize(array $data): void

Восстановить объект из собственного массива состояния; это служебная операция изменения получателя.

Часть вызоваДоговор
dataМассив состояния подходящей версии/типа, обычно полученный от __serialize внутри стандартного протокола.
Возвратvoid; изменяет внутреннее состояние существующего объекта.
ОтказДля DateInterval пустой массив в8.5.11 заполняется нулевыми компонентами, а не обязательно отвергается. Это не валидатор бизнес-схемы; некорректный date_string может дать Error.
Синтаксис примераДвойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация. clone копирует объект; catch (Error $error) ловит ошибку восстановления.
Путь даты в первом примере
  1. object->__serialize() → state
  2. clone object → отдельный restored
  3. restored->__unserialize(state) меняет restored; void
  4. Следующий код получает полезный результат из restored

У __unserialize параметр data —массив, а возврат void. Важно не написать restored = object->__unserialize(...): тогда в переменную попал бы null. Получатель уже существует, и после вызова работаем именно с ним.

Увидеть восстановленное состояние у получателяСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
$state = $object->__serialize();
$restored = clone $object;
$restored->__unserialize($state);
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Доставка после восстановления правила: 12.04.2026 12:00 UTC

Clone создаёт отдельный получатель restored. Прямой __unserialize получает массив state, заполняет получатель и заканчивается без полезного return. Следующая строка использует сам restored: как шаг для add, который возвращает дату доставки. В обычном коде создание объекта и этот hook организует unserialize; вручную вызывать его требуется здесь лишь для разбора механизма.

Отбросить повреждённый получатель и построить правило зановоСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
try {
    $object->__unserialize(['date_string' => 'NOT-AN-INTERVAL']);
} catch (Error $error) {
    echo "Повреждённый снимок отклонён\n";
    $restored = new DateInterval('P2DT3H');
}
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Повреждённый снимок отклонён
Доставка после восстановления правила: 12.04.2026 12:00 UTC

Error прерывает восстановление, после чего object не используется. Restored создаётся заново из отдельно известных проверенных настроек. У DateInterval пустой массив не служит надёжным отрицательным тестом: PHP 8.5.11 заполняет нулевые компоненты. Здесь неверный date_string действительно даёт Error. Если исходных настроек нет, обработка заканчивается сообщением об отказе; восстановить отсутствующие данные догадкой нельзя.

Сравните: DateInterval::__serialize, DateInterval::__wakeup.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::__wakeupУзнать старый hook восстановления и убрать ручное использование при миграции.

Deprecated PHP 8.5; наблюдение 8.5.11 · date

public function __wakeup(): void

Узнать старый hook восстановления и убрать ручное использование при миграции.

Часть вызоваДоговор
Параметры / возвратНет параметров; void.
НазначениеLegacy-восстановление сериализованного объекта из сохранённых свойств; не повторный конструктор и не штатная команда приложения.
ВерсияDeprecated сPHP8.5: современные hooks __serialize/__unserialize заменяют эту точку.
ОшибкиНеподходящее старое состояние может дать Error; успешный new не гарантирует уместность ручного __wakeup. Двойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация.
Что делать со старым вызовом
  1. Найден ручной __wakeup после восстановления
  2. PHP8.5 сообщает deprecation
  3. Для собственного payload используем обычный unserialize
  4. Современный hook восстановит объект автоматически

В первом опыте set_error_handler перехватывает только E_DEPRECATED и печатает факт устаревания; finally восстанавливает прежний handler. Это наблюдение старого API, не его рекомендация и не скрытое подавление всей диагностики.

Наблюдение legacy-hook на PHP8.5.11Самостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
set_error_handler(function ($severity, $message) {
    echo "Устаревший hook\n";
    return true;
}, E_DEPRECATED);
try {
    $object->__wakeup();
    echo "Вызов завершён\n";
} catch (Error $error) {
    echo "Нет ожидаемого старого состояния\n";
} finally {
    restore_error_handler();
}
Ожидаемый результат
Устаревший hook
Вызов завершён

С8.5 метод deprecated. В этом конкретном опыте DateInterval вызов завершается, но это не причина вставлять его после каждого new.

После обычного восстановления сразу продолжить расчётСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
$payload = serialize($object);
$restored = unserialize($payload, ['allowed_classes' => [DateInterval::class]]);
if (!$restored instanceof DateInterval) { throw new RuntimeException('Wrong own snapshot'); }
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Доставка после восстановления правила: 12.04.2026 12:00 UTC

Этот полный маршрут использует современные hooks автоматически. После unserialize можно сразу рассчитать доставку из восстановленного интервала. Первый опыт карточки сохраняет узкое наблюдение старого __wakeup на PHP 8.5.11 и его диагностику. В действующий маршрут такой ручной вызов добавлять не нужно.

Сравните: DateInterval::__serialize, DateInterval::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DateInterval::__set_stateСоздать отдельный объект при восстановлении PHP-экспорта; не выполнять посторонний код из настроек.

Проверено PHP 8.5.11; служебный API · date

public static function __set_state(array $array): DateInterval

Создать отдельный объект при восстановлении PHP-экспорта; не выполнять посторонний код из настроек.

Часть вызоваДоговор
arrayМассив состояния, формируемый PHP-экспортом; это не произвольный JSON-массив приложения.
ВозвратНовый DateInterval; исходный объект не меняется.
ОшибкиДля DateInterval пустой массив в8.5.11 заполняется нулевыми компонентами, а не обязательно отвергается. Это не валидатор бизнес-схемы; некорректный date_string может дать Error.
МеханизмДвойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация. Для пользовательского ввода не выполняют eval(var_export/полученного PHP); здесь такого исполнения нет.
Путь даты в первом примере
  1. object → собственный массив state
  2. DateInterval::__set_state(state) создаёт объект
  3. return → restored в месте вызова
  4. restored используется в расчёте/расписании

__set_state — статическая фабрика, которую использует PHP-код, полученный через var_export объекта. Она получает массив и возвращает новый объект. var_export сам лишь создаёт текст PHP и не исполняет его; учебный опыт вызывает фабрику напрямую с собственным состоянием, без eval.

В PHP-представлении объекта, которое готовит var_export, встречается статическая фабрика __set_state. Она принимает массив полей и возвращает новый объект, в отличие от __unserialize с void. Здесь вызываем фабрику напрямую с собственным состоянием; строковый PHP-код не исполняем.

Получить объект из собственного экспортируемого состоянияСамостоятельный пример
<?php
$object = new DateInterval('P2DT3H');
$state = $object->__serialize();
$restored = DateInterval::__set_state($state);
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Доставка после восстановления правила: 12.04.2026 12:00 UTC

Статический __set_state получает массив и возвращает отдельный DateInterval в restored. В PHP 8.5.11 собственная схема __serialize подходит этой фабрике. Передача restored в add превращает сохранённые компоненты в дату конкретной доставки. Исходный object не переинициализируется. Этот путь объясняет экспорт; для обычного создания приложения проще конструктор из понятных start/step/end или P2DT3H.

Обработать ошибку фабрики до использования результатаСамостоятельный пример
<?php
try {
    $restored = DateInterval::__set_state(['date_string' => 'NOT-AN-INTERVAL']);
} catch (Error $error) {
    echo "Фабрика отклонила снимок; берём проверенные настройки\n";
    $restored = new DateInterval('P2DT3H');
}
$ordered = new DateTimeImmutable('2026-04-10T09:00:00Z');
$delivery = $ordered->add($restored);
echo 'Доставка после восстановления правила: ', $delivery->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
Фабрика отклонила снимок; берём проверенные настройки
Доставка после восстановления правила: 12.04.2026 12:00 UTC

При ошибке фабрики присваивание restored не завершается. В catch переменная явно получает новый корректный объект из известных настроек, и только затем используется. Произвольную структуру пользовательского ввода нельзя считать экспортом: для неё нужны свои поля и обычный конструктор с их проверкой.

Сравните: DateInterval::__serialize, DateInterval::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::__serializeПолучить внутренний массив состояния для PHP-сериализации объекта.

Проверено PHP 8.5.11; служебный API · date

public function __serialize(): array

Получить внутренний массив состояния для PHP-сериализации объекта.

Часть вызоваДоговор
ПараметрыНет.
РезультатМассив данных; serialize затем создаёт из него строковое представление. Состояние шага/периода сохраняется, объект не перечисляет даты ради сериализации.
Ошибки и ограниченияОбъект должен быть корректно инициализирован. Не считать формат массива универсальным переносимым договором стороннего сервиса.
Источник APIДополнительный реальный метод из точного php-8.5.11 stub и реализации; отдельной страницы в frozen manual нет. Двойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация.
Путь даты в первом примере
  1. object хранит 1 апреля 09:00 / P1D / 2 повтора
  2. __serialize() → массив state
  3. Чтение конкретных полей по ключам
  4. Исходный план выдаёт проверки 1/2/3 апреля

Нужно сохранить собственное расписание проверок и затем снова получить его даты. __serialize без аргументов возвращает массив состояния; serialize вызывает этот метод и кодирует массив в строку. Сначала посмотрим только нужные нам поля и их связь с полезным результатом.

Сопоставить сохраняемые поля с действующим правиломСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$state = $object->__serialize();
echo 'Сохранённый старт: ', $state['start']->format('d.m.Y H:i'), "\n";
echo 'Сохранённый шаг: ', $state['interval']->format('%d'), " день\n";
echo 'Счёт с включённым началом: ', $state['recurrences'], "\n";
foreach ($object as $date) { echo 'Проверка: ', $date->format('d.m.Y H:i'), "\n"; }
Ожидаемый результат
Сохранённый старт: 01.04.2026 09:00
Сохранённый шаг: 1 день
Счёт с включённым началом: 3
Проверка: 01.04.2026 09:00
Проверка: 02.04.2026 09:00
Проверка: 03.04.2026 09:00

Внутри массива есть объекты start и interval, поэтому после чтения ключа вызываются их методы. Recurrences=3 включает начальную точку. Выданные проверки показывают назначение сохранённых полей; сериализация сама не перечисляла весь план. Это наблюдение внутренней схемы PHP 8.5.11, не универсальный формат чужого API.

Применить восстановленный объект в работеСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$payload = serialize($object);
$restored = unserialize($payload, ['allowed_classes' => [DatePeriod::class, DateInterval::class, DateTimeImmutable::class]]);
if (!$restored instanceof DatePeriod) { throw new RuntimeException('Wrong own snapshot'); }
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

Payload —строка, созданная здесь же из собственного объекта. Unserialize восстанавливает объект, PHP вызывает __unserialize для его состояния, и готовый результат попадает в restored. Foreach получает восстановленное расписание и выдаёт три полные даты. В allowed_classes перечислены также вложенные типы даты и шага. Проверка instanceof обнаруживает неожиданную форму собственного снимка. Для внешнего ввода нужен отдельный явный формат данных; список классов не устраняет риски чужой сериализации.

Сравните: DatePeriod::__unserialize, DatePeriod::__set_state.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::__unserializeВосстановить объект из собственного массива состояния; это служебная операция изменения получателя.

Проверено PHP 8.5.11; служебный API · date

public function __unserialize(array $data): void

Восстановить объект из собственного массива состояния; это служебная операция изменения получателя.

Часть вызоваДоговор
dataМассив состояния подходящей версии/типа, обычно полученный от __serialize внутри стандартного протокола.
Возвратvoid; изменяет внутреннее состояние существующего объекта.
ОтказDatePeriod проверяет ожидаемые типы/поля состояния; неполный массив, например [], отклоняется Error. Внутри данных есть объекты даты и интервала, не только строки.
Синтаксис примераДвойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация. clone копирует объект; catch (Error $error) ловит ошибку восстановления.
Путь даты в первом примере
  1. object->__serialize() → state
  2. clone object → отдельный restored
  3. restored->__unserialize(state) меняет restored; void
  4. Следующий код получает полезный результат из restored

У __unserialize параметр data —массив, а возврат void. Важно не написать restored = object->__unserialize(...): тогда в переменную попал бы null. Получатель уже существует, и после вызова работаем именно с ним.

Увидеть восстановленное состояние у получателяСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$state = $object->__serialize();
$restored = clone $object;
$restored->__unserialize($state);
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

Clone создаёт отдельный получатель restored. Прямой __unserialize получает массив state, заполняет получатель и заканчивается без полезного return. Следующая строка использует сам restored: как расписание для foreach, который выдаёт даты проверок. В обычном коде создание объекта и этот hook организует unserialize; вручную вызывать его требуется здесь лишь для разбора механизма.

Отбросить повреждённый получатель и построить правило зановоСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
try {
    $object->__unserialize([]);
} catch (Error $error) {
    echo "Повреждённый снимок отклонён\n";
    $restored = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
}
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Повреждённый снимок отклонён
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

Error прерывает восстановление, после чего object не используется. Restored создаётся заново из отдельно известных проверенных настроек. У DatePeriod неполный массив [] действительно отклоняется: нужны согласованные границы, шаг и флаги. Если исходных настроек нет, обработка заканчивается сообщением об отказе; восстановить отсутствующие данные догадкой нельзя.

Сравните: DatePeriod::__serialize, DatePeriod::__wakeup.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::__wakeupУзнать старый hook восстановления и убрать ручное использование при миграции.

Deprecated PHP 8.5; наблюдение 8.5.11 · date

public function __wakeup(): void

Узнать старый hook восстановления и убрать ручное использование при миграции.

Часть вызоваДоговор
Параметры / возвратНет параметров; void.
НазначениеLegacy-восстановление сериализованного объекта из сохранённых свойств; не повторный конструктор и не штатная команда приложения.
ВерсияDeprecated сPHP8.5: современные hooks __serialize/__unserialize заменяют эту точку.
ОшибкиНеподходящее старое состояние может дать Error; успешный new не гарантирует уместность ручного __wakeup. Двойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация.
Что делать со старым вызовом
  1. Найден ручной __wakeup после восстановления
  2. PHP8.5 сообщает deprecation
  3. Для собственного payload используем обычный unserialize
  4. Современный hook восстановит объект автоматически

В первом опыте set_error_handler перехватывает только E_DEPRECATED и печатает факт устаревания; finally восстанавливает прежний handler. Это наблюдение старого API, не его рекомендация и не скрытое подавление всей диагностики.

Наблюдение legacy-hook на PHP8.5.11Самостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T00:00:00Z'), new DateInterval('P1D'), 2);
set_error_handler(function ($severity, $message) {
    echo "Устаревший hook\n";
    return true;
}, E_DEPRECATED);
try {
    $object->__wakeup();
    echo "Вызов завершён\n";
} catch (Error $error) {
    echo "Нет ожидаемого старого состояния\n";
} finally {
    restore_error_handler();
}
Ожидаемый результат
Устаревший hook
Нет ожидаемого старого состояния

С8.5 метод deprecated. У DatePeriod ручной вызов на обычном корректном объекте не воспроизводит ожидаемый legacy-путь и в этом опыте даёт Error; это не способ инициализировать период.

После обычного восстановления сразу продолжить расчётСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$payload = serialize($object);
$restored = unserialize($payload, ['allowed_classes' => [DatePeriod::class, DateInterval::class, DateTimeImmutable::class]]);
if (!$restored instanceof DatePeriod) { throw new RuntimeException('Wrong own snapshot'); }
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

Этот полный маршрут использует современные hooks автоматически. После unserialize можно сразу получить даты проверок из восстановленного периода. Первый опыт карточки сохраняет узкое наблюдение старого __wakeup на PHP 8.5.11 и его диагностику. В действующий маршрут такой ручной вызов добавлять не нужно.

Сравните: DatePeriod::__serialize, DatePeriod::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::__set_stateСоздать отдельный объект при восстановлении PHP-экспорта; не выполнять посторонний код из настроек.

Проверено PHP 8.5.11; служебный API · date

public static function __set_state(array $array): DatePeriod

Создать отдельный объект при восстановлении PHP-экспорта; не выполнять посторонний код из настроек.

Часть вызоваДоговор
arrayМассив состояния, формируемый PHP-экспортом; это не произвольный JSON-массив приложения.
ВозвратНовый DatePeriod; исходный объект не меняется.
ОшибкиDatePeriod проверяет ожидаемые типы/поля состояния; неполный массив, например [], отклоняется Error. Внутри данных есть объекты даты и интервала, не только строки.
МеханизмДвойное подчёркивание обозначает служебный метод объекта. Обычная программа вызывает serialize($object) и unserialize($text), PHP сам подключает соответствующие hooks. __serialize возвращает массив состояния, а не сериализованную строку; __unserialize восстанавливает состояние и возвращает void (никакого полезного значения). В опытах строка/массив созданы только текущим кодом, без внешних данных. Для обмена с API выбирают явную JSON-схему, а PHP-сериализацию не принимают из запроса. Служебные методы и сериализация. Для пользовательского ввода не выполняют eval(var_export/полученного PHP); здесь такого исполнения нет.
Путь даты в первом примере
  1. object → собственный массив state
  2. DatePeriod::__set_state(state) создаёт объект
  3. return → restored в месте вызова
  4. restored используется в расчёте/расписании

__set_state — статическая фабрика, которую использует PHP-код, полученный через var_export объекта. Она получает массив и возвращает новый объект. var_export сам лишь создаёт текст PHP и не исполняет его; учебный опыт вызывает фабрику напрямую с собственным состоянием, без eval.

В PHP-представлении объекта, которое готовит var_export, встречается статическая фабрика __set_state. Она принимает массив полей и возвращает новый объект, в отличие от __unserialize с void. Здесь вызываем фабрику напрямую с собственным состоянием; строковый PHP-код не исполняем.

Получить объект из собственного экспортируемого состоянияСамостоятельный пример
<?php
$object = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$state = $object->__serialize();
$restored = DatePeriod::__set_state($state);
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

Статический __set_state получает массив и возвращает отдельный DatePeriod в restored. В PHP 8.5.11 собственная схема __serialize подходит этой фабрике. Foreach превращает восстановленный план в конкретные даты проверок. Исходный object не переинициализируется. Этот путь объясняет экспорт; для обычного создания приложения проще конструктор из понятных start/step/end или P2DT3H.

Обработать ошибку фабрики до использования результатаСамостоятельный пример
<?php
try {
    $restored = DatePeriod::__set_state([]);
} catch (Error $error) {
    echo "Фабрика отклонила снимок; берём проверенные настройки\n";
    $restored = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
}
foreach ($restored as $date) { echo 'Проверка после восстановления: ', $date->format('d.m.Y H:i'), " UTC\n"; }
Ожидаемый результат
Фабрика отклонила снимок; берём проверенные настройки
Проверка после восстановления: 01.04.2026 09:00 UTC
Проверка после восстановления: 02.04.2026 09:00 UTC
Проверка после восстановления: 03.04.2026 09:00 UTC

При ошибке фабрики присваивание restored не завершается. В catch переменная явно получает новый корректный объект из известных настроек, и только затем используется. Произвольную структуру пользовательского ввода нельзя считать экспортом: для неё нужны свои поля и обычный конструктор с их проверкой.

Сравните: DatePeriod::__serialize, DatePeriod::__unserialize.

Время, часовые пояса и календарь. Практические задачи с датами.

DatePeriod::getIteratorПолучить итератор дат, который обычно скрыт за foreach.

PHP 8.0+; проверено 8.5.11 · date

public function getIterator(): Iterator

Получить итератор дат, который обычно скрыт за foreach.

Часть вызоваДоговор
ПараметрыНет.
ВозвратIterator для обхода этого периода, а не массив дат.
Протоколrewind — начало; valid — есть ли элемент; current — значение; key — ключ; next — следующий. foreach делает эти шаги автоматически.
ВерсияDatePeriod реализует IteratorAggregate сPHP8.0; метод подтверждён точным stub8.5.11.
ОграниченияПолный обход стоит времени по числу дат; не превращайте неограниченный внешний ввод в огромный диапазон.
Путь даты в первом примере
  1. getIterator() →механизм обхода в iterator
  2. rewind →первая позиция; valid →есть дата
  3. current →1 апреля 09:00
  4. next и current →2 апреля 09:00

Иногда обработчик берёт задания по одному, а не сразу выполняет foreach. Для этого нужен итератор периода. Проверяйте valid перед current, особенно если границы могут дать пустую последовательность; конкретный внутренний класс итератора не является договором приложения.

Получить первое и следующее задание пошаговоСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 2);
$iterator = $period->getIterator();
$iterator->rewind();
if ($iterator->valid()) { echo 'Текущее задание: ', $iterator->current()->format('d.m.Y H:i'), "\n"; }
$iterator->next();
if ($iterator->valid()) { echo 'Следующее задание: ', $iterator->current()->format('d.m.Y H:i'), "\n"; }
Ожидаемый результат
Текущее задание: 01.04.2026 09:00
Следующее задание: 02.04.2026 09:00

GetIterator возвращает объект обхода, не массив и не дату. Rewind устанавливает первую позицию, valid проверяет, есть ли значение, current возвращает дату этой позиции, next продвигает на один шаг. Format у полученной даты создаёт строку задания. Здесь вручную разобраны две позиции; обычный foreach выполняет этот протокол автоматически.

Прочитать план для двух независимых представленийСамостоятельный пример
<?php
$period = new DatePeriod(new DateTimeImmutable('2026-04-01T09:00:00Z'), new DateInterval('P1D'), 1);
foreach ($period->getIterator() as $date) {
    echo 'Календарь: ', $date->format('d.m.Y'), "\n";
}
foreach ($period->getIterator() as $date) {
    echo 'Список заданий: проверка ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Календарь: 01.04.2026
Календарь: 02.04.2026
Список заданий: проверка 01.04.2026 09:00
Список заданий: проверка 02.04.2026 09:00

Второй вызов getIterator создаёт новый последовательный обход, поэтому завершение первого не лишает второе представление дат. Эти два полных обхода выполняются один за другим; это не обещание любых одновременных операций над внутренним current. Для обычной страницы foreach(period as date) проще, ручной getter нужен при явном протоколе итератора.

Сравните: DatePeriod, DatePeriod::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::__constructСоздать настройки часового пояса для показа встречи, заказа или расписания.

PHP 5.2+ · date

public function DateTimeZone::__construct(string $timezone)

Создать настройки часового пояса для показа встречи, заказа или расписания.

Часть вызоваДоговор
timezoneСтрока: региональный идентификатор IANA, фиксированное смещение или допустимое сокращение.
Результат newОбъект DateTimeZone. Сам конструктор не возвращает отдельного значения; new создаёт объект.
ОшибкаНеверное имя: DateInvalidTimeZoneException с PHP 8.3; раньше Exception. Неверный тип аргумента — TypeError, NUL-байт внутри строки — ValueError.
Три типаТип 1: +03:00, только смещение. Тип 2: CET, сокращение. Тип 3: Europe/Berlin, идентификатор с правилами по датам.
Одно событие, два способа показать часы
  1. 12:00 UTC — момент встречи
  2. new DateTimeZone("Asia/Tokyo") — правила отображения
  3. setTimezone() → 21:00, момент встречи тот же

new создаёт объект, имя класса выбирает его устройство, строка в скобках задаёт настройку. -> вызывает действие объекта. DateTimeZone не содержит дату встречи и не меняет часы компьютера. Передайте его объекту даты. В первом примере Z в исходной строке означает UTC; H:i просит вывести часы и минуты.

Показать встречу посетителю из ТокиоСамостоятельный пример
<?php
declare(strict_types=1);

$zone = new DateTimeZone('Asia/Tokyo');
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo $meeting->setTimezone($zone)->format('H:i'), "\n";
echo $meeting->format('H:i'), "\n";
Ожидаемый результат
21:00
12:00

setTimezone() у DateTimeImmutable возвращает новый объект. Исходная встреча сохранила свой вид в UTC; ни один вариант не означает перенос встречи на девять часов.

Фиксированное смещение и регион летом расходятсяСамостоятельный пример
<?php
declare(strict_types=1);

$summer = new DateTimeImmutable('2024-07-15T12:00:00Z');
$fixed = new DateTimeZone('+01:00');
$region = new DateTimeZone('Europe/Berlin');
echo $summer->setTimezone($fixed)->format('H:i P'), "\n";
echo $summer->setTimezone($region)->format('H:i P'), "\n";
Ожидаемый результат
13:00 +01:00
14:00 +02:00

Фиксированный +01:00 не знает летних правил Берлина. Для повторяющегося местного расписания храните регион и местное время, а политику неоднозначного или отсутствующего часа задавайте отдельно; один объект пояса её за вас не выбирает.

Отклонить опечатку в настройкеСамостоятельный пример
<?php
declare(strict_types=1);

try {
    $zone = new DateTimeZone('Europe/NoSuchCity');
    echo $zone->getName(), "\n";
} catch (Exception $error) {
    echo "Выберите часовой пояс из списка\n";
}
Ожидаемый результат
Выберите часовой пояс из списка

Exception здесь намеренно ловит и новый подкласс PHP 8.3, и старое исключение PHP 8.2. Текст внутренней ошибки не нужен посетителю. Для выбора региона дополнительно проверяйте в listIdentifiers(): успешный конструктор допускает и сокращения, и смещения.

Имена IANA могут содержать три части, например America/Argentina/Buenos_Aires. Не режьте их по первому косому слешу и не вычисляйте смещение из названия. Для обычного показа даты не требуется вручную обходить таблицу переходов.

Сравните: timezone_open, DateTimeZone::listIdentifiers, DateTimeZone::getOffset, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::getNameПрочитать сохранённое описание пояса, например для настроек филиала.

PHP 5.2+ · date

public function DateTimeZone::getName(): string

Прочитать сохранённое описание пояса, например для настроек филиала.

Часть вызоваДоговор
АргументыНет: используется уже созданный объект.
РезультатСтроковое имя, сокращение или нормализованное смещение. Его можно снова передать конструктору.
ГраницаЭто не перевод названия города и не обещание канонизировать старый идентификатор. Метод не читает пояс браузера.
Настройка сохраняется как строка
  1. Объект Europe/Berlin
  2. getName() → Europe/Berlin
  3. Строка в конфигурации → новый объект при следующем запросе

Вызов без аргументов записывается с пустыми скобками: $zone->getName(). Результат можно вывести или положить в массив. Объект принадлежит текущему процессу; для следующего HTTP-запроса сохраняют строку в хранилище настроек.

Имя пояса для настройки магазинаСамостоятельный пример
<?php
declare(strict_types=1);

$zone = new DateTimeZone('Europe/Berlin');
$settings = ['timezone' => $zone->getName()];
$restored = new DateTimeZone($settings['timezone']);
echo $restored->getName(), "\n";
Ожидаемый результат
Europe/Berlin

Массив имитирует прочитанную конфигурацию. В реальном сервисе она придёт из файла или БД; сериализовать внутренности DateTimeZone для такой настройки не требуется.

getName не превращает смещение или псевдоним в регионСамостоятельный пример
<?php
declare(strict_types=1);

$fixed = new DateTimeZone('GMT+04:45');
$legacy = new DateTimeZone('US/Eastern');
echo $fixed->getName(), "\n";
echo $legacy->getName(), "\n";
Ожидаемый результат
+04:45
US/Eastern

Смещение нормализовалось, а совместимое старое имя осталось старым. Для нового выбора предлагается America/New_York. Миграцию уже сохранённых имён выполняйте явно, с проверкой правил дат; getName() не является сервисом миграции.

Если выводите стороннюю настройку в HTML, применяйте htmlspecialchars. Извлечь фактическое смещение для даты поможет DateTimeZone::getOffset.

Сравните: timezone_name_get, DateTimeZone::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::getOffsetУзнать смещение выбранного региона от UTC в конкретный момент.

PHP 5.2+ · date

public function DateTimeZone::getOffset(DateTimeInterface $datetime): int

Узнать смещение выбранного региона от UTC в конкретный момент.

Часть вызоваДоговор
datetimeDateTime или DateTimeImmutable, задающий момент. Его собственный пояс может отличаться от пояса, у которого вызван метод.
РезультатЦелое число секунд: положительное восточнее UTC, отрицательное западнее. Это не количество часов и не разница между двумя переданными поясами.
Ошибки и состояниеНеверный тип — TypeError. Объект даты и объект пояса не изменяются. Смещение может меняться с датой; оно не всегда кратно часу.
Путь даты в первом примере
  1. zone: Europe/Berlin; meeting: 15 января 12:00 UTC
  2. getOffset(meeting) → offsetSeconds=3600
  3. setTimezone(zone) → местная дата 13:00
  4. Подпись соединяет дату и значение секунд; июль даёт 14:00/7200

Один регион может иметь разные смещения зимой и летом. Поэтому метод требует объект даты. Двоеточие : int в сигнатуре справочника описывает возвращаемый тип; в строке вызова его писать не нужно.

Показать зимнюю и летнюю встречу с действующим смещениемСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Berlin');
foreach (['Зимняя встреча' => '2024-01-15T12:00:00Z', 'Летняя встреча' => '2024-07-15T12:00:00Z'] as $label => $text) {
    $meeting = new DateTimeImmutable($text);
    $offsetSeconds = $zone->getOffset($meeting);
    $local = $meeting->setTimezone($zone);
    echo $label, ': ', $local->format('d.m.Y H:i P'), '; от UTC ', $offsetSeconds, " секунд\n";
}
Ожидаемый результат
Зимняя встреча: 15.01.2024 13:00 +01:00; от UTC 3600 секунд
Летняя встреча: 15.07.2024 14:00 +02:00; от UTC 7200 секунд

Zone выбирает правила Берлина, meeting задаёт момент для поиска. GetOffset возвращает 3600 или 7200 в offsetSeconds. Это диагностическая часть подписи; часы получены отдельным setTimezone, который сохраняет момент. Foreach повторяет две одинаковые операции над двумя известными датами. Прибавлять offsetSeconds к timestamp для показа нельзя: так изменился бы момент встречи.

Принять два представления одного момента и показать неполный часСамостоятельный пример
<?php
$zone = new DateTimeZone('Asia/Kathmandu');
$utc = new DateTimeImmutable('2024-01-15T12:00:00Z');
$otherView = new DateTimeImmutable('2024-01-15T07:00:00-05:00');
foreach (['UTC-вход' => $utc, 'Вход со смещением' => $otherView] as $label => $instant) {
    echo $label, ': ', $zone->getOffset($instant), ' секунд; в Катманду ',
        $instant->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
UTC-вход: 20700 секунд; в Катманду 15.01.2024 17:45 +05:45
Вход со смещением: 20700 секунд; в Катманду 15.01.2024 17:45 +05:45

12:00 UTC и 07:00−05:00 обозначают один момент. GetOffset использует этот момент, а не записанные местные часы входного объекта, поэтому возвращает одно число 20700. Оно равно 5 часам 45 минутам. Format(P) сохраняет неполный час; округление до целого числа часов дало бы неверную подпись.

Для перевода часов используйте setTimezone(), а не прибавление getOffset() к Unix timestamp: прибавление создаёт другой момент. getOffset нужен для диагностики, сравнения правил и специальных протоколов.

Сравните: timezone_offset_get, DateTimeZone::getTransitions, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::getLocationПолучить страну и справочную точку региона, чтобы подписать выбор часового пояса.

PHP 5.2+ · date

public function DateTimeZone::getLocation(): array|false

Получить страну и справочную точку региона, чтобы подписать выбор часового пояса.

Часть вызоваДоговор
АргументыНет.
РезультатМассив country_code (строка ISO 3166-1), latitude и longitude (float), comments (строка); либо false, если у объекта нет таблицы региона.
Граница данныхУ смещения и сокращения результат false. Координаты обозначают представительную точку, а не границы региона и не положение посетителя. Для служебных зон возможны условные значения, например страна ?? у UTC.
Путь даты в первом примере
  1. zone: Europe/Berlin
  2. getLocation() → массив location или false
  3. После проверки читаем country_code/latitude/longitude/comments
  4. Получаем подписанную справочную карточку региона

После вызова сначала сравните результат с false, затем читайте ключ массива в квадратных скобках. === различает false и массив без приведения типов. Не привязывайте оплату или ограничения доставки к справочной точке часового пояса.

Подписать справочную точку выбранного регионаСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Berlin');
$location = $zone->getLocation();
if ($location === false) { throw new RuntimeException('Region geography unavailable'); }
echo 'Регион: ', $zone->getName(), '; страна ', $location['country_code'], "\n";
echo 'Справочная точка: ', number_format($location['latitude'], 2, '.', ''), ', ',
    number_format($location['longitude'], 2, '.', ''), "\n";
echo 'Комментарий: ', $location['comments'] === '' ? 'нет дополнительного описания' : $location['comments'], "\n";
Ожидаемый результат
Регион: Europe/Berlin; страна DE
Справочная точка: 52.50, 13.37
Комментарий: most of Germany

GetLocation вернул массив в location. По ключам получены страна, широта, долгота и комментарий базы. Number_format лишь показывает координаты с двумя десятичными знаками; исходные float не округляются внутри объекта. Это точка справочника для подписи региона, а не адрес посетителя и не карта границ страны. Текст комментария зависит от tzdb.

Фиксированное смещение не сообщает странуСамостоятельный пример
<?php
declare(strict_types=1);

$zone = new DateTimeZone('+03:00');
$location = $zone->getLocation();
echo $location === false ? "Страна неизвестна\n" : $location['country_code']."\n";
$utcLocation = (new DateTimeZone('UTC'))->getLocation();
echo $utcLocation['country_code'] === '??' ? "UTC: страна не задана\n" : "Страна задана\n";
Ожидаемый результат
Страна неизвестна
UTC: страна не задана

Разные причины отсутствия содержательной географии требуют разных проверок. false — отсутствие структуры; ?? внутри массива — служебная метка. Не заменяйте их предположением о стране сервера.

Сравните: timezone_location_get, DateTimeZone::listIdentifiers, DateTimeZone::getName.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::getTransitionsПосмотреть, когда меняются правила часов региона, и объяснить скачок в расписании.

PHP 5.2+ · date

public function DateTimeZone::getTransitions(int $timestampBegin = PHP_INT_MIN, int $timestampEnd = 2147483647): array|false

Посмотреть, когда меняются правила часов региона, и объяснить скачок в расписании.

Часть вызоваДоговор
timestampBeginUnix timestamp в секундах. По умолчанию PHP_INT_MIN; первая запись описывает состояние именно на этой границе, даже если перехода тогда не было.
timestampEndВерхняя граница в секундах. С PHP 8.1 по умолчанию 2147483647; раньше PHP_INT_MAX. Для своего периода укажите обе границы явно.
РезультатСписок массивов ts (int), time (строка), offset (секунды int), isdst (bool), abbr (строка). Для типов 1/2 — false, а не пустой список.
Версии и ошибкиtime использует ISO8601_EXPANDED с PHP 8.2; раньше ISO8601. Неверные типы — TypeError. Собственный диапазон проверьте на begin < end; метод не служит валидатором бизнес-периода.
Начальная строка и изменения — разные вещи
  1. 1 января: действует +01:00, это снимок
  2. 31 марта: смещение меняется на +02:00
  3. 27 октября: смещение меняется на +01:00

Метод нужен при исследовании расписаний, а не при каждом обычном выводе даты. Возвращается вложенный массив: внешний список строк, внутри каждой — подписанные поля. foreach читает строки по очереди. isdst — метка правила DST, не универсальное обещание «прибавить ровно час»: история регионов сложнее.

Объяснить посетителю изменения часов за годСамостоятельный пример
<?php
declare(strict_types=1);

$zone = new DateTimeZone('Europe/Berlin');
$begin = (new DateTimeImmutable('2024-01-01T00:00:00Z'))->getTimestamp();
$end = (new DateTimeImmutable('2025-01-01T00:00:00Z'))->getTimestamp();
$rows = $zone->getTransitions($begin, $end);
foreach ($rows as $index => $row) {
    echo $index === 0 ? 'На начало: ' : 'Изменение: ';
    echo $row['time'], ' / ', $row['offset'], "\n";
}
Ожидаемый результат
На начало: 2024-01-01T00:00:00+00:00 / 3600
Изменение: 2024-03-31T01:00:00+00:00 / 7200
Изменение: 2024-10-27T01:00:00+00:00 / 3600

Первая строка не означает, что в новогоднюю полночь переводили часы. Поле time обозначает UTC-момент записи; локальные часы по обе стороны перехода — отдельное преобразование.

Отличить фиксированную поправку от региональной таблицы без переводовСамостоятельный пример
<?php
$begin = 1704067200;
$end = 1735689600;
foreach (['+03:00', 'UTC'] as $name) {
    $rows = (new DateTimeZone($name))->getTransitions($begin, $end);
    if ($rows === false) { echo $name, ": таблицы переходов нет\n"; continue; }
    foreach ($rows as $row) {
        echo $name, ': состояние ', $row['time'], '; смещение ', $row['offset'], " секунд\n";
    }
}
Ожидаемый результат
+03:00: таблицы переходов нет
UTC: состояние 2024-01-01T00:00:00+00:00; смещение 0 секунд

У фиксированного +03:00 результат false, поэтому foreach не выполняется. У UTC есть региональное представление, и возвращается начальная строка на begin. В заданном 2024 году следующих изменений нет. Это три разных понятия: отсутствие таблицы, начальное состояние и реальное последующее изменение.

Показать местные часы по обе стороны выбранного переходаСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Berlin');
$begin = 1711846800;
$end = $begin + 60;
$rows = $zone->getTransitions($begin - 1, $end);
$changes = [];
foreach (array_slice($rows, 1) as $row) {
    if ($row['ts'] >= $begin && $row['ts'] < $end) { $changes[$row['ts']] = $row; }
}
foreach ($changes as $row) {
    $before = (new DateTimeImmutable('@' . ($row['ts'] - 1)))->setTimezone($zone);
    $after = (new DateTimeImmutable('@' . $row['ts']))->setTimezone($zone);
    echo 'До: ', $before->format('d.m.Y H:i:s P'), "\n";
    echo 'После: ', $after->format('d.m.Y H:i:s P'), "\n";
}
Ожидаемый результат
До: 31.03.2024 01:59:59 +01:00
После: 31.03.2024 03:00:00 +02:00

Begin задан как известный момент 31 марта 01:00 UTC. Запрос начинается секундой раньше: первая строка описывает прежнее состояние и удаляется array_slice. Фильтр оставляет [begin,end), ключ ts исключает дубликаты. Затем два Unix-момента через @ превращаются в местные даты; видно, что часов 02:xx здесь не было. Интервал ограничен минутой, а не всей историей; при PHP_INT_MIN вычитать ещё 1 нельзя.

Правила зависят от установленной tzdb; государства их меняют. Сохранённый расчёт будущего события может потребовать пересмотра после обновления. Исследуйте timezone_version_get и не обещайте, что таблица навсегда предсказывает будущие решения.

Сравните: timezone_transitions_get, DateTimeZone::getOffset, DateTimeZone::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::listIdentifiersПредложить человеку допустимые региональные настройки вместо ввода непонятных сокращений.

PHP 5.2+ · date

public static function DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array

Предложить человеку допустимые региональные настройки вместо ввода непонятных сокращений.

Часть вызоваДоговор
timezoneGroupALL — актуальные имена; ALL_WITH_BC — также совместимые устаревшие. Региональные флаги можно объединять через |. PER_COUNTRY — отдельный режим.
countryCodeДве прописные буквы ISO 3166-1, например JP. Учитывается только при timezoneGroup == PER_COUNTRY. null допустим с PHP 7.1, но в этом режиме код обязателен.
Результат и ошибкиЧисловой массив строк. Неверная длина/отсутствие кода в PER_COUNTRY — ValueError в PHP 8. Неизвестный двухбуквенный код либо неверный регистр могут дать пустой массив. До PHP 8 при ошибке возвращался false.
Выбор ограничен задачей
  1. Филиал в Японии → PER_COUNTRY + JP
  2. Список → Asia/Tokyo
  3. Сохранить выбранную строку, не её позицию в массиве

Это статический метод: запись DateTimeZone::listIdentifiers() вызывает его у класса, создавать new не требуется. :: также читает константу класса. У страны может быть много поясов; индекс списка не является постоянным идентификатором.

Варианты для японского филиалаСамостоятельный пример
<?php
declare(strict_types=1);

$choices = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, 'JP');
foreach ($choices as $name) {
    echo $name, "\n";
}
Ожидаемый результат
Asia/Tokyo

foreach проходит полученный список. Форму HTML собирают с экранированием, а присланную строку повторно проверяют на сервере; показывание списка в браузере не делает POST доверенным.

Принять регион доступного офиса и показать его встречуСамостоятельный пример
<?php
$choices = DateTimeZone::listIdentifiers(DateTimeZone::EUROPE | DateTimeZone::ASIA);
$event = new DateTimeImmutable('2024-01-15T12:00:00Z');
foreach (['Europe/Berlin', 'Asia/Tokyo', 'America/New_York'] as $submitted) {
    if (!in_array($submitted, $choices, true)) {
        echo $submitted, ": офис вне выбранных регионов\n";
        continue;
    }
    $zone = new DateTimeZone($submitted);
    echo $submitted, ': встреча ', $event->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Europe/Berlin: встреча 15.01.2024 13:00 +01:00
Asia/Tokyo: встреча 15.01.2024 21:00 +09:00
America/New_York: офис вне выбранных регионов

Оператор | объединяет европейскую и азиатскую группы. Функция возвращает список строк в choices, а строгий in_array проверяет каждую введённую строку. Для двух разрешённых имён создаются объекты и реальные подписи встреч; continue пропускает создание для Нью-Йорка. PER_COUNTRY —отдельный режим, его не добавляют к этой битовой комбинации.

Различить недостающий аргумент и пустой список страныСамостоятельный пример
<?php
try {
    DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY);
} catch (ValueError $error) {
    echo "Сначала выберите страну: код не передан\n";
}
foreach (['XX', 'JP'] as $country) {
    $choices = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $country);
    if ($choices === []) { echo $country, ": доступных регионов нет\n"; continue; }
    echo $country, ': ', implode(', ', $choices), "\n";
}
Ожидаемый результат
Сначала выберите страну: код не передан
XX: доступных регионов нет
JP: Asia/Tokyo

Первый вызов не вернул список: ValueError передал управление в catch. Двухбуквенный XX прошёл проверку формы аргумента, но вернул пустой массив. JP вернул реальное имя. Поэтому проверка длины строки не заменяет разрешённый перечень стран; пустой результат требует своей понятной подписи.

Список зависит от версии tzdb. Можно кэшировать его с timezone_version_get. Браузер способен предложить свой IANA ID через Intl.DateTimeFormat, однако сервер проверяет его по своему списку и даёт пользователю изменить выбор.

Сравните: timezone_identifiers_list, DateTimeZone::__construct, DateTimeZone::getLocation.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::listAbbreviationsИсследовать сокращение из старого журнала, не принимая его за уникальное название региона.

PHP 5.2+ · date

public static function DateTimeZone::listAbbreviations(): array

Исследовать сокращение из старого журнала, не принимая его за уникальное название региона.

Часть вызоваДоговор
АргументыНет; статический вызов у класса.
РезультатАссоциативный массив: строчное сокращение → список вариантов. Каждый вариант содержит dst (bool), offset (секунды int), timezone_id (string или null).
ОграничениеИсторическая предкомпилированная таблица неоднозначных сокращений. Обновление PECL timezonedb само по себе эту таблицу не обновляет. Не использовать её как источник вариантов современной формы.
Путь даты в первом примере
  1. DateTimeZone::listAbbreviations() → table
  2. table[cst] → rows с несколькими вариантами
  3. Выбраны два конкретных региона обычного времени
  4. Вывод показывает имена и разные смещения

Вложенный список означает, что одному короткому ключу соответствуют несколько записей. Получить первый элемент технически легко, но это может выбрать другой континент. Здесь сначала показываем сам факт неоднозначности, затем переносим его в обработчик импорта.

Поставщик прислал короткое сокращение вместо региона. Прежде чем импортировать время заказа, нужно увидеть неоднозначность. Функция возвращает вложенный массив; timezone_id —имя или null, offset —секунды, dst —логический признак. Ни первая запись, ни самое знакомое имя не доказывают намерение поставщика.

Показать два разных значения одного сокращенияСамостоятельный пример
<?php
$table = DateTimeZone::listAbbreviations();
$rows = $table['cst'];
$wanted = ['America/Chicago', 'Asia/Shanghai'];
$selected = [];
foreach ($rows as $row) {
    if (in_array($row['timezone_id'], $wanted, true) && !$row['dst']) {
        $selected[$row['timezone_id']] = $row['offset'];
    }
}
foreach ($wanted as $region) {
    echo 'CST: ', $region, '; ', $selected[$region], " секунд от UTC\n";
}
Ожидаемый результат
CST: America/Chicago; -21600 секунд от UTC
CST: Asia/Shanghai; 28800 секунд от UTC

Внешний массив table индексируется строчным сокращением, внутри rows —список вариантов. Выбираем два заранее названных региона и записи обычного времени, сохраняем offset под ключом имени. Второй foreach выводит конкретные пары в нужном порядке. Знак и величина различаются: одно сокращение не определяет момент импортированного события. Эта историческая таблица не заменяет getOffset для конкретной даты.

Отправить неоднозначный импорт на уточнениеСамостоятельный пример
<?php
declare(strict_types=1);

$table = DateTimeZone::listAbbreviations();
$input = 'PST';
$rows = $table[strtolower($input)] ?? [];
$regions = [];
foreach ($rows as $row) {
    if ($row['timezone_id'] !== null) {
        $regions[$row['timezone_id']] = true;
    }
}
echo count($regions) > 1 ? "Уточните регион у поставщика\n" : "Проверьте единственный вариант\n";
Ожидаемый результат
Уточните регион у поставщика

Ключи массива убирают повторения одного идентификатора. Даже единственный найденный вариант требовал бы проверки даты и договора интеграции: историческая таблица не доказывает намерение автора строки.

Для настроек аккаунта используйте DateTimeZone::listIdentifiers. Если протокол отдаёт только числовое смещение вместе с конкретной датой, сохраните это смещение для данного момента; не объявляйте его постоянным регионом.

Сравните: timezone_abbreviations_list, timezone_name_from_abbr, DateTimeZone::listIdentifiers.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_openПолучить объект часового пояса обычным вызовом функции и проверить отказ.

PHP 5.2+ · date

function timezone_open(string $timezone): DateTimeZone|false

Получить объект часового пояса обычным вызовом функции и проверить отказ.

Часть вызоваДоговор
timezoneПоддерживаемое имя IANA, смещение (+02:00) либо сокращение. Для настройки профиля предпочтителен регион.
РезультатОбъект DateTimeZone или false. В PHP 8.5 неверное имя также вызывает E_WARNING; неверный тип — TypeError, NUL-байт в строке — ValueError.
Отличие от newnew DateTimeZone при неверном имени выбрасывает исключение; процедурный вариант предупреждает и возвращает false. Не подменяйте один контракт другим.
Функция и объект работают вместе
  1. timezone_open("Asia/Tokyo")
  2. Получили DateTimeZone или false
  3. Проверенный объект передали дате

После знака = функция выполняется, и её результат попадает в переменную. false — специальное значение «не удалось», а не имя пояса. Процедурная запись не запрещает пользоваться объектами: она просто иначе создаёт тот же вид значения.

Процедурное создание пояса для отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$zone = timezone_open('Asia/Tokyo');
if ($zone === false) {
    throw new RuntimeException('Invalid report configuration');
}
$date = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo $date->setTimezone($zone)->format('H:i'), "\n";
Ожидаемый результат
21:00

В конфигурации пример использует заведомо существующее имя. Проверка false делает условие пригодным для чтения реальной настройки, но сама по себе не перехватывает предупреждение при ошибке.

Не передавать произвольный ввод в функциюСамостоятельный пример
<?php
declare(strict_types=1);

$submitted = 'Europe/NoSuchCity';
$allowed = timezone_identifiers_list();
if (!in_array($submitted, $allowed, true)) {
    echo "Такого региона нет в доступном списке\n";
} else {
    $zone = timezone_open($submitted);
    echo timezone_name_get($zone), "\n";
}
Ожидаемый результат
Такого региона нет в доступном списке

Для публичной формы проверка списка полезнее подавления предупреждений через @. Это намеренно более узкая политика: функция принимает смещения и сокращения, форма принимает региональные имена. При ошибке собственной конфигурации удобен конструктор с try/catch.

Не заменяйте ошибочный пояс на UTC молча, если это меняет время обещанной доставки. Покажите отказ выбора или явно согласованную запасную настройку.

Сравните: DateTimeZone::__construct, timezone_identifiers_list, timezone_name_get.

Время, часовые пояса и календарь. Практические задачи с датами.

Увидеть предупреждение и отказ процедурного открытияСамостоятельный пример
<?php
$warning = null;
set_error_handler(function (int $severity, string $message) use (&$warning): bool {
    if ($severity !== E_WARNING) { return false; }
    $warning = $message;
    return true;
}, E_WARNING);
try { $zone = timezone_open('Europe/NoSuchCity'); }
finally { restore_error_handler(); }
if ($warning !== null) { echo "Диагностика: PHP сообщил о неизвестном поясе\n"; }
if ($zone === false) { echo "Встреча не подтверждена: исправьте настройку региона\n"; }
Ожидаемый результат
Диагностика: PHP сообщил о неизвестном поясе
Встреча не подтверждена: исправьте настройку региона

Временный обработчик сохраняет именно предупреждение этого вызова. Finally снимает его, а проверка false запрещает передавать несуществующий объект в setTimezone. Это демонстрация контракта, не подавление диагностик всего приложения. В обычном вводе лучше проверить регион по разрешённому списку, как показано выше; конструктор new имеет иной путь отказа через исключение.

timezone_name_getПолучить имя пояса в процедурном коде, например перед сохранением настроек.

PHP 5.2+ · date

function timezone_name_get(DateTimeZone $object): string

Получить имя пояса в процедурном коде, например перед сохранением настроек.

Часть вызоваДоговор
objectИнициализированный DateTimeZone, созданный new или timezone_open(). false от неудачного открытия сюда передавать нельзя.
РезультатСтроковое описание пояса. Аналог $object->getName(), без определения геолокации и без гарантии перевода старого имени в каноническое.
ОшибкиНеверный тип — TypeError. Вызов не меняет объект и глобальный пояс приложения.
Путь даты в первом примере
  1. zone: Asia/Tokyo
  2. timezone_name_get(zone) → строка в settings[timezone]
  3. new DateTimeZone(settings[timezone]) →restored
  4. Встреча 12:00 UTC →21:00

Аргумент теперь находится в скобках функции, а не слева от стрелки. timezone_name_get($zone) и $zone->getName() выражают одну операцию; выбрать можно по принятому стилю проекта.

Сохранить имя пояса в настройке и использовать его сноваСамостоятельный пример
<?php
$zone = new DateTimeZone('Asia/Tokyo');
$settings = ['timezone' => timezone_name_get($zone)];
$restored = new DateTimeZone($settings['timezone']);
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'Настройка: ', $settings['timezone'], "\n";
echo 'Встреча: ', $meeting->setTimezone($restored)->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Настройка: Asia/Tokyo
Встреча: 15.01.2024 21:00 +09:00

Аргумент zone попадает в object функции. Возвращённая строка записывается под ключом timezone массива settings. Затем конструктор получает эту строку и создаёт restored для отображения встречи. Это полный путь объект→строка настройки→объект; массив здесь заменяет чтение сохранённой конфигурации, файл/БД не изменяются.

Увидеть летние последствия разных сохранённых настроекСамостоятельный пример
<?php
$summer = new DateTimeImmutable('2024-07-15T12:00:00Z');
foreach (['+01:00', 'Europe/Berlin'] as $setting) {
    $zone = new DateTimeZone($setting);
    echo 'Настройка ', timezone_name_get($zone), ': ', $summer->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Настройка +01:00: 15.07.2024 13:00 +01:00
Настройка Europe/Berlin: 15.07.2024 14:00 +02:00

Timezone_name_get возвращает описание выбранной модели. Обе настройки могли дать одинаковые часы зимой, но летом фиксированное смещение остаётся+01:00, а Берлин применяет+02:00. Поэтому строку региона нельзя заменить нынешним числом секунд при сохранении будущего расписания.

Сравните: DateTimeZone::getName, timezone_open, timezone_offset_get.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_offset_getВ процедурном отчёте прочитать смещение региона на дату события.

PHP 5.2+ · date

function timezone_offset_get(DateTimeZone $object, DateTimeInterface $datetime): int

В процедурном отчёте прочитать смещение региона на дату события.

Часть вызоваДоговор
objectПояс, чьё правило требуется, а не исходный пояс события.
datetimeDateTime/DateTimeImmutable: момент, на который рассчитывается правило.
РезультатЗнаковое целое число секунд относительно UTC. Никаких изменений объектов; неверный тип аргумента вызывает TypeError.
Путь даты в первом примере
  1. office: America/New_York; event: 15 января 12:00 UTC
  2. timezone_offset_get(office,event) → −18000
  3. setTimezone(office) →07:00−05:00
  4. Отчёт показывает местную встречу и правило

Порядок аргументов важен: первый сообщает «где», второй «когда». Для простого показа часов лучше сразу setTimezone()/format(); числовое смещение пригодится при разборе ошибки API или проверке режима расписания.

Добавить правило американского офиса в отчёт о встречеСамостоятельный пример
<?php
$office = new DateTimeZone('America/New_York');
$event = new DateTimeImmutable('2024-01-15T12:00:00Z');
$offsetSeconds = timezone_offset_get($office, $event);
echo 'Офис: ', $office->getName(), "\n";
echo 'Встреча в офисе: ', $event->setTimezone($office)->format('d.m.Y H:i P'), "\n";
echo 'Смещение от UTC: ', $offsetSeconds, " секунд\n";
Ожидаемый результат
Офис: America/New_York
Встреча в офисе: 15.01.2024 07:00 -05:00
Смещение от UTC: -18000 секунд

Функция получает office первым аргументом и event вторым: где искать правило и на какой момент. Возвращённые −18000 секунд попадают в offsetSeconds и затем в диагностическую строку. Для местных часов отдельно используется setTimezone. Ни office, ни event функция не меняет.

Увидеть сезонную разницу в полчасаСамостоятельный пример
<?php
$zone = new DateTimeZone('Australia/Lord_Howe');
$january = new DateTimeImmutable('2024-01-15T00:00:00Z');
$july = new DateTimeImmutable('2024-07-15T00:00:00Z');
echo 'Январская встреча: ', $january->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
echo 'Июльская встреча: ', $july->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
$differenceSeconds = timezone_offset_get($zone, $january) - timezone_offset_get($zone, $july);
echo 'Разница сезонных поправок: ', intdiv($differenceSeconds, 60), " минут\n";
Ожидаемый результат
Январская встреча: 15.01.2024 11:00 +11:00
Июльская встреча: 15.07.2024 10:30 +10:30
Разница сезонных поправок: 30 минут

Два вызова возвращают 39600 и 37800 секунд. Вычитание даёт 1800, intdiv(...,60) переводит эту целую разницу в 30 минут. Видимые часы 11:00 и 10:30 подтверждают её. Это сравнение правил двух дат, не продолжительность между январём и июлем; метка DST не обещает универсального сдвига ровно на час.

Сравните: DateTimeZone::getOffset, timezone_transitions_get, DateTimeImmutable::setTimezone.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_location_getПолучить справочную географию выбранного пояса для подписи в административном интерфейсе.

PHP 5.2+ · date

function timezone_location_get(DateTimeZone $object): array|false

Получить справочную географию выбранного пояса для подписи в административном интерфейсе.

Часть вызоваДоговор
objectDateTimeZone. Типы смещения и сокращения не содержат географической структуры.
РезультатМассив country_code, latitude, longitude, comments или false; поля означают страну, представительную точку и пояснение из базы, не реальное местоположение клиента.
Ошибки и ограниченияНеверный тип — TypeError. Для служебных зон возможна страна ??; непустой массив не гарантирует полезную геолокацию.
Подпись выбора, а не слежение за клиентом
  1. Пользователь выбрал Asia/Tokyo
  2. Справочник сообщает JP
  3. Подписываем настройку; адрес доставки запрашиваем отдельно

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

Подпись японского поясаСамостоятельный пример
<?php
declare(strict_types=1);

$location = timezone_location_get(new DateTimeZone('Asia/Tokyo'));
echo $location['country_code'], "\n";
Ожидаемый результат
JP

Регион заранее известен, поэтому пример читает ключ напрямую. При приёме произвольного допустимого DateTimeZone нужна проверка результата, как во втором примере.

Общая функция подписи для региона и смещенияСамостоятельный пример
<?php
declare(strict_types=1);

function countryLabel(DateTimeZone $zone): string {
    $location = timezone_location_get($zone);
    if ($location === false || $location['country_code'] === '??') {
        return 'Страна не определена';
    }
    return $location['country_code'];
}
echo countryLabel(new DateTimeZone('Asia/Tokyo')), "\n";
echo countryLabel(new DateTimeZone('+09:00')), "\n";
echo countryLabel(new DateTimeZone('UTC')), "\n";
Ожидаемый результат
JP
Страна не определена
Страна не определена

Функция возвращает строку для дальнейшего отображения. Оператор || прекращает вычисление после true, поэтому у false мы не пытаемся прочитать country_code. Карту границ по одной latitude/longitude построить нельзя.

Сравните: DateTimeZone::getLocation, timezone_identifiers_list, timezone_name_get.

Время, часовые пояса и календарь. Практические задачи с датами.

Вызов countryLabel(new DateTimeZone("Asia/Tokyo")) сначала создаёт объект и передаёт его в локальный параметр zone. Внутри timezone_location_get(zone) возвращает массив в location. У Tokyo проверка отказа ложна, поэтому return отдаёт строку JP в исходное выражение countryLabel(...), а внешний echo выводит её. Для +09:00 первым условием становится true: || не читает ключ у false, return отдаёт «Страна не определена». У UTC есть массив, но country_code равен ??, поэтому возвращается та же подпись по другой причине.

timezone_identifiers_listПолучить список разрешённых регионов для формы и серверной проверки.

PHP 5.2+ · date

function timezone_identifiers_list(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array

Получить список разрешённых регионов для формы и серверной проверки.

Часть вызоваДоговор
timezoneGroupALL по умолчанию; региональные флаги можно объединять |. ALL_WITH_BC включает совместимые устаревшие имена. PER_COUNTRY задаёт отдельную фильтрацию.
countryCodeДвухбуквенный прописной код страны только для точного режима PER_COUNTRY. Nullable с PHP 7.1, но отсутствие кода в этом режиме — ошибка.
Результат и ошибкиМассив строк. В PHP 8 неправильный код по длине вызывает ValueError; до PHP 8 ошибка давала false. Неизвестный двухбуквенный код может дать []. Неверные типы — TypeError.
Путь даты в первом примере
  1. PER_COUNTRY, JP → allowed=[Asia/Tokyo]
  2. submitted=Asia/Tokyo проходит строгий поиск
  3. new DateTimeZone(submitted) → zone
  4. Момент 12:00 UTC показан как 21:00 в филиале

Список из функции относится к базе текущего PHP. Не принимайте строку только потому, что JavaScript прислал её из выпадающего списка: запрос можно составить вручную. Одинаковый серверный набор используйте при показе и проверке.

Применить проверенный выбор японского филиалаСамостоятельный пример
<?php
$allowed = timezone_identifiers_list(DateTimeZone::PER_COUNTRY, 'JP');
$submitted = 'Asia/Tokyo';
if (!in_array($submitted, $allowed, true)) {
    echo "Нужен регион из списка выбранной страны\n";
} else {
    $zone = new DateTimeZone($submitted);
    $event = new DateTimeImmutable('2024-01-15T12:00:00Z');
    echo 'Сохранённая настройка: ', $submitted, "\n";
    echo 'Встреча в филиале: ', $event->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Сохранённая настройка: Asia/Tokyo
Встреча в филиале: 15.01.2024 21:00 +09:00

Timezone_identifiers_list вернул массив allowed. In_array с true проверил тип и точное значение submitted, после чего это имя использовано в конструкторе. В примере сохранение обозначено строкой результата, записи в БД нет. Для HTTP сначала проверяют строковый тип поля и право менять настройку; список в браузере сам этого не гарантирует.

Прочитать старую настройку и предложить новое имяСамостоятельный пример
<?php
$current = timezone_identifiers_list();
$compatible = timezone_identifiers_list(DateTimeZone::ALL_WITH_BC);
$stored = 'US/Eastern';
if (!in_array($stored, $current, true) && in_array($stored, $compatible, true)) {
    echo 'Старая совместимая настройка: ', $stored, "\n";
}
$proposed = 'America/New_York';
if (in_array($proposed, $current, true)) {
    $event = new DateTimeImmutable('2024-07-15T12:00:00Z');
    echo 'По старой настройке: ', $event->setTimezone(new DateTimeZone($stored))->format('d.m.Y H:i P'), "\n";
    echo 'По предложению ', $proposed, ': ', $event->setTimezone(new DateTimeZone($proposed))->format('d.m.Y H:i P'), "\n";
}
Ожидаемый результат
Старая совместимая настройка: US/Eastern
По старой настройке: 15.07.2024 08:00 -04:00
По предложению America/New_York: 15.07.2024 08:00 -04:00

ALL не предлагает US/Eastern новому пользователю, ALL_WITH_BC позволяет распознать старую запись. Здесь новое имя задано явно и проверено на одной известной дате. Совпадение этих часов не является доказательством всей исторической/будущей эквивалентности: миграцию определяют по договору приложения и нужным датам; функция не выполняет её автоматически.

Сравните: DateTimeZone::listIdentifiers, timezone_open, timezone_version_get.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_abbreviations_listРазобрать сокращение из импортированного журнала и увидеть варианты его значения.

PHP 5.2+ · date

function timezone_abbreviations_list(): array

Разобрать сокращение из импортированного журнала и увидеть варианты его значения.

Часть вызоваДоговор
АргументыНет.
РезультатМассив сокращений в нижнем регистре. Каждое значение — список записей dst (bool), offset (секунды int), timezone_id (string|null).
ОграниченияТаблица включает историю и неоднозначные сокращения; её данные предкомпилированы и не обновляются установкой новой PECL timezonedb. Список для формы строится через timezone_identifiers_list().
Путь даты в первом примере
  1. timezone_abbreviations_list() → table
  2. table[pst] → rows с несколькими вариантами
  3. Выбраны два конкретных региона обычного времени
  4. Вывод показывает имена и разные смещения

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

Поставщик прислал короткое сокращение вместо региона. Прежде чем импортировать время заказа, нужно увидеть неоднозначность. Функция возвращает вложенный массив; timezone_id —имя или null, offset —секунды, dst —логический признак. Ни первая запись, ни самое знакомое имя не доказывают намерение поставщика.

Показать два разных значения одного сокращенияСамостоятельный пример
<?php
$table = timezone_abbreviations_list();
$rows = $table['pst'];
$wanted = ['America/Los_Angeles', 'Asia/Manila'];
$selected = [];
foreach ($rows as $row) {
    if (in_array($row['timezone_id'], $wanted, true) && !$row['dst']) {
        $selected[$row['timezone_id']] = $row['offset'];
    }
}
foreach ($wanted as $region) {
    echo 'PST: ', $region, '; ', $selected[$region], " секунд от UTC\n";
}
Ожидаемый результат
PST: America/Los_Angeles; -28800 секунд от UTC
PST: Asia/Manila; 28800 секунд от UTC

Внешний массив table индексируется строчным сокращением, внутри rows —список вариантов. Выбираем два заранее названных региона и записи обычного времени, сохраняем offset под ключом имени. Второй foreach выводит конкретные пары в нужном порядке. Знак и величина различаются: одно сокращение не определяет момент импортированного события. Эта историческая таблица не заменяет getOffset для конкретной даты.

Увидеть, как неоднозначный PST меняет время заказаСамостоятельный пример
<?php
$input = '2024-01-15 10:00:00';
foreach (['America/Los_Angeles', 'Asia/Manila'] as $region) {
    $local = new DateTimeImmutable($input, new DateTimeZone($region));
    $utc = $local->setTimezone(new DateTimeZone('UTC'));
    echo 'Если источник ', $region, ': ', $utc->format('d.m.Y H:i'), " UTC\n";
}
echo "Импорт отложен: поставщик должен подтвердить регион\n";
Ожидаемый результат
Если источник America/Los_Angeles: 15.01.2024 18:00 UTC
Если источник Asia/Manila: 15.01.2024 02:00 UTC
Импорт отложен: поставщик должен подтвердить регион

После чтения таблицы первого примера проверяем два возможных смысла одной строки 10:00. Конструктор понимает локальные часы через явно выбранный регион; setTimezone переводит каждый момент в UTC. Получились 18:00 и 02:00, разница 16 часов. Мы не выбрали вариант автоматически и не сохранили заказ с догаданным временем.

Сравните: DateTimeZone::listAbbreviations, timezone_name_from_abbr, timezone_identifiers_list.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_name_from_abbrПолучить предположение о названии пояса из старого сокращения; подтвердить его до применения.

PHP 5.1.3+ · date

function timezone_name_from_abbr(string $abbr, int $utcOffset = -1, int $isDST = -1): string|false

Получить предположение о названии пояса из старого сокращения; подтвердить его до применения.

Часть вызоваДоговор
abbrСокращение. Если оно не найдено, поиск может продолжиться только по offset и isDST. Пустая строка тоже позволяет такой поиск.
utcOffsetСекунды от UTC. -1 означает первый вариант сокращения. Иначе ищется точное смещение; при отсутствии совпадения возможен первый вариант с иным смещением.
isDST-1 — не учитывать признак; 0 — обычное время, 1 — DST. Это целочисленный режим поиска, не требование добавить час.
РезультатПредположительное имя или false. Неверные типы — TypeError. Успех не доказывает, что получен регион посетителя или что offset был строго соблюдён.
Путь даты в первом примере
  1. EST,−18000,0 → эвристический поиск
  2. return → guess=America/New_York или false
  3. Предложение показывается источнику
  4. Отдельно подтверждённый регион →конкретный момент заказа

Название функции может создать ложное ощущение точного преобразования. Это эвристика для совместимости: множество регионов имеют одинаковое смещение. Для новой формы лучше сразу попросить IANA ID; если браузер подсказывает его, пользователь всё равно может выбрать другой.

Показать найденное имя как предложение для подтвержденияСамостоятельный пример
<?php
foreach (['EST', 'NoSuchAbbreviation'] as $abbr) {
    $guess = $abbr === 'EST' ? timezone_name_from_abbr($abbr, -18000, 0) : timezone_name_from_abbr($abbr);
    if ($guess === false) { echo $abbr, ": нужен ручной выбор региона\n"; continue; }
    echo $abbr, ': предложение ', $guess, "; подтвердите у источника\n";
}
$confirmed = 'America/New_York';
$order = new DateTimeImmutable('2024-01-15 10:00:00', new DateTimeZone($confirmed));
echo 'После отдельного подтверждения: ', $order->setTimezone(new DateTimeZone('UTC'))->format('d.m.Y H:i'), " UTC\n";
Ожидаемый результат
EST: предложение America/New_York; подтвердите у источника
NoSuchAbbreviation: нужен ручной выбор региона
После отдельного подтверждения: 15.01.2024 15:00 UTC

Для EST параметры задают −18000 секунд и обычное время 0; возвращённое имя лишь показывается в предложении. Неизвестное сокращение возвращает false. Confirmed задан отдельной строкой как уже полученное подтверждение, а не автоматически присвоен guess: только после него локальные 10:00 переводятся в 15:00 UTC.

Сравните: timezone_abbreviations_list, timezone_identifiers_list, DateTimeZone::getOffset.

Время, часовые пояса и календарь. Практические задачи с датами.

Проверить, почему найденное имя не подтверждает входное смещениеСамостоятельный пример
<?php
$claimedSeconds = 99999;
$guess = timezone_name_from_abbr('CET', $claimedSeconds, -1);
if ($guess === false) { throw new RuntimeException('No candidate'); }
$date = new DateTimeImmutable('2024-01-15T12:00:00Z');
$actualSeconds = (new DateTimeZone($guess))->getOffset($date);
echo 'Предложено: ', $guess, "\n";
echo 'Во входе: ', $claimedSeconds, '; на проверяемую дату: ', $actualSeconds, " секунд\n";
if ($actualSeconds !== $claimedSeconds) { echo "Импорт отклонён: заявленное смещение не совпадает\n"; }
Ожидаемый результат
Предложено: Europe/Berlin
Во входе: 99999; на проверяемую дату: 3600 секунд
Импорт отклонён: заявленное смещение не совпадает

При отсутствии точного offset функция может вернуть первый вариант сокращения. Поэтому необычные 99999 не обеспечили false. Отдельный getOffset проверил кандидата на конкретную дату и дал 3600; прикладное правило отказало в импорте. Даже совпадение секунд само по себе не доказывает уникальный регион человека.

timezone_transitions_getПрочитать историю правил региона в процедурном коде, например для диагностики расписания.

PHP 5.2+ · date

function timezone_transitions_get(DateTimeZone $object, int $timestampBegin = PHP_INT_MIN, int $timestampEnd = 2147483647): array|false

Прочитать историю правил региона в процедурном коде, например для диагностики расписания.

Часть вызоваДоговор
objectОбъект регионального пояса. Фиксированное смещение и сокращение возвращают false.
timestampBegin / timestampEndUnix timestamps в секундах. Задавайте ограниченный период, begin < end. Верхний default с PHP 8.1 — 2147483647, раньше PHP_INT_MAX.
РезультатСписок массивов ts, time, offset, isdst, abbr. Первая строка — состояние в begin, а не обязательно изменение. Строка time в PHP 8.2+ имеет формат ISO8601_EXPANDED.
Границы и ошибкиНеверный тип — TypeError. Точный end может попасть в результат в ветви POSIX-правил PHP 8.5; если нужен строгий интервал, фильтруйте и исключайте начальное состояние явно.
Путь даты в первом примере
  1. Europe/London и ограниченный 2024 год
  2. timezone_transitions_get → rows
  3. rows[0] → initial на 1 января
  4. Подпись показывает GMT/0 и смысл начального снимка

Это та же операция, что метод DateTimeZone::getTransitions, только объект передаётся первым аргументом. В карточке метода разобраны точные границы и устранение повторений. Здесь сначала получим снимок, затем проверим существенное изменение правил.

Объяснить, с каким правилом начался отчётный годСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/London');
$rows = timezone_transitions_get($zone, 1704067200, 1735689600);
if ($rows === false) { throw new RuntimeException('Regional transition table required'); }
$initial = $rows[0];
echo 'Начало отчёта: ', $initial['time'], "\n";
echo 'Тогда действовало: ', $initial['abbr'], '; ', $initial['offset'], " секунд от UTC\n";
echo "Это начальное состояние, а не сообщение о переводе часов\n";
Ожидаемый результат
Начало отчёта: 2024-01-01T00:00:00+00:00
Тогда действовало: GMT; 0 секунд от UTC
Это начальное состояние, а не сообщение о переводе часов

Первый аргумент задаёт регион, второй и третий —границы 2024 года в секундах Unix. Возвращённый rows сначала проверен на false, затем rows[0] даёт массив initial. Читаются момент, сокращение и секунды. Нулевая январская поправка не утверждает, что весь год Лондон совпадает с UTC.

Вывести изменения смещения, которые объясняют расписаниеСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/London');
$begin = 1704067200;
$end = 1735689600;
$rows = timezone_transitions_get($zone, $begin, $end);
$previousOffset = $rows[0]['offset'];
foreach (array_slice($rows, 1) as $row) {
    if ($row['ts'] >= $end) { continue; }
    if ($row['offset'] !== $previousOffset) {
        echo $row['time'], ': ', $previousOffset, ' → ', $row['offset'], " секунд\n";
    }
    $previousOffset = $row['offset'];
}
Ожидаемый результат
2024-03-31T01:00:00+00:00: 0 → 3600 секунд
2024-10-27T01:00:00+00:00: 3600 → 0 секунд

В этом самостоятельном примере заранее выбран регион с таблицей. PreviousOffset начинается из начального снимка; после каждой строки он обновляется. Выводятся реальные моменты и старые/новые значения, а не только количество строк. Переименование сокращения без изменения секунд здесь не считается переводом часов —это выбранное правило отчёта. В другом регионе число событий может отличаться.

Сравните: DateTimeZone::getTransitions, timezone_offset_get, timezone_version_get.

Время, часовые пояса и календарь. Практические задачи с датами.

timezone_version_getУзнать версию правил времени, чтобы расследовать разницу расчётов между серверами.

PHP 5.3+ · date

function timezone_version_get(): string

Узнать версию правил времени, чтобы расследовать разницу расчётов между серверами.

Часть вызоваДоговор
АргументыНет.
РезультатСтрока обычно вида YYYY.increment. Некоторые сборки Linux используют системную базу и возвращают 0.system.
ОбновлениеПравила обновляются вместе с PHP или через PECL timezonedb; у системной базы обновление зависит от пакетов ОС. Версия правил не равна версии PHP.
Граница проверкиНепустая версия не гарантирует актуальность будущих политических решений. Строка 0.system не означает отсутствие базы; тогда нужно проверить пакет tzdata ОС.
Путь даты в первом примере
  1. timezone_version_get() → строка version
  2. Контрольный момент и Europe/Berlin заданы явно
  3. format →местные 14:00
  4. Диагностика содержит реальную версию, регион и результат

Обычный вызов возвращает строку, которую полезно включить в техническую диагностику вместе с PHP_VERSION и идентификатором региона. Не отправляйте в журнал полный профиль клиента: для этой проблемы достаточно среды и обезличенного тестового момента.

Включить действительную версию базы в диагностику времениСамостоятельный пример
<?php
$version = timezone_version_get();
$zone = new DateTimeZone('Europe/Berlin');
$event = new DateTimeImmutable('2024-07-15T12:00:00Z');
echo 'База правил: ', $version, "\n";
echo 'Проверяемый регион: ', $zone->getName(), "\n";
echo 'Результат: ', $event->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
if ($version === '0.system') { echo "Для сравнения серверов нужна также версия пакета tzdata ОС\n"; }
Ожидаемый результат
База правил: 2026.3
Проверяемый регион: Europe/Berlin
Результат: 15.07.2024 14:00 +02:00

Timezone_version_get без аргументов возвращает строку текущей среды в version. Она действительно попала в вывод рядом с воспроизводимой датой и регионом. Здесь ожидаемый номер относится к учебной Windows-сборке; на другом PHP он закономерно отличается, а 0.system требует проверки пакета ОС. Само чтение номера не обновляет правила.

Использовать версию в реальном ключе списка регионовСамостоятельный пример
<?php
$key = 'timezone-options:' . timezone_version_get() . ':JP';
$cache = [];
$cache[$key] = timezone_identifiers_list(DateTimeZone::PER_COUNTRY, 'JP');
echo 'Ключ: ', $key, "\n";
echo 'Сохранённые варианты: ', implode(', ', $cache[$key]), "\n";
Ожидаемый результат
Ключ: timezone-options:2026.3:JP
Сохранённые варианты: Asia/Tokyo

Точка соединяет префикс, реальную версию и код страны. Под полученным ключом сохранён массив вариантов; второе чтение обращается к тому же ключу и выводит Asia/Tokyo. Это локальный массив для объяснения устройства кэша, не подключение внешнего сервиса. Для 0.system включите версию tzdata или идентификатор развёртывания, иначе обновление ОС не изменит такой ключ.

Сравните: timezone_identifiers_list, timezone_transitions_get, DateTimeZone::listAbbreviations.

Время, часовые пояса и календарь. Практические задачи с датами.

DateTimeZone::__serializeПонять, какие данные PHP сохраняет при сериализации объекта пояса; для настройки обычно достаточно имени.

PHP 8.2+ · date

public function DateTimeZone::__serialize(): array

Понять, какие данные PHP сохраняет при сериализации объекта пояса; для настройки обычно достаточно имени.

Часть вызоваДоговор
АргументыНет. PHP вызывает этот специальный метод из serialize($zone).
РезультатМассив состояния: timezone_type (1–3), timezone (строка), а также пользовательские свойства при наличии. Это внутренний договор восстановления, не JSON-ответ API.
Версия и ошибкиМетод появился в PHP 8.2. Неинициализированный объект вызывает ошибку; в современной ветке — DateObjectError. Формат не заменяет переносимый контракт данных приложения.
Путь даты в первом примере
  1. zone хранит Europe/Berlin
  2. __serialize() → state: тип 3 и имя
  3. Поля прочитаны по ключам
  4. Исходный zone даёт 13:00 для известного UTC-момента

Два подчёркивания обозначают специальный метод протокола PHP. Обычное приложение вызывает serialize, а здесь прямой вызов нужен только для исследования. Он возвращает массив; serialize() дополнительно кодирует его в строку.

Связать поля снимка с правилом показа встречиСамостоятельный пример
<?php
$zone = new DateTimeZone('Europe/Berlin');
$state = $zone->__serialize();
echo 'Сохранённый тип: ', $state['timezone_type'], " (регион)\n";
echo 'Сохранённое имя: ', $state['timezone'], "\n";
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'По текущим правилам: ', $meeting->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Сохранённый тип: 3 (регион)
Сохранённое имя: Europe/Berlin
По текущим правилам: 15.01.2024 13:00 +01:00

Вызов без аргументов возвращает массив в state. Ключи timezone_type и timezone обозначают вид и имя пояса. Вывод встречи отдельно применяет исходный zone: он остался рабочим. В state не скопирована вся история Берлина; при восстановлении имени правила будут взяты из базы той среды. Поэтому такой снимок не фиксирует навсегда нынешний прогноз будущих переходов.

Восстановить свой объект и показать встречу в его регионеСамостоятельный пример
<?php
$original = new DateTimeZone('Asia/Tokyo');
$bytes = serialize($original);
$copy = unserialize($bytes, ['allowed_classes' => [DateTimeZone::class]]);
if (!$copy instanceof DateTimeZone) { throw new RuntimeException('Wrong own snapshot'); }
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'Восстановленный регион: ', $copy->getName(), "\n";
echo 'Встреча: ', $meeting->setTimezone($copy)->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Восстановленный регион: Asia/Tokyo
Встреча: 15.01.2024 21:00 +09:00

Serialize автоматически получил массив __serialize и превратил его в строку bytes. Unserialize этой собственной строки создал отдельный объект, вызвал __unserialize и вернул результат в copy. После проверки типа copy передан в setTimezone. Сам hook не выводил 21:00: это результат format у даты. Allowed_classes ограничивает классы снимка, но не делает внешнюю сериализацию доверенной.

Собственные свойства подкласса тоже могут попасть в снимок: не храните в таком объекте токены и персональные данные без причины. Обычная настройка «пояс филиала» проще и прозрачнее через DateTimeZone::getName.

Сравните: DateTimeZone::__unserialize, DateTimeZone::getName.

Время, часовые пояса и календарь. Практические задачи с датами.

У этого метода нет отдельной страницы в рассматриваемом дереве PHP.net: контракт сверён с официальными исходниками и локальным PHP 8.5.11. Он учтён отдельно от семи документированных страниц методов.

DateTimeZone::__unserializeВосстановить внутренний объект из массива состояния, который ранее создал сам PHP.

PHP 8.2+ · date

public function DateTimeZone::__unserialize(array $data): void

Восстановить внутренний объект из массива состояния, который ранее создал сам PHP.

Часть вызоваДоговор
dataМассив состояния с целым timezone_type в диапазоне 1–3 и строкой timezone. Строка не должна содержать NUL; действительный пояс должен распознаваться.
Результатvoid: изменяется состояние получателя, новый объект этим методом не возвращается. PHP вызывает метод во время unserialize().
Ошибки и версияPHP 8.2+. Некорректная структура/неизвестный пояс — Error; неверный тип самого аргумента — TypeError. Не использовать как валидатор произвольного входного JSON.
Путь даты в первом примере
  1. state хранит собственное имя Asia/Tokyo
  2. receiver пока UTC →часы 12:00
  3. receiver->__unserialize(state) меняет receiver; void
  4. Следующий показ того же meeting даёт 21:00

Это поздняя техническая тема: для нового объекта предпочтителен конструктор. Прямой вызов ниже показывает механику протокола и разницу между возвратом значения и изменением объекта. Код не читает файл, сеть или внешнюю сериализованную строку.

Увидеть изменение получателя через результат показа датыСамостоятельный пример
<?php
$state = (new DateTimeZone('Asia/Tokyo'))->__serialize();
$receiver = new DateTimeZone('UTC');
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'До восстановления: ', $meeting->setTimezone($receiver)->format('H:i P'), "\n";
$receiver->__unserialize($state);
echo 'После восстановления: ', $meeting->setTimezone($receiver)->format('H:i P'), "\n";
Ожидаемый результат
До восстановления: 12:00 +00:00
После восстановления: 21:00 +09:00

State передан в локальный data метода. Возврат void ничего не даёт для присваивания: изменился сам receiver, и следующий setTimezone использовал уже его новые правила. Meeting остался тем же моментом 12:00 UTC. Это демонстрация внутреннего протокола; обычное переключение представления даты делает setTimezone(new DateTimeZone(...)).

Отказаться от повреждённого снимка и применить известную настройкуСамостоятельный пример
<?php
$receiver = new DateTimeZone('UTC');
try { $receiver->__unserialize(['timezone_type' => 3]); }
catch (Error $error) {
    echo "Снимок повреждён; прежний получатель отброшен\n";
    $receiver = new DateTimeZone('Asia/Tokyo');
}
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'По проверенной настройке филиала: ', $meeting->setTimezone($receiver)->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Снимок повреждён; прежний получатель отброшен
По проверенной настройке филиала: 15.01.2024 21:00 +09:00

В массиве не было timezone, поэтому восстановление дало Error. Catch создаёт новый объект из отдельно известной настройки филиала, затем он используется для встречи. Tokyo здесь явно задан как проверенная конфигурация; при её отсутствии нужно закончить отказом, а не молча угадывать регион или состояние повреждённого receiver.

Сравните: DateTimeZone::__serialize, DateTimeZone::__construct, DateTimeZone::__set_state.

Время, часовые пояса и календарь. Практические задачи с датами.

У этого метода нет отдельной страницы в рассматриваемом дереве PHP.net: контракт сверён с официальными исходниками и локальным PHP 8.5.11. Он учтён отдельно от семи документированных страниц методов.

DateTimeZone::__wakeupУзнать старый механизм восстановления, чтобы убрать его ручные вызовы из старого проекта.

Наследованный API; deprecated в PHP 8.5 · date

public function DateTimeZone::__wakeup(): void

Узнать старый механизм восстановления, чтобы убрать его ручные вызовы из старого проекта.

Часть вызоваДоговор
АргументыНет: исторически состояние читалось из восстановленных свойств объекта.
Результат и ошибкаvoid; при неподходящих свойствах — Error. Вызов у обычного нового объекта не является командой «обновить правила».
ВерсияВ PHP 8.5 конкретный встроенный метод помечен Deprecated и прямой вызов выдаёт E_DEPRECATED. Современный протокол использует __serialize/__unserialize с PHP 8.2.
Почему старый вызов не нужен
  1. Старый код вручную вызывает __wakeup
  2. Обычный объект не содержит ожидаемой старой раскладки свойств
  3. Удаляем ручной вызов: new для создания, новый протокол для локального снимка

Общая soft-deprecation магических __sleep/__wakeup и конкретное предупреждение встроенного DateTimeZone — разные уровни. Здесь речь именно о методе этого класса. Для первого знакомства с поясами он не нужен.

Увидеть отказ старого ручного вызова в PHP 8.5Самостоятельный пример
<?php
declare(strict_types=1);

$deprecated = false;
set_error_handler(static function (int $severity) use (&$deprecated): bool {
    if ($severity !== E_DEPRECATED) {
        return false;
    }
    $deprecated = true;
    return true;
});
try {
    $zone = new DateTimeZone('UTC');
    $zone->__wakeup();
} catch (Error $error) {
    echo "Ручной вызов не восстановил состояние\n";
} finally {
    restore_error_handler();
}
echo $deprecated ? "Устарело в PHP 8.5\n" : "Нет уведомления этой версии\n";
Ожидаемый результат
Ручной вызов не восстановил состояние
Устарело в PHP 8.5

Диагностический пример именно для PHP 8.5; в 8.2–8.4 последняя строка будет иной. Узкий временный обработчик только фиксирует E_DEPRECATED, затем снимается в finally. Это наблюдение, а не рекомендация подавлять устаревания в приложении.

Продолжить показ встреч через обычную сохранённую настройкуСамостоятельный пример
<?php
$oldObject = new DateTimeZone('Europe/Berlin');
$config = ['timezone' => $oldObject->getName()];
$newObject = new DateTimeZone($config['timezone']);
$meeting = new DateTimeImmutable('2024-07-15T12:00:00Z');
echo 'Настройка восстановлена: ', $newObject->getName(), "\n";
echo 'Встреча: ', $meeting->setTimezone($newObject)->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Настройка восстановлена: Europe/Berlin
Встреча: 15.07.2024 14:00 +02:00

Config содержит обычную строку. New создаёт рабочий объект сразу, поэтому ручной __wakeup не требуется ни до setTimezone, ни после него. Для собственного внутреннего serialized-хранилища PHP также сам применяет современные hooks. Первый старый опыт сохранён именно как наблюдение E_DEPRECATED и Error прямого вызова на PHP8.5, не как маршрут создания пояса.

Сравните: DateTimeZone::__unserialize, DateTimeZone::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

У этого метода нет отдельной страницы в рассматриваемом дереве PHP.net: контракт сверён с официальными исходниками и локальным PHP 8.5.11. Он учтён отдельно от семи документированных страниц методов.

DateTimeZone::__set_stateПонять восстановление объекта, которое появляется в PHP-коде от var_export().

Доступен в поддерживаемой базе PHP 8.2+ · date

public static function DateTimeZone::__set_state(array $array): DateTimeZone

Понять восстановление объекта, которое появляется в PHP-коде от var_export().

Часть вызоваДоговор
arrayЭкспортированные поля timezone_type и timezone. Должны представлять пригодное для восстановления состояние; неверная структура вызывает Error, тип аргумента — TypeError.
РезультатНовый объект DateTimeZone. Статический метод: получателя слева от -> нет. Отличается от __unserialize, который изменяет существующий объект.
Границаvar_export возвращает текст PHP-кода, не данные JSON. Не выполняйте eval() над внешним текстом; для конфигурации пояса обычно проще хранить имя.
Путь даты в первом примере
  1. zone=Asia/Tokyo
  2. var_export(zone,true) → строка text
  3. В text записан __set_state с массивом полей
  4. echo показывает весь текст; объект из текста пока не создавался

Слово static означает вызов у класса через ::. Сам var_export ничего не восстанавливает. Первый пример читает строку, второй вызывает метод с нашим собственным массивом; ни один не исполняет строку через eval.

Увидеть полный текст собственного экспорта поясаСамостоятельный пример
<?php
$zone = new DateTimeZone('Asia/Tokyo');
$text = var_export($zone, true);
echo $text, "\n";
Ожидаемый результат
\DateTimeZone::__set_state(array(
   'timezone_type' => 3,
   'timezone' => 'Asia/Tokyo',
))

True просит var_export вернуть строку в text. В ней видно полное имя класса с начальным обратным слешем, статический вызов через :: и массив двух полей. Echo лишь печатает этот текст, не выполняет его. Для пользователя сайта время встречи показывают format; такой экспорт нужен при чтении доверенного PHP-представления конфигурации.

Получить отдельный объект фабрики и обработать неполный массивСамостоятельный пример
<?php
$state = (new DateTimeZone('Asia/Tokyo'))->__serialize();
$zone = DateTimeZone::__set_state($state);
$meeting = new DateTimeImmutable('2024-01-15T12:00:00Z');
echo 'Через восстановленный пояс: ', $meeting->setTimezone($zone)->format('d.m.Y H:i P'), "\n";
try { DateTimeZone::__set_state([]); }
catch (Error $error) { echo "Пустой снимок отклонён; новый регион не назначен\n"; }
Ожидаемый результат
Через восстановленный пояс: 15.01.2024 21:00 +09:00
Пустой снимок отклонён; новый регион не назначен

Статическая фабрика получает собственный массив и возвращает новый объект в zone. Дата применяет его и возвращает местное представление 21:00. Вторая фабрика с [] не вернула объект: Error перевёл выполнение в catch. Не следует создавать эту внутреннюю схему из полей внешней формы; для обычного имени есть конструктор.

Сравните: var_export, DateTimeZone::__unserialize, DateTimeZone::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

У этого метода нет отдельной страницы в рассматриваемом дереве PHP.net: контракт сверён с официальными исходниками и локальным PHP 8.5.11. Он учтён отдельно от семи документированных страниц методов.

dateПолучить читаемую дату события в выбранном часовом поясе.

PHP 4+ · date

date(string $format, ?int $timestamp = null): string

Получить читаемую дату события в выбранном часовом поясе.

Часть вызоваДоговор
formatАлфавит DateTimeInterface::format: каждая распознанная буква заменяется полем; прочие знаки выводятся буквально, \ экранирует следующую букву.
timestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0.
ВозвратСтрока в текущем default timezone PHP. u всегда 000000, v всегда 000 для целой секунды. Пустой format даёт пустую строку.
Путь даты в первом примере
  1. receivedAt=1704067200; format=d.m.Y H:i
  2. date(format,receivedAt) → label
  3. label=01.01.2024 03:00
  4. echo строит подпись заказа; timestamp прежний

В первом аргументе d — день с нулём, m — месяц, Y — полный год; во втором передаётся готовый Unix timestamp. H:i — часы/минуты: m в этом языке никогда не означает минуты. Функция возвращает строку, а echo отдельно печатает её. полная таблица формата вывода охватывает каждый символ, включая X/x с 8.2, p с 8.0 и различие ISO-года o/календарного Y.

Подготовить подпись события из сохранённой Unix-секундыСамостоятельный пример
<?php
date_default_timezone_set('Europe/Moscow');
$receivedAt = 1704067200;
$label = date('d.m.Y H:i', $receivedAt);
echo 'Заказ BR41 получен: ', $label, ' по Москве', "\n";
echo 'Сохранённая метка: ', $receivedAt, " секунд\n";
Ожидаемый результат
Заказ BR41 получен: 01.01.2024 03:00 по Москве
Сохранённая метка: 1704067200 секунд

Формат и receivedAt передаются двум параметрам функции. Return —готовая строка в label; после этого echo добавляет номер заказа. Число секунд не меняется. Date использует общий default Europe/Moscow. Один и тот же момент 00:00 UTC здесь показан как 03:00; функция не прибавляла часы к сохранённой метке.

Целый timestamp не хранит микросекундыСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
echo date('Y-m-d\TH:i:s.uP', 1704067200), "\n";
echo date('o-W', 1609459200), '/', date('Y', 1609459200), "\n";
Ожидаемый результат
2024-01-01T00:00:00.000000+00:00
2020-53/2021

Обратный слеш делает T буквальной. u даёт шесть нулей: дроби в int нет. 1 января 2021 относится к ISO-неделе 53 2020 года; для группировки по неделям нельзя брать Y-W.

Форматирование не переводит английские названия по setlocale и не придаёт timestamp отдельную зону. Для многих пользователей удобнее DateTimeImmutable с явной DateTimeZone; для перевода названий — IntlDateFormatter с собственным языком и другой грамматикой формата. DATE_* — строки шаблона, а не преобразование зоны. Буквальная надпись GMT безопасна только после действительного вывода в UTC; DATE_RFC7231 deprecated с PHP 8.5. date зависит от общего default: библиотека не должна незаметно менять его для всего приложения. Неверные типы аргументов дают TypeError; неверная конфигурация зоны может выдать warning, поэтому настройте окружение явно.

Сравните: gmdate, DateTimeInterface::format, time.

Время, часовые пояса и календарь. Практические задачи с датами.

gmdateПолучить UTC-подпись для внешнего протокола.

PHP 4+ · date

gmdate(string $format, ?int $timestamp = null): string

Получить UTC-подпись для внешнего протокола.

Часть вызоваДоговор
formatАлфавит DateTimeInterface::format: каждая распознанная буква заменяется полем; прочие знаки выводятся буквально, \ экранирует следующую букву.
timestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0.
ВозвратСтрока в UTC. u всегда 000000, v всегда 000 для целой секунды. Пустой format даёт пустую строку.
Путь даты в первом примере
  1. receivedAt=1704067200; format=d.m.Y H:i
  2. gmdate(format,receivedAt) → label
  3. label=01.01.2024 00:00
  4. echo строит подпись заказа; timestamp прежний

В первом аргументе d — день с нулём, m — месяц, Y — полный год; во втором передаётся готовый Unix timestamp. H:i — часы/минуты: m в этом языке никогда не означает минуты. Функция возвращает строку, а echo отдельно печатает её. полная таблица формата вывода охватывает каждый символ, включая X/x с 8.2, p с 8.0 и различие ISO-года o/календарного Y.

Подготовить подпись события из сохранённой Unix-секундыСамостоятельный пример
<?php
date_default_timezone_set('Europe/Moscow');
$receivedAt = 1704067200;
$label = gmdate('d.m.Y H:i', $receivedAt);
echo 'Заказ BR41 получен: ', $label, ' UTC', "\n";
echo 'Сохранённая метка: ', $receivedAt, " секунд\n";
Ожидаемый результат
Заказ BR41 получен: 01.01.2024 00:00 UTC
Сохранённая метка: 1704067200 секунд

Формат и receivedAt передаются двум параметрам функции. Return —готовая строка в label; после этого echo добавляет номер заказа. Число секунд не меняется. Gmdate использует UTC, хотя общая настройка скрипта Moscow. Оно подходит для единообразного журнала.

Целый timestamp не хранит микросекундыСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
echo gmdate('Y-m-d\TH:i:s.uP', 1704067200), "\n";
echo gmdate('o-W', 1609459200), '/', gmdate('Y', 1609459200), "\n";
Ожидаемый результат
2024-01-01T00:00:00.000000+00:00
2020-53/2021

Обратный слеш делает T буквальной. u даёт шесть нулей: дроби в int нет. 1 января 2021 относится к ISO-неделе 53 2020 года; для группировки по неделям нельзя брать Y-W.

Форматирование не переводит английские названия по setlocale и не придаёт timestamp отдельную зону. Для многих пользователей удобнее DateTimeImmutable с явной DateTimeZone; для перевода названий — IntlDateFormatter с собственным языком и другой грамматикой формата. DATE_* — строки шаблона, а не преобразование зоны. Буквальная надпись GMT безопасна только после действительного вывода в UTC; DATE_RFC7231 deprecated с PHP 8.5. gmdate не «вычитает три часа» из timestamp: меняется отображение. Неверные типы аргументов дают TypeError; неверная конфигурация зоны может выдать warning, поэтому настройте окружение явно.

Сравните: date, DateTimeInterface::format, time.

Время, часовые пояса и календарь. Практические задачи с датами.

mktimeПостроить момент отгрузки из отдельных календарных полей местного времени.

PHP 4+ · date

mktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int|false

Построить момент отгрузки из отдельных календарных полей местного времени.

Часть вызоваДоговор
hourОбязательный int. Час относительно начала заданного дня; -1 означает час до полуночи,24 — следующий день.
minute = nullМинуты относительно часа; выход за 0…59 нормализуется в соседние часы. null — текущая минута.
second = nullСекунды относительно минуты; выход за 0…59 нормализуется. null — текущая секунда.
month = nullНомер месяца 1…12;0 — декабрь прошлого года,13 — январь следующего. null — текущий месяц.
day = nullДень месяца;0 — последний день предыдущего месяца,-1 — предпоследний, лишние дни переходят в следующий. null — текущий день.
year = nullПолный год предпочтителен.0…69 преобразуются в 2000…2069,70…100 — в 1970…2000. null — текущий год.
Возврат / отказint Unix-seconds; E_WARNING+false если результат не помещается в PHP int. Неверные типы дают TypeError; вызов без hour — ArgumentCountError. С PHP 8.0 hour обязателен, остальные поля nullable.
Значения nullБерутся из текущих локальных полей default timezone.
Путь даты в первом примере
  1. 15 июня 2024,09:00 —входные календарные поля
  2. mktime(9,0,0,6,15,2024) → departure
  3. Проверка ===false перед использованием
  4. gmdate и date дают две подписи одного момента

Шесть полей записывают в порядке час, минута, секунда, месяц, день, год. Чтобы дата не зависела от момента запуска, передайте все шесть. mktime толкует поля в default timezone; это календарное локальное время, а результат — общий Unix timestamp.

Передать момент отгрузки в UTC и показать местную подписьСамостоятельный пример
<?php
date_default_timezone_set('Europe/Moscow');
$departure = mktime(9, 0, 0, 6, 15, 2024);
if ($departure === false) { throw new RuntimeException('Departure outside integer range'); }
echo 'Передать партнёру: ', gmdate('Y-m-d H:i', $departure), " UTC\n";
echo 'В московском складе: ', date('d.m.Y H:i P', $departure), "\n";
Ожидаемый результат
Передать партнёру: 2024-06-15 06:00 UTC
В московском складе: 15.06.2024 09:00 +03:00

Шесть аргументов соответствуют часам 9, минутам 0, секундам 0, месяцу 6, дню 15, году 2024. Возвращённый int сохраняется в departure и проверяется на false до форматирования. Mktime читает 09:00 в текущей московской зоне; UTC-партнёр получает 06:00. Две подписи показывают один момент, а не два разных времени отправки.

Рассчитать конец февраля и распознать непригодность для валидацииСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$last = mktime(0, 0, 0, 3, 0, 2024);
$overflow = mktime(0, 0, 0, 2, 31, 2024);
echo 'Последний день февраля: ', gmdate('d.m.Y', $last), "\n";
echo 'Результат поля «31 февраля»: ', gmdate('d.m.Y', $overflow), "\n";
if (!checkdate(2, 31, 2024)) { echo "Ввод клиента отклонён до сохранения\n"; }
Ожидаемый результат
Последний день февраля: 29.02.2024
Результат поля «31 февраля»: 02.03.2024
Ввод клиента отклонён до сохранения

Day=0 у марта означает последний день предыдущего месяца —это осознанный расчёт 29 февраля. Во втором вызове нормализация 31 февраля даёт 2 марта. Возврат timestamp означает, что расчёт выполнен, а не что клиент выбрал существующий день. Checkdate решает отдельную задачу и запрещает сохранить неверный ввод.

Граница результата определяется PHP int: на 32-bit около 1901…2038, на 64-bit существенно шире; старое «32-bit наиболее обычно» не описание сегодняшней Windows x64. Перед форматированием проверяйте ===false, иначе false может превратиться в другую дату. Один календарный день через mktime может отличаться от 86400 секунд из-за DST. Неоднозначный или отсутствующий локальный час требует отдельной политики; здесь специально используется однозначная Москва 2024. Для объектов с зоной и календарной арифметики используйте DateTimeImmutable. Попробуйте month=13 и day=1: получится январь следующего года, а не ошибка.

Сравните: checkdate, gmmktime, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

gmmktimeПостроить момент отгрузки из отдельных календарных полей UTC.

PHP 4+ · date

gmmktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int|false

Построить момент отгрузки из отдельных календарных полей UTC.

Часть вызоваДоговор
hourОбязательный int. Час относительно начала заданного дня; -1 означает час до полуночи,24 — следующий день.
minute = nullМинуты относительно часа; выход за 0…59 нормализуется в соседние часы. null — текущая минута.
second = nullСекунды относительно минуты; выход за 0…59 нормализуется. null — текущая секунда.
month = nullНомер месяца 1…12;0 — декабрь прошлого года,13 — январь следующего. null — текущий месяц.
day = nullДень месяца;0 — последний день предыдущего месяца,-1 — предпоследний, лишние дни переходят в следующий. null — текущий день.
year = nullПолный год предпочтителен.0…69 преобразуются в 2000…2069,70…100 — в 1970…2000. null — текущий год.
Возврат / отказint Unix-seconds; E_WARNING+false если результат не помещается в PHP int. Неверные типы дают TypeError; вызов без hour — ArgumentCountError. С PHP 8.0 hour обязателен, остальные поля nullable.
Значения nullБерутся из текущих UTC-полей.
Путь даты в первом примере
  1. 15 июня 2024,09:00 —входные календарные поля
  2. gmmktime(9,0,0,6,15,2024) → departure
  3. Проверка ===false перед использованием
  4. gmdate и date дают две подписи одного момента

Шесть полей записывают в порядке час, минута, секунда, месяц, день, год. Чтобы дата не зависела от момента запуска, передайте все шесть. gmmktime толкует их в UTC, даже если default timezone другой.

Передать момент отгрузки в UTC и показать местную подписьСамостоятельный пример
<?php
date_default_timezone_set('Europe/Moscow');
$departure = gmmktime(9, 0, 0, 6, 15, 2024);
if ($departure === false) { throw new RuntimeException('Departure outside integer range'); }
echo 'Передать партнёру: ', gmdate('Y-m-d H:i', $departure), " UTC\n";
echo 'В московском складе: ', date('d.m.Y H:i P', $departure), "\n";
Ожидаемый результат
Передать партнёру: 2024-06-15 09:00 UTC
В московском складе: 15.06.2024 12:00 +03:00

Шесть аргументов соответствуют часам 9, минутам 0, секундам 0, месяцу 6, дню 15, году 2024. Возвращённый int сохраняется в departure и проверяется на false до форматирования. Gmmktime читает поля как 09:00 UTC независимо от Moscow; местный склад видит 12:00. Две подписи показывают один момент, а не два разных времени отправки.

Рассчитать конец февраля и распознать непригодность для валидацииСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$last = gmmktime(0, 0, 0, 3, 0, 2024);
$overflow = gmmktime(0, 0, 0, 2, 31, 2024);
echo 'Последний день февраля: ', gmdate('d.m.Y', $last), "\n";
echo 'Результат поля «31 февраля»: ', gmdate('d.m.Y', $overflow), "\n";
if (!checkdate(2, 31, 2024)) { echo "Ввод клиента отклонён до сохранения\n"; }
Ожидаемый результат
Последний день февраля: 29.02.2024
Результат поля «31 февраля»: 02.03.2024
Ввод клиента отклонён до сохранения

Day=0 у марта означает последний день предыдущего месяца —это осознанный расчёт 29 февраля. Во втором вызове нормализация 31 февраля даёт 2 марта. Возврат timestamp означает, что расчёт выполнен, а не что клиент выбрал существующий день. Checkdate решает отдельную задачу и запрещает сохранить неверный ввод.

Граница результата определяется PHP int: на 32-bit около 1901…2038, на 64-bit существенно шире; старое «32-bit наиболее обычно» не описание сегодняшней Windows x64. Перед форматированием проверяйте ===false, иначе false может превратиться в другую дату. Историческая PHP-страница говорит, что gmmktime внутри зависит от локального mktime. В PHP 8.5.11 общий helper имеет отдельную GMT-ветвь timelib без преобразования через локальную зону: современный договор — UTC. Для объектов с зоной и календарной арифметики используйте DateTimeImmutable. Попробуйте month=13 и day=1: получится январь следующего года, а не ошибка.

Сравните: checkdate, mktime, DateTimeImmutable::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

strtotimeПрочитать текстовую настройку даты или относительный срок от явно заданной базы.

PHP 4+ · date

strtotime(string $datetime, ?int $baseTimestamp = null): int|false

Прочитать текстовую настройку даты или относительный срок от явно заданной базы.

Часть вызоваДоговор
datetimeПоддерживаемая английская запись даты/времени/относительного изменения. При отсутствии явно указанной зоны действует default timezone.
baseTimestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0. База относительных изменений и недостающих полей.
Возврат / ошибкиint либо false при ошибке разбора. Неверный тип аргумента: TypeError; неверная конфигурация зоны может дать warning.
Путь даты в первом примере
  1. Строка со всеми полями и UTC → strtotime
  2. return int →at либо false
  3. Успех →gmdate→подпись встречи
  4. Ошибка →сообщение; нулевой timestamp отдельно допустим

Первый аргумент — текст по поддерживаемой английской грамматике. Это не произвольный человеческий язык и не ISO-only валидатор. "UTC" в первом примере явно задаёт зону; gmdate выводит полученный момент в той же зоне. Второй аргумент, когда нужен, — int timestamp, а не строка даты.

Прочитать настройку встречи и обработать нераспознанный текстСамостоятельный пример
<?php
foreach (['2024-06-15 10:30:00 UTC', 'not a date'] as $setting) {
    $at = strtotime($setting);
    if ($at === false) { echo 'Настройка отклонена: ', $setting, "\n"; continue; }
    echo 'Встреча назначена: ', gmdate('d.m.Y H:i', $at), " UTC\n";
}
$epoch = strtotime('1970-01-01 00:00:00 UTC');
if ($epoch !== false) { echo 'Допустимая нулевая метка: ', $epoch, '; ', gmdate('d.m.Y', $epoch), "\n"; }
Ожидаемый результат
Встреча назначена: 15.06.2024 10:30 UTC
Настройка отклонена: not a date
Допустимая нулевая метка: 0; 01.01.1970

Строка setting становится параметром datetime. Strtotime возвращает int или false в at; continue останавливает только текущую итерацию после отказа. Успешное число получает gmdate и возвращает видимую подпись. Третий вызов возвращает законный 0, поэтому нужна строгая проверка!==false, а не if(epoch).

Прибавить месяц или перейти к его первому днюСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$base = gmmktime(12, 0, 0, 1, 31, 2024);
echo gmdate('Y-m-d H:i', strtotime('+1 month', $base)), "\n";
echo gmdate('Y-m-d H:i', strtotime('first day of next month', $base)), "\n";
Ожидаемый результат
2024-03-02 12:00
2024-02-01 12:00

+1 month сохраняет day31, затем нормализует несуществующее 31 февраля. Формулировка first day явно меняет требование: она не означает «та же дата следующего месяца». Время 12:00 унаследовано от base.

Нормализация не доказывает корректность поляСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$value = '2024-02-31';
$parsed = strtotime($value);
echo date('Y-m-d', $parsed), "\n";
echo checkdate(2, 31, 2024) ? 'Допустимо' : 'Введите существующую дату';
echo "\n";
Ожидаемый результат
2024-03-02
Введите существующую дату

Парсер принял нормализуемую комбинацию, а требование формы отклонило её. Для строгой строки заданного формата нужна проверка parse warnings и обратного форматирования.

Отсутствующие поля могут наследоваться от базы: February на 31 мая наследует day31 и уходит в март. Пишите 1 February или first day of February, если нужен первый день. Не все слова сбрасывают время одинаково; точная английская грамматика — в разделе форматов этой главы. 00…69 в двухзначном годе означают 2000…2069,70…99 —1970…1999. На 32-bit есть ограничение int примерно 1901…2038;64-bit тоже конечен, не называйте его буквально бесконечным. Явная зона в строке и default zone при интерпретации базы должны быть согласованы. strtotime возвращает момент без сохранения исходной зоны; для дальнейшей календарной арифметики предпочтительнее DateTimeImmutable, а для контролируемого ввода createFromFormat. На PHP 8.5.11 NUL может быть проигнорирован парсером: не переносите сюда обещание ValueError от createFromFormat. Для внешнего договора отклоняйте управляющие символы и используйте строгий фиксированный формат.

Сравните: date_parse, date_parse_from_format, DateTimeImmutable::createFromFormat, mktime.

Время, часовые пояса и календарь. Практические задачи с датами.

idateПолучить одно числовое поле календаря для условия или группировки.

PHP 5.0+; N/o PHP 8.2+ · date

idate(string $format, ?int $timestamp = null): int|false

Получить одно числовое поле календаря для условия или группировки.

Часть вызоваДоговор
formatРовно один символ из полной таблицы ниже; это свой ограниченный алфавит, не все буквы date().
timestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0.
Возврат / отказint без ведущих нулей либо false с предупреждением при неизвестном символе. Неверный тип — TypeError; неверная длина формата — E_WARNING+false.
Путь даты в первом примере
  1. at=1 января 2024 UTC
  2. idate(m,at) →month=1
  3. intdiv(month−1,3)+1 →quarter=1
  4. Год и квартал →reports/2024/Q1

В отличие от date, idate принимает ровно один поддерживаемый символ и возвращает int. Номер месяца 1 получается числом 1, без нуля. Для готовой подписи "01" нужен date("m", ...), а для числовой ветки условия удобно idate("m", ...).

СимволЦелочисленное поле
BSwatch beats 0…999
dДень месяца 1…31
h / HЧас 1…12 / 0…23
i / sМинуты / секунды 0…59
IDST:1 действует,0 нет
LВисокосный год:1/0
mМесяц 1…12
NISO-день 1…7, понедельник 1; PHP 8.2+
oГод ISO-недели; PHP 8.2+
tДней в месяце 28…31
UЦелая Unix-секунда
wДень недели 0…6, воскресенье 0
WISO-неделя 1…53
y / YДвузначная часть года / полный год; int не сохраняет ведущие нули
zПорядковый день года 0…365
ZUTC-offset в секундах, включая отрицательный
Выбрать квартальную папку по числовому месяцуСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$at = 1704067200;
$month = idate('m', $at);
if ($month === false) { throw new RuntimeException('Month unavailable'); }
$quarter = intdiv($month - 1, 3) + 1;
$folder = 'reports/' . idate('Y', $at) . '/Q' . $quarter;
echo 'Числовой месяц: ', $month, '; для подписи: ', date('m', $at), "\n";
echo 'Папка отчёта: ', $folder, "\n";
echo 'Дней в этом месяце: ', idate('t', $at), "\n";
Ожидаемый результат
Числовой месяц: 1; для подписи: 01
Папка отчёта: reports/2024/Q1
Дней в этом месяце: 31

Idate(m,at) возвращает число 1 в month. Формула сначала переводит месяцы 1…12 в 0…11, целочисленно делит на 3 и возвращает кварталы 1…4. Точка соединяет год и квартал в строку folder; это имя назначения, папка в примере не создаётся. Date(m) даёт строку 01 для человека. Каждому idate передан один символ, не полноценный шаблон даты.

Выбрать файл недели на границе календарного годаСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$at = 1609459200;
$filename = sprintf('weekly-%04d-W%02d.csv', idate('o', $at), idate('W', $at));
echo 'Дата события: ', date('d.m.Y', $at), "\n";
echo 'Файл недели: ', $filename, "\n";
echo 'Позиция дня ISO: ', idate('N', $at), " (пятница)\n";
Ожидаемый результат
Дата события: 01.01.2021
Файл недели: weekly-2020-W53.csv
Позиция дня ISO: 5 (пятница)

Idate(o) возвращает 2020 —год ISO-недели, idate(W) возвращает 53. Sprintf добавляет нужные нули и собирает имя файла, сам файл здесь не записывается. Для 1 января календарный Y равен 2021, но подстановка его в имя недели разнесла бы одну ISO-неделю по разным годам. O и N доступны с PHP8.2.

Не передавайте "Y-m-d": это не форматировщик строки. Неподдерживаемый символ или длина не 1 дают E_WARNING+false в PHP 8.5.11. Строго отличайте false от 0: ноль законен для секунд/смещения/yday и ряда других полей. Все значения вычисляются в default timezone; для объектной зоны используйте форматирование объекта. Важная граница реализации PHP 8.5.11: idate использует C int; U может обрезать timestamp за пределами 32 бит даже при 64-битном PHP, а значение -1 трактуется как отказ. Для timestamp сохраняйте исходное число/time/getTimestamp вместо idate(U). В этой реализации также принимаются j/n/g/G как числовые aliases d/m/h/H; официальная таблица их не перечисляет, поэтому для переносимого договора используйте документированные символы.

Сравните: date, getdate, localtime.

Время, часовые пояса и календарь. Практические задачи с датами.

getdateПолучить календарные поля события для отчёта или простой ветки расписания.

PHP 4+ · date

getdate(?int $timestamp = null): array

Получить календарные поля события для отчёта или простой ветки расписания.

Часть вызоваДоговор
timestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0.
ВозвратМассив 11 полей в default timezone. Поля 0/seconds/minutes/hours/mday/wday/mon/year/yday — int, weekday/month — string.
ОшибкиНеверный тип — TypeError; корректная конфигурация зоны обязательна.
Путь даты в первом примере
  1. getdate(1704067200) →parts в UTC
  2. year=2024,mon=1,mday=1 →reportKey
  3. yday=0 →первый день года
  4. Подпись отчёта и исходный момент

getdate возвращает массив с понятными ключами; ["mon"] читает месяц, ["year"] — полный год. Это поля в default timezone, а не UTC автоматически. Строковые weekday/month остаются английскими и не зависят от перевода интерфейса.

КлючСмысл
seconds / minutes / hoursСекунда 0…59, минута 0…59, час 0…23.
mday / mon / yearДень 1…31, месяц 1…12, полный год.
wday / ydayВоскресенье 0…суббота 6; день года 0…365.
weekday / monthАнглийские названия Sunday…Saturday и January…December.
0 (целый ключ)Исходный int Unix timestamp; диапазон зависит от PHP int, не ограничен 2038 на 64-bit.
Получить поля события и собрать ключ дневного отчётаСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$parts = getdate(1704067200);
$reportKey = sprintf('%04d-%02d-%02d', $parts['year'], $parts['mon'], $parts['mday']);
echo 'Отчёт за ', $reportKey, "\n";
echo 'Имя дня в ответе PHP: ', $parts['weekday'], "\n";
echo 'Номер дня с начала года: ', $parts['yday'] + 1, "\n";
echo 'Исходный момент: ', $parts[0], " секунд\n";
Ожидаемый результат
Отчёт за 2024-01-01
Имя дня в ответе PHP: Monday
Номер дня с начала года: 1
Исходный момент: 1704067200 секунд

Возвращённый массив сохранён в parts. Year/mon/mday формируют дату отчёта; yday=0 превращается в человеческий порядковый номер 1. Ключ 0 хранит исходный timestamp. Monday —английское значение поля, не выбранный язык интерфейса. Month уже начинается с 1; прибавлять 1 к mon, как у localtime, нельзя.

Выбрать выходной день без сравнения переводовСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
function dayKind(int $at): string
{
    $p = getdate($at);
    return in_array($p['wday'], [0, 6], true) ? 'Выходной' : 'Будний';
}
echo dayKind(gmmktime(12, 0, 0, 6, 15, 2024)), "\n";
echo dayKind(gmmktime(12, 0, 0, 6, 17, 2024)), "\n";
Ожидаемый результат
Выходной
Будний

Это учебное правило суббота/воскресенье, не производственный календарь праздников. Проверка по номеру не зависит от языка подписи.

Сравните: idate, date, localtime.

Время, часовые пояса и календарь. Практические задачи с датами.

Во втором примере dayKind получает timestamp в локальный at, getdate(at) возвращает поля в p, затем return отдаёт «Выходной» или «Будний» в конкретный внешний вызов echo dayKind(...). Вызов для 15 июня 2024 выбирает субботу 6, для 17 июня —понедельник 1. Это простое правило двух выходных, а не календарь праздников предприятия.

localtimeРазобрать timestamp в структуру tm для совместимости со старым интерфейсом.

PHP 4+ · date

localtime(?int $timestamp = null, bool $associative = false): array

Разобрать timestamp в структуру tm для совместимости со старым интерфейсом.

Часть вызоваДоговор
timestampЦелое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0.
associativefalse — числовые индексы 0…8; true — ключи tm_*.
Возврат / ошибкиМассив 9 полей в default timezone. TypeError для неверного типа; зона должна быть корректна.
Путь даты в первом примере
  1. localtime(at,true) →tm с именованными полями
  2. tm_year124+1900 →2024
  3. tm_mon0+1 →1
  4. sprintf →label=01.01.2024

localtime сохраняет соглашения C-структуры tm. Месяц начинается с 0, год хранится как расстояние от 1900. Это не те же числа, что у getdate. Второй аргумент true даёт именованные поля; без него возвращается список в порядке таблицы.

Индекс / имяСмысл
0 tm_secСекунды 0…59
1 tm_minМинуты 0…59
2 tm_hourЧас 0…23
3 tm_mdayДень месяца 1…31
4 tm_monМесяц 0…11
5 tm_yearГод минус 1900
6 tm_wdayВоскресенье 0…суббота 6
7 tm_ydayДень года 0…365
8 tm_isdstПоложительное — DST,0 — нет, отрицательное — неизвестно
Преобразовать старую структуру tm в понятную дату событияСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$tm = localtime(1704067200, true);
echo 'Исходные tm_year/tm_mon: ', $tm['tm_year'], '/', $tm['tm_mon'], "\n";
$label = sprintf('%02d.%02d.%04d', $tm['tm_mday'], $tm['tm_mon'] + 1, $tm['tm_year'] + 1900);
echo 'Дата события: ', $label, "\n";
Ожидаемый результат
Исходные tm_year/tm_mon: 124/0
Дата события: 01.01.2024

Второй аргумент true выбирает именованные ключи. Tm_year хранит 2024−1900=124, tm_mon хранит январь как 0. После явно показанных поправок sprintf возвращает label. Эти поправки нужны именно структуре tm; для getdate они были бы ошибкой. Следующий пример сохраняет документированный порядок числовых индексов.

Перевести список старого интерфейса в подписьСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$tm = localtime(gmmktime(9, 5, 0, 6, 15, 2024));
echo sprintf('%04d-%02d-%02d %02d:%02d', $tm[5] + 1900, $tm[4] + 1, $tm[3], $tm[2], $tm[1]), "\n";
Ожидаемый результат
2024-06-15 09:05

Индексы взяты из документированного порядка. Если внешний интерфейс не требует tm, date или объектная format проще и уменьшают риск перепутать поля.

Сравните: getdate, date, mktime.

Время, часовые пояса и календарь. Практические задачи с датами.

date_default_timezone_getУзнать, в какой зоне PHP сейчас интерпретирует даты без явно заданного пояса.

PHP 5.1+ · date

date_default_timezone_get(): string

Узнать, в какой зоне PHP сейчас интерпретирует даты без явно заданного пояса.

Часть вызоваДоговор
АргументыНет.
ВозвратСтрока timezone-ID действующего default. Не UTC-offset и не локаль.
ГраницаНе узнаёт timezone клиента, не меняет системные часы и не возвращает обязательно исходное значение php.ini.
Путь даты в первом примере
  1. set задаёт Europe/Moscow
  2. get без аргументов →zoneName
  3. date читает тот же default →03:00
  4. Отчёт явно подписывает настройку

Пустые скобки означают, что аргументы не нужны. Это настройка интерпретации/вывода дат PHP, а не время часов компьютера. Приоритет: вызов date_default_timezone_set в процессе, затем date.timezone из INI, затем UTC. Неверное INI-значение может сопровождаться предупреждением; не рассчитывайте на угадывание часового пояса пользователя браузера.

Подписать, в какой зоне построен отчётСамостоятельный пример
<?php
date_default_timezone_set('Europe/Moscow');
$zoneName = date_default_timezone_get();
echo 'Часовой пояс отчёта: ', $zoneName, "\n";
echo 'Событие: ', date('d.m.Y H:i P', 1704067200), "\n";
Ожидаемый результат
Часовой пояс отчёта: Europe/Moscow
Событие: 01.01.2024 03:00 +03:00

Getter без аргументов возвращает текущую общую настройку в zoneName. Она выводится рядом с датой, которую date сформировал в той же зоне. Это пояс PHP-контекста, не автоматически определённое местоположение клиента и не изменение часов ОС.

Вернуть общую настройку и увидеть следующий правильный отчётСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$previous = date_default_timezone_get();
try {
    date_default_timezone_set('Asia/Tokyo');
    echo 'Отчёт филиала: ', date('d.m.Y H:i P', 1704067200), "\n";
} finally { date_default_timezone_set($previous); }
echo 'Следующая запись журнала: ', date('d.m.Y H:i P', 1704067200), "\n";
echo 'Контекст после отчёта: ', date_default_timezone_get(), "\n";
Ожидаемый результат
Отчёт филиала: 01.01.2024 09:00 +09:00
Следующая запись журнала: 01.01.2024 00:00 +00:00
Контекст после отчёта: UTC

Первый getter сохранил UTC в previous. В try временно показан токийский отчёт; finally возвращает прежнюю строку в setter даже при выходе через исключение. Следующий date снова даёт 00:00 UTC, то есть виден практический эффект восстановления. У объектного форматирования с отдельной DateTimeZone общее состояние вообще можно не менять.

Сравните: date_default_timezone_set, DateTimeZone::__construct, date.

Время, часовые пояса и календарь. Практические задачи с датами.

date_default_timezone_setЯвно настроить общий timezone скрипта до работы с датами.

PHP 5.1+ · date

date_default_timezone_set(string $timezoneId): bool

Явно настроить общий timezone скрипта до работы с датами.

Часть вызоваДоговор
timezoneIdПоддерживаемый идентификатор, например UTC/Europe/Moscow/Asia/Tokyo.
Возврат / ошибкиtrue при принятии; false и E_NOTICE при неверном ID в PHP 8.5.11. Неверный тип — TypeError.
Альтернативаdate.timezone в INI задаёт default окружения; объектная DateTimeZone задаёт зону конкретной операции.
Путь даты в первом примере
  1. existing создан в UTC
  2. set(Europe/Moscow) →true
  3. date показывает 03:00, existing остаётся 00:00 UTC
  4. Новый текст 00:00 без зоны теперь означает 00:00 Moscow

Функция меняет default в текущем PHP-контексте. Она не переводит хранимые timestamp, не меняет часы ОС и не изменяет уже созданные DateTime-объекты. Проверяйте true; внешний текст лучше выбирать из поддерживаемого списка IANA, а не подставлять сокращение или смещение случайного пользователя.

Разделить общий default и зону уже созданной датыСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$existing = new DateTimeImmutable('2024-01-01 00:00:00');
$ok = date_default_timezone_set('Europe/Moscow');
if (!$ok) { throw new RuntimeException('Invalid timezone configuration'); }
echo 'Новый процедурный вывод: ', date('d.m.Y H:i P', 1704067200), "\n";
echo 'Ранее созданный объект: ', $existing->format('d.m.Y H:i P'), "\n";
echo 'Новая дата без явной зоны: ', (new DateTimeImmutable('2024-01-01 00:00:00'))->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Новый процедурный вывод: 01.01.2024 03:00 +03:00
Ранее созданный объект: 01.01.2024 00:00 +00:00
Новая дата без явной зоны: 01.01.2024 00:00 +03:00

Setter получил Europe/Moscow и вернул true в ok. Date теперь отображает известный timestamp по новому default. Existing сохранил UTC, выбранный при его создании. Новый конструктор с тем же текстом 00:00, но без зоны, читает его уже как московское время: это другой момент. Изменение общего контекста не переводит старые объекты автоматически.

Сохранить прежний смысл дат после отказа настройкиСамостоятельный пример
<?php
date_default_timezone_set('UTC');
$notice = false;
set_error_handler(function (int $level) use (&$notice): bool {
    if ($level !== E_NOTICE) { return false; }
    $notice = true;
    return true;
});
try { $ok = date_default_timezone_set('Warehouse/Unknown'); }
finally { restore_error_handler(); }
if ($notice) { echo "PHP сообщил о неверном идентификаторе\n"; }
if ($ok === false) { echo "Настройка не принята; исправьте конфигурацию\n"; }
echo 'Сохранившийся контекст: ', date_default_timezone_get(), "\n";
echo 'Следующее событие: ', date('d.m.Y H:i P', 1704067200), "\n";
Ожидаемый результат
PHP сообщил о неверном идентификаторе
Настройка не принята; исправьте конфигурацию
Сохранившийся контекст: UTC
Следующее событие: 01.01.2024 00:00 +00:00

На проверенном PHP8.5.11 неверное имя даёт E_NOTICE и false. Временный handler явно фиксирует этот сигнал, finally возвращает обычную обработку. Getter и последующий date показывают сохранённый UTC. Продолжение здесь демонстрирует состояние; рабочий запуск с обязательной неверной конфигурацией следует остановить до обещаний пользователю.

Сравните: date_default_timezone_get, timezone_identifiers_list, DateTimeZone::__construct.

Время, часовые пояса и календарь. Практические задачи с датами.

microtimeПоставить в журнал отметку времени с дробной секундой, не путая её с секундомером.

PHP 4+ · date

microtime(bool $as_float = false): string|float

Поставить в журнал отметку времени с дробной секундой, не путая её с секундомером.

Часть вызоваДоговор
as_floatfalse — строка "доля секунды целые секунды"; true — один float Unix-seconds.
Возвратstring либо float, текущее wall-clock время.
ГраницыЗависит от системных часов/gettimeofday; для измерения интервалов PHP рекомендует hrtime. Неверный тип параметра в строгом вызове — TypeError.
Путь даты в первом примере
  1. microtime(true) →receivedAt, float секунд
  2. retryAt=receivedAt+0.25
  3. Разница×1000 →250 миллисекунд
  4. Число идёт в понятную настройку повтора

microtime() без аргумента возвращает строку из двух чисел через пробел: дробь секунды, затем целые секунды эпохи. Название первого поля иногда пишут msec, но это именно доля секунды, не миллисекунды. microtime(true) возвращает одно float секунд с дробью; он удобнее для числовой операции, но имеет ограниченную точность float.

Для короткого интервала целой секунды time недостаточно. Microtime(true) возвращает одно число секунд с дробью. Сначала используем эту простую числовую форму, а затем разберём строковую форму microtime() без аргументов на фиксированном ответе.

Назначить короткую задержку повтора от одного снимкаСамостоятельный пример
<?php
$receivedAt = microtime(true);
$retryAt = $receivedAt + 0.25;
$delayMilliseconds = (int) round(($retryAt - $receivedAt) * 1000);
echo 'Первый повтор запроса назначен через ', $delayMilliseconds, " мс\n";
echo "Ожидание и отправка здесь не выполняются\n";
Ожидаемый результат
Первый повтор запроса назначен через 250 мс
Ожидание и отправка здесь не выполняются

True выбирает float секунд с дробью; возвращённый снимок сохранён в receivedAt. Добавлены 0.25 секунды, вычитание возвращает ту же длительность, умножение на 1000 переводит её в миллисекунды. Round и приведение int формируют число для подписи. Мы рассчитали настройку повтора, а не измерили производительность; системные часы могут корректироваться, поэтому для измерения длительности нужен hrtime.

Превратить строковый снимок в точную отметку журналаСамостоятельный пример
<?php
function eventLabel(string $snapshot, string $event): string
{
    [$fraction, $seconds] = explode(' ', $snapshot);
    $microseconds = substr($fraction, 2, 6);
    $at = DateTimeImmutable::createFromFormat('U.u', $seconds . '.' . $microseconds);
    if ($at === false) { throw new RuntimeException('Invalid own clock snapshot'); }
    return $at->format('Y-m-d H:i:s.u') . ' UTC — ' . $event;
}
$frozenSnapshot = '0.12500000 1704067200';
echo eventLabel($frozenSnapshot, 'Заказ BR41 принят'), "\n";
Ожидаемый результат
2024-01-01 00:00:00.125000 UTC — Заказ BR41 принят

FrozenSnapshot —явно заданный пример строки той формы, которую возвращает microtime(), а не фактическое время запуска. В рабочем вызове eventLabel(microtime(),event) PHP сначала получает строку часов и передаёт её в локальный snapshot. Explode разделяет один снимок; substr берёт шесть цифр после 0. для микросекунд. Шаблон U.u читает секунды эпохи и дробь, затем return отдаёт строку журнала обратно в echo. Helper рассчитан на собственный ответ часов, не на произвольный внешний текст.

Системная поддержка gettimeofday требуется реализации microtime; в этой Windows-сборке она есть. Микросекундный формат не означает уникальность каждой метки или точность синхронизации серверов. На большом timestamp float не представляет все микросекунды одинаково точно. Для сохранения текста дроби можно оставить исходные компоненты, для точного объекта времени используйте осознанный U.u-разбор и проверяйте errors. $_SERVER["REQUEST_TIME_FLOAT"] — другая точка измерения, начало запроса.

Сравните: time, gettimeofday.

Время, часовые пояса и календарь. Практические задачи с датами.

gettimeofdayПолучить секунды и микросекунды одним согласованным снимком часов.

PHP 4+ · date

gettimeofday(bool $as_float = false): array|float

Получить секунды и микросекунды одним согласованным снимком часов.

Часть вызоваДоговор
as_floatfalse — массив 4 полей; true — float seconds.
ВозвратИнтерфейс системного gettimeofday; структура ниже либо float.
Ошибки / границыНеверный тип аргумента — TypeError; разрешение/вспомогательные timezone-поля платформозависимы.
Путь даты в первом примере
  1. gettimeofday() →clock с sec и usec
  2. К usec добавлено 250000
  3. intdiv переносит миллион в sec, %оставляет дробную часть
  4. Разница пар →250мс для повтора

Форма массива удобна, если протокол хранит секунды и микросекунды отдельно: sec — целая секунда, usec —0…999999. Вызов gettimeofday(true) вместо этого отдаёт float секунд. Сохраняйте один ответ: два вызова не обязаны описывать одну и ту же микросекунду.

Поле массиваДоговор
secЦелые секунды Unix-эпохи.
usecМикросекунды внутри этой секунды,0…999999.
minuteswestИсторическое поле минут к западу от Greenwich; не IANA timezone-ID.
dsttimeИсторическое поле коррекции DST; не универсальная политика переходов зоны.

Если протокол хранит время двумя целыми полями, gettimeofday() даёт их одним снимком: sec и usec. В отличие от складывания float можно отдельно учесть переход через границу секунды. Сначала расчёт использует настоящий снимок, затем следующий пример показывает точные поля на фиксированном значении.

Назначить повтор с сохранением целых секунд и микросекундСамостоятельный пример
<?php
$clock = gettimeofday();
$newMicroseconds = $clock['usec'] + 250000;
$retry = [
    'sec' => $clock['sec'] + intdiv($newMicroseconds, 1000000),
    'usec' => $newMicroseconds % 1000000,
];
$delayMicroseconds = ($retry['sec'] - $clock['sec']) * 1000000 + $retry['usec'] - $clock['usec'];
echo 'Повтор назначен через ', intdiv($delayMicroseconds, 1000), " мс\n";
echo "Целая секунда и остаток передаются двумя полями\n";
Ожидаемый результат
Повтор назначен через 250 мс
Целая секунда и остаток передаются двумя полями

Gettimeofday без аргумента вернул массив clock. К usec добавлены 250000 микросекунд. Если сумма пересекла миллион, intdiv переносит целую секунду в sec, а% оставляет микросекундный остаток. Разница двух пар независимо от текущего числа даёт 250мс. Это расчёт назначения, не ожидание и не сетевой запрос; minuteswest/dsttime не участвуют в выборе региона.

Вычислить срок полсекунды в числовой формеСамостоятельный пример
<?php
$queuedAt = gettimeofday(true);
$deadline = $queuedAt + 0.5;
$windowMilliseconds = (int) round(($deadline - $queuedAt) * 1000);
echo 'Окно ожидания ответа: ', $windowMilliseconds, " мс\n";
echo "После истечения окна обработчик должен выбрать повтор или отказ\n";
Ожидаемый результат
Окно ожидания ответа: 500 мс
После истечения окна обработчик должен выбрать повтор или отказ

True меняет форму возврата: теперь это float, не массив, поэтому ключей sec/usec нет. Deadline вычислен от одного снимка queuedAt; видимая длительность 500мс передаётся логике ожидания. Сам пример не запускает очередь или таймер. Wall-clock может корректироваться; для реально прошедшего времени и timeout внутри одного процесса предпочтителен монотонный hrtime.

minuteswest/dsttime не заменяют DateTimeZone::getOffset для определённой даты и зоны. Для подписи выбирайте явную зону, а числовую секунду храните отдельно. Как и microtime, это wall clock, который может корректироваться; для benchmark нужен hrtime. На платформах могут различаться разрешение системных часов и вспомогательные timezone-поля.

Сравните: microtime, time, DateTimeZone::getOffset.

Время, часовые пояса и календарь. Практические задачи с датами.

Увидеть переход микросекунд через границу секундыСамостоятельный пример
<?php
$clock = ['sec' => 1704067200, 'usec' => 900000];
$newMicroseconds = $clock['usec'] + 250000;
$retry = [
    'sec' => $clock['sec'] + intdiv($newMicroseconds, 1000000),
    'usec' => $newMicroseconds % 1000000,
];
echo 'Исходные поля: sec=', $clock['sec'], ', usec=', $clock['usec'], "\n";
echo 'Поля повтора: sec=', $retry['sec'], ', usec=', $retry['usec'], "\n";
echo 'Повтор: ', gmdate('Y-m-d H:i:s', $retry['sec']), '.', sprintf('%06d', $retry['usec']), " UTC\n";
Ожидаемый результат
Исходные поля: sec=1704067200, usec=900000
Поля повтора: sec=1704067201, usec=150000
Повтор: 2024-01-01 00:00:01.150000 UTC

Здесь clock намеренно фиксирован для точного результата, а не выдаётся за ответ текущих часов. 900000+250000=1150000: перенос даёт следующую секунду 1704067201 и остаток 150000. Gmdate форматирует целую часть, sprintf добавляет шесть цифр дроби. Одинаковый алгоритм выше применён к реальному gettimeofday; остальные системные поля в запись протокола не нужны.

date_sun_infoПолучить расчётный дневной план восхода, заката и сумерек для координат.

PHP 5.1.2+;расчёт исправлен 7.2 · date

date_sun_info(int $timestamp, float $latitude, float $longitude): array

Получить расчётный дневной план восхода, заката и сумерек для координат.

Часть вызоваДоговор
timestampМомент дня расчёта; день определяется вместе с default timezone.
latitude / longitudeГрадусы, север/восток положительны; обязательны, не берутся из INI.
Возврат9 именованных событий; int timestamp, false когда солнце весь день ниже порога, true когда выше. transit обычно int.
ОшибкиTypeError при неверном типе; ValueError при не-finite координате в PHP 8.5.11.
День и координаты → события
  1. 15 июня 2024 UTC,55.75 N, 37.62 E
  2. Модель рассчитывает времена для 9 событий
  3. sunrise 00:44 UTC, sunset 18:15 UTC в данном примере

Передайте момент нужного календарного дня и географические координаты в градусах. Север и восток положительны, юг и запад отрицательны. Расчёт выбирает календарные поля дня в default timezone; для воспроизводимости пример задаёт UTC. Возвращённые int — абсолютные Unix-seconds, которые потом форматируются в нужной зоне. Координаты не определяют timezone автоматически.

ПолеСобытие / угол
sunrise / sunsetВосход/закат; в официальном договоре зенит 90°35′. Это не тот же параметр default старых date_sunrise/sunset.
transitВерхняя кульминация: солнце достигает максимальной высоты в этот день; не обязательно проходит точно над головой.
civil_twilight_begin / civil_twilight_endГражданские сумерки, зенит 96°; утром до восхода, вечером после заката.
nautical_twilight_begin / nautical_twilight_endНавигационные сумерки, зенит 102°; между гражданскими и астрономическими.
astronomical_twilight_begin / astronomical_twilight_endАстрономические сумерки, зенит 108°.
Подписать расчётный восход и закатСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$day = gmmktime(12, 0, 0, 6, 15, 2024);
$sun = date_sun_info($day, 55.75, 37.62);
foreach (['sunrise', 'sunset'] as $event) {
    if (!is_int($sun[$event])) { throw new RuntimeException('Событие не имеет обычного времени'); }
    echo $event, ':', gmdate('H:i', $sun[$event]), " UTC\n";
}
Ожидаемый результат
sunrise:00:44 UTC
sunset:18:15 UTC

Координаты Москвы заданы явно. Это расчёт модели на фиксированный день PHP 8.5.11, а не точное наблюдение погоды/горизонта. Сначала проверен тип, затем timestamp форматируется.

Не превращать полярный день в дату эпохиСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$summer = date_sun_info(gmmktime(12, 0, 0, 6, 21, 2024), 78.22, 15.65);
$winter = date_sun_info(gmmktime(12, 0, 0, 12, 21, 2024), 78.22, 15.65);
echo $summer['sunset'] === true ? 'Всё время выше порога' : 'Есть обычный закат';
echo "\n", $winter['sunrise'] === false ? 'Всё время ниже порога' : 'Есть обычный восход', "\n";
Ожидаемый результат
Всё время выше порога
Всё время ниже порога

true/false — осмысленные астрономические состояния соответствующего порога, не timestamp 1/0. Не отправляйте boolean в date().

Проверяйте координаты как finite float и по своему договору latitude−90…90/longitude−180…180: PHP 8.5.11 явно отвергает NAN/INF через ValueError, но не является полным географическим валидатором. В результате события могут быть на соседнем UTC-дне; храните полную дату/момент, а не только HH:mm. С PHP 7.2 расчёт исправлен относительно локальной полуночи вместо полудня, что слегка меняет результаты старых версий. Модель не учитывает здания, горы и текущую погоду. При миграции со старых функций не обещайте те же секунды: модель/настройки зенита отличаются.

Сравните: date_sunrise, date_sunset, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_sunriseПонять старый расчёт восхода и перейти на date_sun_info.

PHP 5.0+;deprecated с PHP 8.1 · date

date_sunrise(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null): string|int|float|false

Понять старый расчёт восхода и перейти на date_sun_info.

Часть вызоваДоговор
timestampДень расчёта в текущем timezone-контексте.
returnFormatSUNFUNCS_RET_STRING→HH:mm, DOUBLE→дробные часы, TIMESTAMP→Unix-seconds; три допустимых режима.
latitude / longitudeГрадусы, север/восток+, юг/запад−. null→date.default_latitude/longitude.
zenithУгол от местной вертикали до центра солнца в градусах. null→date.sunrise_zenith.
utcOffsetЧасы от UTC для строки/float; null→контекст default timezone, для TIMESTAMP игнорируется.
Версии / отказPHP 8.0 добавил null для четырёх float-параметров; deprecated с PHP 8.1; неудача false, неверный формат ValueError; сигнал E_DEPRECATED.
Legacy-настройки → HH:mm
  1. 15 июня 2024, Москва, зенит 90.833333
  2. Явное utcOffset=0
  3. Получаем 00:41 и замечаем устаревание

Это legacy API, deprecated с PHP 8.1; константы SUNFUNCS_RET_* также deprecated с PHP 8.4. Ниже показано чтение старого кода и миграция. Первый пример намеренно перехватывает только E_DEPRECATED и обязательно возвращает обычный обработчик; так устаревание видно в результате, а не спрятано глобальным error_reporting.

Увидеть старый формат и сигнал устареванияСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$deprecations = 0;
set_error_handler(function (int $level) use (&$deprecations): bool {
    if ($level === E_DEPRECATED) { ++$deprecations; return true; }
    return false;
});
try {
    $at = date_sunrise(gmmktime(12, 0, 0, 6, 15, 2024), SUNFUNCS_RET_STRING,
        55.75, 37.62, 90.833333, 0);
} finally { restore_error_handler(); }
echo $at, " UTC\n";
echo $deprecations > 0 ? 'Устаревший API' : 'Без сигнала';
echo "\n";
Ожидаемый результат
00:41 UTC
Устаревший API

Все координаты, зенит и смещение заданы явно: значение не зависит от location-настроек INI. SUNFUNCS_RET_STRING — часы/минуты строки, не Unix timestamp.

Современное событие с проверкой полярного состоянияСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$data = date_sun_info(gmmktime(12, 0, 0, 6, 15, 2024), 55.75, 37.62);
$at = $data['sunrise'];
if (is_int($at)) { echo gmdate('Y-m-d H:i', $at), " UTC\n"; }
elseif ($at === true) { echo "Солнце весь день выше порога\n"; }
else { echo "Солнце весь день ниже порога\n"; }
Ожидаемый результат
2024-06-15 00:44 UTC

У нового API нет returnFormat/utcOffset: выбираем поле события, проверяем его тип и форматируем абсолютный момент. Отдельного произвольного zenith у date_sun_info нет; для своих порогов нужен другой осознанный астрономический инструмент.

ЗенитСмысл legacy-параметра
90°50′ ≈90.833333Обычный восход по старому договору.
96°Граница гражданских сумерек.
102°Граница навигационных сумерек.
108°Граница астрономических сумерек.

SUNFUNCS_RET_DOUBLE возвращает часы как дробное число:16.5 означает 16:30, не 16:50. SUNFUNCS_RET_TIMESTAMP возвращает int и игнорирует utcOffset. Смещение для строк/float задаётся в часах, может быть дробным; это не IANA-ID с правилами DST. null использует настройки/контекст, поэтому в миграционной проверке задавайте всё явно. false может означать отсутствие восхода/заката в полярный день/ночь либо ошибку; нельзя форматировать его как 0. Неверный returnFormat даёт ValueError в PHP 8.5.11; не-finite latitude/longitude у legacy дают false, в отличие от нового date_sun_info. Новые и старые результаты могут различаться на минуты из-за разного расчётного порога, это не обещанная взаимозаменяемость секунд.

Сравните: date_sun_info, date_sunset, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

date_sunsetПонять старый расчёт заката и перейти на date_sun_info.

PHP 5.0+;deprecated с PHP 8.1 · date

date_sunset(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null): string|int|float|false

Понять старый расчёт заката и перейти на date_sun_info.

Часть вызоваДоговор
timestampДень расчёта в текущем timezone-контексте.
returnFormatSUNFUNCS_RET_STRING→HH:mm, DOUBLE→дробные часы, TIMESTAMP→Unix-seconds; три допустимых режима.
latitude / longitudeГрадусы, север/восток+, юг/запад−. null→date.default_latitude/longitude.
zenithУгол от местной вертикали до центра солнца в градусах. null→date.sunset_zenith.
utcOffsetЧасы от UTC для строки/float; null→контекст default timezone, для TIMESTAMP игнорируется.
Версии / отказPHP 8.0 добавил null для четырёх float-параметров; deprecated с PHP 8.1; неудача false, неверный формат ValueError; сигнал E_DEPRECATED.
Legacy-настройки → HH:mm
  1. 15 июня 2024, Москва, зенит 90.833333
  2. Явное utcOffset=0
  3. Получаем 18:18 и замечаем устаревание

Это legacy API, deprecated с PHP 8.1; константы SUNFUNCS_RET_* также deprecated с PHP 8.4. Ниже показано чтение старого кода и миграция. Первый пример намеренно перехватывает только E_DEPRECATED и обязательно возвращает обычный обработчик; так устаревание видно в результате, а не спрятано глобальным error_reporting.

Увидеть старый формат и сигнал устареванияСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$deprecations = 0;
set_error_handler(function (int $level) use (&$deprecations): bool {
    if ($level === E_DEPRECATED) { ++$deprecations; return true; }
    return false;
});
try {
    $at = date_sunset(gmmktime(12, 0, 0, 6, 15, 2024), SUNFUNCS_RET_STRING,
        55.75, 37.62, 90.833333, 0);
} finally { restore_error_handler(); }
echo $at, " UTC\n";
echo $deprecations > 0 ? 'Устаревший API' : 'Без сигнала';
echo "\n";
Ожидаемый результат
18:18 UTC
Устаревший API

Все координаты, зенит и смещение заданы явно: значение не зависит от location-настроек INI. SUNFUNCS_RET_STRING — часы/минуты строки, не Unix timestamp.

Современное событие с проверкой полярного состоянияСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('UTC');
$data = date_sun_info(gmmktime(12, 0, 0, 6, 15, 2024), 55.75, 37.62);
$at = $data['sunset'];
if (is_int($at)) { echo gmdate('Y-m-d H:i', $at), " UTC\n"; }
elseif ($at === true) { echo "Солнце весь день выше порога\n"; }
else { echo "Солнце весь день ниже порога\n"; }
Ожидаемый результат
2024-06-15 18:15 UTC

У нового API нет returnFormat/utcOffset: выбираем поле события, проверяем его тип и форматируем абсолютный момент. Отдельного произвольного zenith у date_sun_info нет; для своих порогов нужен другой осознанный астрономический инструмент.

ЗенитСмысл legacy-параметра
90°50′ ≈90.833333Обычный закат по старому договору.
96°Граница гражданских сумерек.
102°Граница навигационных сумерек.
108°Граница астрономических сумерек.

SUNFUNCS_RET_DOUBLE возвращает часы как дробное число:16.5 означает 16:30, не 16:50. SUNFUNCS_RET_TIMESTAMP возвращает int и игнорирует utcOffset. Смещение для строк/float задаётся в часах, может быть дробным; это не IANA-ID с правилами DST. null использует настройки/контекст, поэтому в миграционной проверке задавайте всё явно. false может означать отсутствие восхода/заката в полярный день/ночь либо ошибку; нельзя форматировать его как 0. Неверный returnFormat даёт ValueError в PHP 8.5.11; не-finite latitude/longitude у legacy дают false, в отличие от нового date_sun_info. Новые и старые результаты могут различаться на минуты из-за разного расчётного порога, это не обещанная взаимозаменяемость секунд.

Сравните: date_sun_info, date_sunrise, gmdate.

Время, часовые пояса и календарь. Практические задачи с датами.

strftimeПрочитать старый локальный формат даты по системной locale и заменить его явным formatter.

PHP 4+;deprecated с PHP 8.1 · date

strftime(string $format, ?int $timestamp = null): string|false

Прочитать старый локальный формат даты по системной locale и заменить его явным formatter.

Часть вызоваДоговор
formatСтрока с %спецификаторами из таблицы, поддерживаемыми именно C библиотекой платформы.
timestampint Unix-seconds; null/пропуск — текущий момент; nullable с PHP 8.0.
Зона / языкDefault timezone PHP. Имена дней/месяцев по LC_TIME.
Возврат / отказСтрока либо false, может сопровождаться E_WARNING; вызов deprecated: E_DEPRECATED с 8.1. Для нового кода date/gmdate или IntlDateFormatter.
Один момент → старый формат
  1. 1704067200; C-locale
  2. %Y-%m-%d %H:%M выбирает поля
  3. Получаем 2024-01-01 03:00 в Москве

API deprecated с PHP 8.1, но сохранён для чтения старых проектов. Сначала минимальный пример на явно выбранной C-locale; затем современная замена. Формат начинается с %: %Y/%m/%d — год/месяц/день, %H/%M — часы/минуты. Это другой язык, чем date("Y-m-d H:i") и третий язык относительно ICU IntlDateFormatter. Механически копировать строку формата нельзя.

Все обозначения официальной таблицыДоговор и переносимость
%a / %AКороткое / полное имя дня недели по LC_TIME.
%d / %eДень 01…31 / с пробелом перед одной цифрой. %e непереносим; Windows обычно требует %#d.
%jДень года 001…366, в отличие от date("z") с 0.
%u / %wISO-понедельник 1…воскресенье 7 / воскресенье 0…суббота 6. У старого Solaris %u может отличаться.
%U / %WНеделя года с первым воскресеньем / понедельником как началом недели 1; до неё неделя 00.
%VISO-неделя 01…53: первая имеет минимум 4 дня нового года, начало понедельник. Используйте вместе с %G.
%b / %h / %BКороткое имя месяца / его alias / полное имя по locale.
%mМесяц 01…12.
%CЦелая часть year/100 (например 20 для 2024), не номер века в человеческом счёте.
%g / %GДвузначный / полный год ISO-недели; может отличаться от календарного.
%y / %YПоследние 2 цифры / полный календарный год.
%H / %kЧас 24-hour с 0 / с пробелом; %k непереносим.
%I / %lЧас 12-hour с 0 / с пробелом; %l — маленькая L, непереносим.
%M / %SМинуты / секунды 00…59; %m — месяц, не минуты.
%p / %PAM/PM / am/pm по locale; реальный регистр/точки платформозависимы. %P не поддерживается macOS/musl и рядом других C библиотек.
%r / %R / %TЭквиваленты %I:%M:%S %p / %H:%M / %H:%M:%S при поддержке C библиотекой.
%XПредпочтительное представление времени по locale.
%z / %ZСмещение / сокращение зоны по POSIX-договору; на Windows оба могут вернуть имя, не ожидаемые offset/аббревиатуру.
%c / %xПредпочтительные дата+время / дата по locale.
%D / %FЭквиваленты %m/%d/%y / %Y-%m-%d; поддержка зависит от C библиотеки.
%sUnix timestamp как расширение некоторых C библиотек; не универсальный способ UTC-преобразования.
%n / %t / %%Перевод строки / табуляция / буквальный процент.
Воспроизвести простой старый форматСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('Europe/Moscow');
$oldLocale = setlocale(LC_TIME, '0');
if (setlocale(LC_TIME, 'C') === false) { throw new RuntimeException('C locale unavailable'); }
$deprecations = 0;
set_error_handler(function (int $level) use (&$deprecations): bool {
    if ($level === E_DEPRECATED) { ++$deprecations; return true; }
    return false;
});
try {
    $label = strftime('%Y-%m-%d %H:%M', 1704067200);
} finally {
    restore_error_handler();
    if ($oldLocale !== false) { setlocale(LC_TIME, $oldLocale); }
}
if ($label === false) { throw new RuntimeException('Legacy format failed'); }
echo $label, "\n", $deprecations > 0 ? 'Deprecated' : 'No signal', "\n";
Ожидаемый результат
2024-01-01 03:00
Deprecated

Явная C-locale делает числовой пример воспроизводимым; локальные названия ru_RU/ru_RU.UTF-8 могли бы отсутствовать или иметь другую кодировку. strftime использует default timezone, здесь Europe/Moscow. Перехвачен только намеренный сигнал устаревания; обычные предупреждения не подавляются.

Перенести подпись отчёта на явную зону и современный форматСамостоятельный пример
<?php
$timestamp = 1704067200;
$zone = new DateTimeZone('Europe/Moscow');
$date = (new DateTimeImmutable('@' . $timestamp))->setTimezone($zone);
$label = $date->format('d.m.Y H:i P');
echo 'Отчёт сформирован: ', $label, "\n";
echo 'Часовой пояс отчёта: ', $zone->getName(), "\n";
Ожидаемый результат
Отчёт сформирован: 01.01.2024 03:00 +03:00
Часовой пояс отчёта: Europe/Moscow

Входной timestamp сохранился. @ перед числом создаёт UTC-момент, setTimezone возвращает представление в выбранной зоне. Format отдаёт строку в label, внешний echo добавляет назначение отчёта. Старые%Y-%m-%d и%H:%M нельзя скопировать в format: здесь алфавит без процентов, i означает минуты. Для числовой подписи LC_TIME не нужен; следующий пример отдельно выбирает язык названий через IntlDateFormatter.

Отдельно выбрать язык названий и часовой поясСамостоятельный пример
<?php
declare(strict_types=1);

$formatter = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
    'Europe/Moscow', IntlDateFormatter::GREGORIAN, 'd MMMM yyyy, HH:mm');
$label = $formatter->format(1704067200);
if ($label === false) { throw new RuntimeException($formatter->getErrorMessage()); }
echo $label, "\n";
Ожидаемый результат
1 January 2024, 03:00

Нужен intl. Здесь ICU-формат d MMMM yyyy, HH:mm: MMMM означает полное имя месяца, mm минуты. Язык en_US выбирается у объекта, не через общий setlocale. Видно имя January, а не только цифры. Для русского названия месяца можно задать ru_RU, проверив требуемую грамматику подписи и версию ICU.

Все форматы в таблице — официальный исторический договор, но не обещание, что каждая C-библиотека их реализует. Windows может не поддерживать %e/%T/%R/%D и другие; %z/%Z ведут себя иначе, отрицательные timestamps также непереносимы. На неизвестном спецификаторе Windows-реализация PHP может дать несколько E_WARNING (документация описывает 5) и false; другая система может оставить спецификатор буквальным без warning. false также бывает при пустом format или выходе результата за 4095 байт. С PHP 8.0 timestamp nullable. Возвращаемая строка имеет кодировку системной locale, не гарантированно UTF-8. setlocale влияет на процесс и должен восстанавливаться; для нового кода предпочтительны объектный formatter/date/gmdate. %G-%V на границе года отличается от %Y: это отдельный календарь недель, не баг.

Сравните: gmstrftime, date, strptime, DateTimeInterface::format.

Время, часовые пояса и календарь. Практические задачи с датами.

В примере с переводом new IntlDateFormatter получает en_US как язык, два значения NONE отключают готовые стили даты и времени, затем указаны зона, григорианский календарь и собственный ICU-шаблон. MMMM означает полное имя месяца, yyyy —год, HH:mm —часы и минуты. Вызов formatter->format(1704067200) передаёт timestamp и возвращает строку в label либо false; после проверки echo показывает January. Такой объект не меняет LC_TIME всего процесса. Все исторические%спецификаторы и ограничения C-библиотеки выше сохранены для чтения старого кода.

gmstrftimeПрочитать старый UTC формат даты по системной locale и заменить его явным formatter.

PHP 4+;deprecated с PHP 8.1 · date

gmstrftime(string $format, ?int $timestamp = null): string|false

Прочитать старый UTC формат даты по системной locale и заменить его явным formatter.

Часть вызоваДоговор
formatСтрока с %спецификаторами из таблицы, поддерживаемыми именно C библиотекой платформы.
timestampint Unix-seconds; null/пропуск — текущий момент; nullable с PHP 8.0.
Зона / языкUTC независимо от default timezone. Имена дней/месяцев по LC_TIME.
Возврат / отказСтрока либо false, может сопровождаться E_WARNING; вызов deprecated: E_DEPRECATED с 8.1. Для нового кода date/gmdate или IntlDateFormatter.
Один момент → старый формат
  1. 1704067200; C-locale
  2. %Y-%m-%d %H:%M выбирает поля
  3. Получаем 2024-01-01 00:00 UTC

API deprecated с PHP 8.1, но сохранён для чтения старых проектов. Сначала минимальный пример на явно выбранной C-locale; затем современная замена. Формат начинается с %: %Y/%m/%d — год/месяц/день, %H/%M — часы/минуты. Это другой язык, чем date("Y-m-d H:i") и третий язык относительно ICU IntlDateFormatter. Механически копировать строку формата нельзя.

Все обозначения официальной таблицыДоговор и переносимость
%a / %AКороткое / полное имя дня недели по LC_TIME.
%d / %eДень 01…31 / с пробелом перед одной цифрой. %e непереносим; Windows обычно требует %#d.
%jДень года 001…366, в отличие от date("z") с 0.
%u / %wISO-понедельник 1…воскресенье 7 / воскресенье 0…суббота 6. У старого Solaris %u может отличаться.
%U / %WНеделя года с первым воскресеньем / понедельником как началом недели 1; до неё неделя 00.
%VISO-неделя 01…53: первая имеет минимум 4 дня нового года, начало понедельник. Используйте вместе с %G.
%b / %h / %BКороткое имя месяца / его alias / полное имя по locale.
%mМесяц 01…12.
%CЦелая часть year/100 (например 20 для 2024), не номер века в человеческом счёте.
%g / %GДвузначный / полный год ISO-недели; может отличаться от календарного.
%y / %YПоследние 2 цифры / полный календарный год.
%H / %kЧас 24-hour с 0 / с пробелом; %k непереносим.
%I / %lЧас 12-hour с 0 / с пробелом; %l — маленькая L, непереносим.
%M / %SМинуты / секунды 00…59; %m — месяц, не минуты.
%p / %PAM/PM / am/pm по locale; реальный регистр/точки платформозависимы. %P не поддерживается macOS/musl и рядом других C библиотек.
%r / %R / %TЭквиваленты %I:%M:%S %p / %H:%M / %H:%M:%S при поддержке C библиотекой.
%XПредпочтительное представление времени по locale.
%z / %ZСмещение / сокращение зоны по POSIX-договору; на Windows оба могут вернуть имя, не ожидаемые offset/аббревиатуру.
%c / %xПредпочтительные дата+время / дата по locale.
%D / %FЭквиваленты %m/%d/%y / %Y-%m-%d; поддержка зависит от C библиотеки.
%sUnix timestamp как расширение некоторых C библиотек; не универсальный способ UTC-преобразования.
%n / %t / %%Перевод строки / табуляция / буквальный процент.
Воспроизвести простой старый форматСамостоятельный пример
<?php
declare(strict_types=1);

date_default_timezone_set('Europe/Moscow');
$oldLocale = setlocale(LC_TIME, '0');
if (setlocale(LC_TIME, 'C') === false) { throw new RuntimeException('C locale unavailable'); }
$deprecations = 0;
set_error_handler(function (int $level) use (&$deprecations): bool {
    if ($level === E_DEPRECATED) { ++$deprecations; return true; }
    return false;
});
try {
    $label = gmstrftime('%Y-%m-%d %H:%M', 1704067200);
} finally {
    restore_error_handler();
    if ($oldLocale !== false) { setlocale(LC_TIME, $oldLocale); }
}
if ($label === false) { throw new RuntimeException('Legacy format failed'); }
echo $label, "\n", $deprecations > 0 ? 'Deprecated' : 'No signal', "\n";
Ожидаемый результат
2024-01-01 00:00
Deprecated

Явная C-locale делает числовой пример воспроизводимым; локальные названия ru_RU/ru_RU.UTF-8 могли бы отсутствовать или иметь другую кодировку. gmstrftime использует UTC. Перехвачен только намеренный сигнал устаревания; обычные предупреждения не подавляются.

Перенести подпись отчёта на явную зону и современный форматСамостоятельный пример
<?php
$timestamp = 1704067200;
$zone = new DateTimeZone('UTC');
$date = (new DateTimeImmutable('@' . $timestamp))->setTimezone($zone);
$label = $date->format('d.m.Y H:i P');
echo 'Отчёт сформирован: ', $label, "\n";
echo 'Часовой пояс отчёта: ', $zone->getName(), "\n";
Ожидаемый результат
Отчёт сформирован: 01.01.2024 00:00 +00:00
Часовой пояс отчёта: UTC

Входной timestamp сохранился. @ перед числом создаёт UTC-момент, setTimezone возвращает представление в выбранной зоне. Format отдаёт строку в label, внешний echo добавляет назначение отчёта. Старые%Y-%m-%d и%H:%M нельзя скопировать в format: здесь алфавит без процентов, i означает минуты. Для числовой подписи LC_TIME не нужен; следующий пример отдельно выбирает язык названий через IntlDateFormatter.

Отдельно выбрать язык названий и часовой поясСамостоятельный пример
<?php
declare(strict_types=1);

$formatter = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
    'UTC', IntlDateFormatter::GREGORIAN, 'd MMMM yyyy, HH:mm');
$label = $formatter->format(1704067200);
if ($label === false) { throw new RuntimeException($formatter->getErrorMessage()); }
echo $label, "\n";
Ожидаемый результат
1 January 2024, 00:00

Нужен intl. Здесь ICU-формат d MMMM yyyy, HH:mm: MMMM означает полное имя месяца, mm минуты. Язык en_US выбирается у объекта, не через общий setlocale. Видно имя January, а не только цифры. Для русского названия месяца можно задать ru_RU, проверив требуемую грамматику подписи и версию ICU.

Все форматы в таблице — официальный исторический договор, но не обещание, что каждая C-библиотека их реализует. Windows может не поддерживать %e/%T/%R/%D и другие; %z/%Z ведут себя иначе, отрицательные timestamps также непереносимы. На неизвестном спецификаторе Windows-реализация PHP может дать несколько E_WARNING (документация описывает 5) и false; другая система может оставить спецификатор буквальным без warning. false также бывает при пустом format или выходе результата за 4095 байт. С PHP 8.0 timestamp nullable. Возвращаемая строка имеет кодировку системной locale, не гарантированно UTF-8. setlocale влияет на процесс и должен восстанавливаться; для нового кода предпочтительны объектный formatter/date/gmdate. %G-%V на границе года отличается от %Y: это отдельный календарь недель, не баг.

Сравните: strftime, gmdate, strptime, DateTimeInterface::format.

Время, часовые пояса и календарь. Практические задачи с датами.

В примере с переводом new IntlDateFormatter получает en_US как язык, два значения NONE отключают готовые стили даты и времени, затем указаны зона, григорианский календарь и собственный ICU-шаблон. MMMM означает полное имя месяца, yyyy —год, HH:mm —часы и минуты. Вызов formatter->format(1704067200) передаёт timestamp и возвращает строку в label либо false; после проверки echo показывает January. Такой объект не меняет LC_TIME всего процесса. Все исторические%спецификаторы и ограничения C-библиотеки выше сохранены для чтения старого кода.

strptimeПонять старый locale-парсер и заменить его контролируемым разбором даты.

PHP 5.1+;deprecated с PHP 8.1;не реализован на Windows · date

strptime(string $timestamp, string $format): array|false

Понять старый locale-парсер и заменить его контролируемым разбором даты.

Часть вызоваДоговор
timestampНесмотря на имя, это строка даты, а не int Unix-seconds.
format%формат strftime; реально поддерживаемое подмножество зависит от ОС/C-библиотеки.
LocaleLC_TIME влияет на имена месяцев, дней и другие языковые части.
Возврат / ошибкиМассив tm_* плюс unparsed либо false. TypeError для неверных типов; deprecated; на Windows отсутствует.
Альтернативыdate_parse_from_format для locale-independent, IntlDateFormatter::parse для locale-dependent.
Путь даты в первом примере
  1. 15.06.2024 и шаблон!d.m.Y
  2. date_parse_from_format →parts
  3. Проверены error_count и warning_count
  4. year/month/day →report-2024-06-15.csv

strptime — старый парсер системной C-библиотеки. На Windows функция не реализована; два самостоятельных примера ниже используют современные переносимые замены. Исторический вызов оставлен отдельным fragment с предусловием Unix+HAVE_STRPTIME, он не был выполнен на этой Windows-среде. Не нужно устанавливать другой сервер только ради устаревшего API.

Поле результатаЗначение
tm_secСекунда 0…61 по историческому C договору, включая leap-second представления; это не обещание поддержки leap seconds всеми PHP date API.
tm_min / tm_hourМинута 0…59 / час 0…23.
tm_mday / tm_monДень 1…31 / месяц 0…11.
tm_yearГод минус 1900.
tm_wday / tm_ydayДень от воскресенья 0…6 / от 1 января 0…365.
unparsedНеобработанный остаток исходного текста: его нельзя молча игнорировать для строгого поля.
Историческая форма, только для имеющего strptime UnixФрагмент или команда
$parsed = strptime('15.06.2024 rest', '%d.%m.%Y');
// При успешном разборе проверяйте $parsed['unparsed'].
// tm_year нужно увеличить на 1900, tm_mon — на 1.

Контекстный fragment, не запуск: нужен Unix с доступным strptime и известная LC_TIME. Функция устарела и выдаёт E_DEPRECATED. Поддерживаемое подмножество %форматов зависит от ОС/C-библиотеки, даже если strftime умеет печатать этот спецификатор.

Получить из даты имя дневного отчёта без старой структуры tmСамостоятельный пример
<?php
$parts = date_parse_from_format('!d.m.Y', '15.06.2024');
if ($parts['error_count'] || $parts['warning_count']) { throw new RuntimeException('Invalid report date'); }
$filename = sprintf('report-%04d-%02d-%02d.csv', $parts['year'], $parts['month'], $parts['day']);
echo 'Имя отчёта: ', $filename, "\n";
echo 'Незаданные часы: ', sprintf('%02d:%02d', $parts['hour'], $parts['minute']), "\n";
Ожидаемый результат
Имя отчёта: report-2024-06-15.csv
Незаданные часы: 00:00

Строка 15.06.2024 разобрана современным date_parse_from_format; ! сбросил незаданные часы в ноль. Массив parts уже содержит полный 2024 и месяц 6, поэтому старые поправки+1900/+1 не нужны. После проверки ошибок и предупреждений sprintf возвращает имя файла, которое показывает echo; файл в примере не создаётся. Это переносимая замена разбора полей, а не выполненный на Windows strptime.

Поле даты не должно скрывать хвост текстаСамостоятельный пример
<?php
declare(strict_types=1);

function readDate(string $text): ?DateTimeImmutable
{
    if (str_contains($text, "\0")) { return null; }
    $date = DateTimeImmutable::createFromFormat('!d.m.Y', $text, new DateTimeZone('UTC'));
    $issues = DateTimeImmutable::getLastErrors();
    if ($date === false || ($issues !== false && ($issues['warning_count'] || $issues['error_count']))) { return null; }
    return $date->format('d.m.Y') === $text ? $date : null;
}
echo readDate('15.06.2024')?->format('Y-m-d') ?? 'Отказ', "\n";
echo readDate('15.06.2024 rest')?->format('Y-m-d') ?? 'Отказ', "\n";
Ожидаемый результат
2024-06-15
Отказ

?-> вызывает метод только у не-null результата. С PHP 8.2 getLastErrors возвращает false при отсутствии ошибок; проверка учитывает это. Явная проверка отклоняет NUL до createFromFormat, который иначе может бросить ValueError. Внешний ввод не получает необработанный отказ из-за такого байта.

Для текста с переводными именами используйте IntlDateFormatter::parse с явной locale и проверкой потреблённой позиции/нестрогого режима; для числового фиксированного формата — date_parse_from_format/createFromFormat. strptime возвращает поля и хвост, не timestamp и не объект с зоной. Отсутствующие поля и поддержка директив различаются между C-библиотеками, поэтому успешный возврат не доказывает строгую корректность всего ввода. Официальная prose/changelog отмечает deprecated с PHP 8.1, что подтверждено @deprecated в PHP 8.1.0 source; новый #[Deprecated] metadata в PHP 8.5.11 пишет since=8.2. Это расхождение документации/метаданных, а не основание считать PHP 8.1 безопасным новым применением.

Сравните: strftime, date_parse_from_format, DateTimeImmutable::createFromFormat.

Время, часовые пояса и календарь. Практические задачи с датами.

В следующем полном readDate вызов с 15.06.2024 сначала передаёт строку в локальный text. Проверка NUL может вернуть null сразу. CreateFromFormat создаёт candidate-даты или false; getLastErrors относится именно к этому последнему разбору. После проверки ошибок return отдаёт объект только при точном обратном совпадении текста. В месте вызова ?->format вызывается у возвращённого объекта и даёт 2024-06-15. Для строки с rest возвращается null, ?-> пропускает метод, а ?? выбирает слово «Отказ». Никакого частично разобранного хвоста в сохранённую дату не попадает. Исторический fragment остаётся отмечен как не выполненный: штатный strptime отсутствует на Windows.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.