Глава 83 · От первой даты до справочника
Дата и время: полный справочник Date/Time
Классы, все методы и функции, календарные форматы, интервалы, зоны, расписания и ошибки — от первой даты до реальной интеграции.
Из записи встречи в понятную дату
Дата события проходит через приложение
Поставщик сообщил начало встречи: 2024-06-15T07:30:00+00:00. Нужно показать посетителю в Москве то же событие.
- Строка из API
Дата, часы и смещение +00:00 вместе определяют момент.
- Проверенный разбор
createFromFormat, диагностика и сверка точного формата создают DateTimeImmutable.
- Europe/Moscow
setTimezone меняет представление: момент события остаётся тем же.
- 15.06.2024 10:30
format возвращает подпись для посетителя. В API отправляют согласованный формат со смещением.
Строка, момент и местная подпись — разные представления. Ручное прибавление трёх часов к timestamp сдвинуло бы само событие.
В интернет-магазине нужно показать время заказа, в сервисе записи — время встречи, в интеграции — передать момент события другой системе. Строка «15 июня, 10:30» без года и часового пояса не определяет точный момент. Объект даты соединяет календарные поля с правилами зоны, а format превращает их в подпись нужного вида.
- Строка: 2024-06-15 10:30:00
- Зона: Europe/Moscow
- DateTimeImmutable хранит дату с зоной
- 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.
- 07:30 UTC
- Europe/Moscow на этой дате: +03:00
- 10:30 на московских часах
- 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, но наследнику нужно правильно инициализировать родителя. Для обычной работы наследование не требуется.
- $start содержит 15 июня
- $other = $start указывает на тот же объект
- $next = $other->modify("+1 day")
- 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, но наследнику нужно правильно инициализировать родителя. Для обычной работы наследование не требуется.
- $start содержит 15 июня
- $other = $start указывает на тот же объект
- $next = $other->modify("+1 day")
- 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 возможно, но не нужно ради одного форматирования.
- DateTime с 15 июня
- DateTimeImmutable с 16 июня
- Оба передаются в параметр DateTimeInterface
- Общий 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. Зона операционной системы не должна быть неявным договором приложения.
- Приложение определяет свою базовую зону
- Явный DateTimeZone применяется к конкретной дате
- timezone_version_get сообщает версию правил
- Изменения правил проверяются на значимых датах
| Настройка | Значение и область |
|---|---|
| date.timezone | По умолчанию UTC, INI_ALL. С PHP 8.2 пустая/неверная настройка вызывает предупреждение. Для вызова без явной зоны действует приоритет date_default_timezone_set → date.timezone → UTC. |
| date.default_latitude | 31.7667, INI_ALL. Широта: −90 юг … +90 север, экватор 0. |
| date.default_longitude | 35.2333, INI_ALL. Долгота: −180 запад … +180 восток. |
| date.sunrise_zenith | 90.833333, INI_ALL; до PHP 8.0 было 90.583333. Зенит восхода. |
| date.sunset_zenith | 90.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
$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. Таблица сохраняет все стандартные имена, включая устаревшие, чтобы можно было читать старые проекты. Шаблон нужно выбирать по договору получателя, а не по самому современному названию.
- DATE_ATOM содержит Y-m-d\TH:i:sP
- format применяет его к текущей зоне объекта
- Возвращается строка со смещением
- Часовой пояс не меняется автоматически
<?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::* | Шаблон | Применение и граница |
|---|---|---|
| ATOM | Y-m-d\TH:i:sP | Обмен с явным смещением; совместимая форма ISO/RFC3339/XML Schema. |
| COOKIE | l, d-M-Y H:i:s T | Историческая подпись cookie. Имя шаблона не заменяет актуальный договор HTTP. |
| ISO8601 | Y-m-d\TH:i:sO | Историческая ISO-подобная форма; официальный manual рекомендует ATOM или ISO8601_EXPANDED для совместимости со стандартом. |
| ISO8601_EXPANDED | X-m-d\TH:i:sP | Расширенный год всегда со знаком; добавлена в 8.2. |
| RFC822 | D, d M y H:i:s O | Двузначный год, старый формат RFC 822. |
| RFC850 | l, d-M-y H:i:s T | Полный день недели и короткий год, исторический RFC 850. |
| RFC1036 | D, d M y H:i:s O | Как RFC822, исторический формат. |
| RFC1123 | D, d M Y H:i:s O | Четырёхзначный год и числовое смещение. |
| RFC7231 | D, d M Y H:i:s \G\M\T | GMT здесь буквальный текст: сначала UTC. Константы DATE_RFC7231 и интерфейса deprecated в 8.5; введены в 7.0.19/7.1.5. |
| RFC2822 | D, d M Y H:i:s O | Форма RFC2822/RFC5322 с числовым смещением. |
| RFC3339 | Y-m-d\TH:i:sP | Та же строка шаблона, что ATOM. |
| RFC3339_EXTENDED | Y-m-d\TH:i:s.vP | Миллисекунды v; оставшиеся микросекунды в подпись не попадают. |
| RSS | D, d M Y H:i:s O | Совпадает с RFC1123. |
| W3C | Y-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_TIMESTAMP | int timestamp, режим возврата date_sunrise/date_sunset. |
| SUNFUNCS_RET_STRING | Строка часы:минуты, например 08:02. |
| SUNFUNCS_RET_DOUBLE | float часов, например 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 помогают сформировать корректную подпись.
<?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, h | 12-часовое представление часа, до двух цифр. Значения могут переполнить период AM/PM. |
| G, H | 24-часовое представление часа; переполнение может перенести дату. |
| i, s | Минуты/секунды с ведущим нулём; значения сверх диапазона нормализуются с диагностикой. |
| v | До трёх цифр доли секунды как миллисекунд, PHP 7.3+. 12 означает .12 секунды. |
| u | До шести цифр доли секунды. 45 означает .45 секунды, а не 45 микросекунд. |
| e, O, p, P, T | Идентификатор зоны, UTC-смещение или сокращение. Явное значение в строке определяет зону вместо третьего аргумента. |
| U | Unix 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. |
| L | 1 для високосного года, иначе 0. |
| o | Год ISO-недели: на границе декабря/января может отличаться от календарного Y. |
| Y | Календарный год минимум четырьмя цифрами; отрицательные годы со знаком −. |
| y | Последние две цифры года. Для внешнего протокола обычно безопаснее полный Y. |
| X | Расширенный год минимум четырьмя цифрами и всегда со знаком +/−; PHP 8.2+. |
| x | Год как Y в обычном диапазоне; расширенная форма со знаком при необходимости, включая + для 10000 и больше; PHP 8.2+. |
| a / A | am/pm / AM/PM. |
| B | Swatch Internet Time, 000…999. Не обычные часы/минуты. |
| g / h | 12-часовой час 1…12 / 01…12. |
| G / H | 24-часовой час 0…23 / 00…23. |
| i / s | Минуты/секунды с ведущим нулём, 00…59. i — минуты; m — месяц. |
| u / v | Шесть цифр микросекунд / три цифры миллисекунд. date(int timestamp) не хранит дробь; у объекта она может быть. |
| e | Имя/идентификатор часового пояса, например Europe/Moscow или фиксированное смещение. |
| I | Большая i: 1 при действующем летнем времени, иначе 0. |
| O / P | UTC-смещение без двоеточия / с двоеточием: +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 создаёт строку вывода. Одинаковые буквы и похожие названия не означают одинаковые правила. Для формы выбирают однозначный формат, а свободный парсер удобен для правил, которыми управляет сама программа.
- Строка 15.06.2024 → createFromFormat("!d.m.Y", …)
- Объект даты → modify("next monday")
- Объект даты → format("Y-m-d")
- Полученная подпись — строка, не новая настройка парсера
Полный алфавит формата ввода; алфавит вывода. Ниже — все семейства свободной грамматики. Обозначения 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.
- Вход 7:20 pm
- pm означает вторую половину суток
- Парсер формирует час 19
- В 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/pm | hh [пробел] meridian | 4 am; 5PM |
| Час и минуты + am/pm | hh [.:] MM [пробел] meridian | 4:08 am; 7.19P.M. |
| Часы, минуты, секунды + am/pm | hh [.:] MM [.:] SS [пробел] meridian | 4:08:37 am |
| MS SQL с дробью | hh : MM : SS [.:] цифры meridian | 4:08:39:12313am |
| Часы и минуты, 24-часовые | [t/T] HH [.:] MM | 04:08; 19.19; T23:43 |
| Часы и минуты слитно | [t/T] HHMM | 0408; t1919 |
| Часы, минуты, секунды | [t/T] HH [.:] MM [.:] SS | 04.08.37; t19:19:19 |
| То же без разделителей | [t/T] HHMMSS | 040837; 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 запрашивает точку и шесть цифр микросекунд. Так несколько источников получают сопоставимую строку журнала.
<?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, точку, дефис по конкретной строке.
- Вход 06/07/2024
- Парсер читает месяц 06 и день 07
- Дата — 7 июня
- Если ожидалось 6 июля, нужен явный d/m/Y
| Семейство | Структура | Примеры |
|---|---|---|
| Месяц/день по-американски | mm/dd | 5/12 |
| Месяц/день/год по-американски | mm/dd/y | 12/22/78; 1/17/2006 |
| Полный год/месяц/день | YYYY/mm/dd | 2008/6/30 |
| Год и месяц GNU | YYYY-mm | 2008-6; 2008-06 |
| Год-месяц-день | y-mm-dd | 2008-6-30; 78-12-22; 8-6-21 |
| День, месяц, полный год | dd[точка/tab/дефис]mm[точка/дефис]YYYY | 30-6-2008; 22.12.1978 |
| День, месяц, короткий год | dd[точка/tab]mm.yy | 30.6.08; 22.12.78 — приоритеты описаны ниже |
| День, текст месяца, год | dd + разделители + m + разделители + y | 30-June 2008; 22DEC78; 14 III 1879 |
| Текст месяца и полный год | m + разделители + YYYY; день становится 1 | June 2008; DEC1978 |
| Полный год и текст месяца | YYYY + разделители + m; день становится 1 | 2008 June; 1978-XII |
| Текст месяца, день, год | m + dd + y с допустимыми пробелами/пунктуацией/суффиксом | July 1st, 2008; April 17, 1790 |
| Текст месяца и день | m + dd, год из базового контекста | July 1st,; Apr 17 |
| День и текст месяца | dd + m | 1 July; 17 Apr |
| Сокращение месяца-день-год | M-DD-y | May-09-78; Apr-17-1790 |
| Год-сокращение месяца-день | y-M-DD | 78-Dec-22; 1814-MAY-17 |
| Только год, четыре цифры | YYYY; надёжно после уже распознанного времени | 1978; 2008 — могут стать часами/минутами |
| Расширенный год со знаком | + или −, затем 5…19 цифр | -81120; +20192 |
| Только текст месяца | m | March; jun; DEC |
| Восемь цифр ISO | YYYYMMDD | 20240615 |
| ISO с косыми чертами | YYYY/MM/DD, по две цифры месяца/дня | 2024/06/15 |
| ISO с коротким годом | yy-MM-DD | 24-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/yy | 0…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. Название стандарта описывает форму, но не доказывает, что гибкий парсер проверил все ограничения внешнего протокола.
- 2024:06:15 10:30:00 из метаданных
- Парсер распознаёт EXIF-структуру
- Зона задана отдельно, если источник её не передал
- 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 Format | dd/M/YYYY:HH:II:SS смещение | 10/Oct/2000:13:55:36 -0700 |
| EXIF | YYYY:MM:DD HH:II:SS | 2024:06:15 10:30:00 |
| ISO-год и неделя | YYYY[-]Www | 2024W24; 2024-W24 |
| ISO-год, неделя, день | YYYY[-]Www[-]d; d 0…7 по грамматике | 2024W246; 2024-W24-6 |
| MySQL | YYYY-MM-DD HH:II:SS | 2024-06-15 10:30:00 |
| PostgreSQL, порядковый день года | YYYY[.]ddd; ddd 001…366 | 2024.167; 2024167 |
| SOAP | YYYY-MM-DDTHH:II:SS.дробь[смещение] | 2024-06-15T10:30:00.125+03:00 |
| Unix timestamp | @[-]целые секунды | @0; @-1 |
| Unix timestamp с долей | @[-]секунды . от 0 до 6 цифр | @123.125456 |
| XMLRPC | YYYYMMDDThh:II:SS | 20240615T9:30:00 |
| XMLRPC компактный | YYYYMMDD[t/T]hhIISS | 20240615t093000 |
| WDDX | YYYY-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.
- База: 2024-06-15 10:30
- tomorrow 11:00 → завтра в 11:00
- 11:00 tomorrow → tomorrow сбрасывает время
- Получается завтра в 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 / today | 00:00 текущей/базовой даты. |
| now | Относительно базы ничего не меняет; в конструкторе база — текущий момент. |
| noon | 12:00. |
| tomorrow | Следующий день, время сброшено в 00:00. |
| back of N | 15 минут после указанного часа: back of 7pm → 19:15. |
| front of N | 15 минут до указанного часа: 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 сначала устанавливает первое число месяца и меняет смысл на порядковый день недели внутри месяца.
- July 2024 → 1 июля, понедельник
- first monday July 2024 → следующий понедельник 8 июля
- first monday of July 2024 → первое вхождение месяца
- Получаем 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 интервала |
- Счёт от 10 апреля, срок P5D
- Создаём объект даты и шаг
- add возвращает 15 апреля
- Выводим дату, не изменяя исходный счёт
Дата счёта хранит 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 секунд. |
- 01:30 при смещении +02:00
- После перехода смещение становится +01:00
- PT3H продвигает момент на10800 секунд
- Локальные часы показывают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 хранит момент и выбранное представление. Начнём с двух переменных и одного действия; таблицы переходов понадобятся только после этого.
- $meeting: 2024-01-15 12:00 UTC
- $berlin: правила Europe/Berlin → 13:00
- $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. Он описывает пояс, а не календарную дату. У регионального объекта есть доступ к правилам смещения, переходам и справочной географии. Набор правил берётся из базы среды; объект не выполняет сетевой запрос за прогнозом политики государства.
- new DateTimeZone("Europe/Berlin") → отдельный объект правил
- getName/getOffset/getTransitions/getLocation → сведения об этом объекте
- 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 — результат вычисления на конкретную дату, а не долгосрочная настройка для будущих заказов. Смещение из браузера не позволяет однозначно выбрать город.
- Пользователь выбирает Asia/Tokyo
- Сервер проверяет по своему listIdentifiers()
- В БД сохраняется строка Asia/Tokyo
- При показе заказа 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. Безопасный приём формы разобран в работе с формами.
Регион ещё не отвечает на вопрос, что делать с местным временем, которое пропущено или повторяется при переводе часов. Следующие примеры продолжают выбор филиала: сначала один несуществующий час, затем два разных момента с одинаковыми показаниями часов.
- Форма:31.03.2024 02:30
- Правила Europe/Berlin пропускают час 02:xx
- Парсер создаёт 03:30; сравнение с вводом обнаруживает изменение
- Подтверждения записи нет; нужен новый выбор
<?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 до парсера.
<?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().
- Для профиля: getName() → строка → new DateTimeZone
- Для доверенного внутреннего снимка: serialize → __serialize
- Восстановление: unserialize → __unserialize, правила из текущей tzdb
| Метод | Когда встречается |
|---|---|
DateTimeZone::__serialize | PHP 8.2+: массив состояния для serialize(). |
DateTimeZone::__unserialize | PHP 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: статический справочник и установленная среда могут обновляться в разные дни.
- Найти регион в каталоге
- Проверить имя через listIdentifiers() своего PHP
- Сохранить всю строку
- Применить к конкретной дате
Для поля выбора источник истины — DateTimeZone::listIdentifiers на сервере. PHP.net предупреждает: поведение имён вне поддерживаемого списка не определено. Не превращайте этот статический снимок в вечный список разрешений приложения; обновляйте среду и проверяйте совместимость.
- Африка — 52 имён
- Америка — 144 имён
- Антарктида — 11 имён
- Арктика — 1 имён
- Азия — 82 имён
- Атлантика — 10 имён
- Австралия — 11 имён
- Европа — 58 имён
- Индийский океан — 11 имён
- Тихий океан — 38 имён
- Другие и совместимые имена — 180 имён
Списки ниже сохраняют английские идентификаторы без перевода и изменения подчёркиваний. Это технические ключи, а русские названия рядом — объяснение для читателя. Раздел «Другие» содержит совместимость и особое предупреждение про Etc/GMT.
Африка: все поддерживаемые идентификаторы
Для филиала в Найроби выберите Africa/Nairobi. Общий континент не определяет единое смещение: внутри списка разные исторические и действующие правила.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AFRICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Africa/AbidjanAfrica/AccraAfrica/Addis_AbabaAfrica/AlgiersAfrica/AsmaraAfrica/BamakoAfrica/BanguiAfrica/BanjulAfrica/BissauAfrica/BlantyreAfrica/BrazzavilleAfrica/BujumburaAfrica/CairoAfrica/CasablancaAfrica/CeutaAfrica/ConakryAfrica/DakarAfrica/Dar_es_SalaamAfrica/DjiboutiAfrica/DoualaAfrica/El_AaiunAfrica/FreetownAfrica/GaboroneAfrica/HarareAfrica/JohannesburgAfrica/JubaAfrica/KampalaAfrica/KhartoumAfrica/KigaliAfrica/KinshasaAfrica/LagosAfrica/LibrevilleAfrica/LomeAfrica/LuandaAfrica/LubumbashiAfrica/LusakaAfrica/MalaboAfrica/MaputoAfrica/MaseruAfrica/MbabaneAfrica/MogadishuAfrica/MonroviaAfrica/NairobiAfrica/NdjamenaAfrica/NiameyAfrica/NouakchottAfrica/OuagadougouAfrica/Porto-NovoAfrica/Sao_TomeAfrica/TripoliAfrica/TunisAfrica/Windhoek
Официальный список Африка; 52 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Америка: все поддерживаемые идентификаторы
Северная и Южная Америка входят в одну группу. В имени America/Argentina/Buenos_Aires три части; сохраните всю строку, не только последнее название города.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AMERICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
America/AdakAmerica/AnchorageAmerica/AnguillaAmerica/AntiguaAmerica/AraguainaAmerica/Argentina/Buenos_AiresAmerica/Argentina/CatamarcaAmerica/Argentina/CordobaAmerica/Argentina/JujuyAmerica/Argentina/La_RiojaAmerica/Argentina/MendozaAmerica/Argentina/Rio_GallegosAmerica/Argentina/SaltaAmerica/Argentina/San_JuanAmerica/Argentina/San_LuisAmerica/Argentina/TucumanAmerica/Argentina/UshuaiaAmerica/ArubaAmerica/AsuncionAmerica/AtikokanAmerica/BahiaAmerica/Bahia_BanderasAmerica/BarbadosAmerica/BelemAmerica/BelizeAmerica/Blanc-SablonAmerica/Boa_VistaAmerica/BogotaAmerica/BoiseAmerica/Cambridge_BayAmerica/Campo_GrandeAmerica/CancunAmerica/CaracasAmerica/CayenneAmerica/CaymanAmerica/ChicagoAmerica/ChihuahuaAmerica/Ciudad_JuarezAmerica/Costa_RicaAmerica/CoyhaiqueAmerica/CrestonAmerica/CuiabaAmerica/CuracaoAmerica/DanmarkshavnAmerica/DawsonAmerica/Dawson_CreekAmerica/DenverAmerica/DetroitAmerica/DominicaAmerica/EdmontonAmerica/EirunepeAmerica/El_SalvadorAmerica/Fort_NelsonAmerica/FortalezaAmerica/Glace_BayAmerica/Goose_BayAmerica/Grand_TurkAmerica/GrenadaAmerica/GuadeloupeAmerica/GuatemalaAmerica/GuayaquilAmerica/GuyanaAmerica/HalifaxAmerica/HavanaAmerica/HermosilloAmerica/Indiana/IndianapolisAmerica/Indiana/KnoxAmerica/Indiana/MarengoAmerica/Indiana/PetersburgAmerica/Indiana/Tell_CityAmerica/Indiana/VevayAmerica/Indiana/VincennesAmerica/Indiana/WinamacAmerica/InuvikAmerica/IqaluitAmerica/JamaicaAmerica/JuneauAmerica/Kentucky/LouisvilleAmerica/Kentucky/MonticelloAmerica/KralendijkAmerica/La_PazAmerica/LimaAmerica/Los_AngelesAmerica/Lower_PrincesAmerica/MaceioAmerica/ManaguaAmerica/ManausAmerica/MarigotAmerica/MartiniqueAmerica/MatamorosAmerica/MazatlanAmerica/MenomineeAmerica/MeridaAmerica/MetlakatlaAmerica/Mexico_CityAmerica/MiquelonAmerica/MonctonAmerica/MonterreyAmerica/MontevideoAmerica/MontserratAmerica/NassauAmerica/New_YorkAmerica/NomeAmerica/NoronhaAmerica/North_Dakota/BeulahAmerica/North_Dakota/CenterAmerica/North_Dakota/New_SalemAmerica/NuukAmerica/OjinagaAmerica/PanamaAmerica/ParamariboAmerica/PhoenixAmerica/Port-au-PrinceAmerica/Port_of_SpainAmerica/Porto_VelhoAmerica/Puerto_RicoAmerica/Punta_ArenasAmerica/Rankin_InletAmerica/RecifeAmerica/ReginaAmerica/ResoluteAmerica/Rio_BrancoAmerica/SantaremAmerica/SantiagoAmerica/Santo_DomingoAmerica/Sao_PauloAmerica/ScoresbysundAmerica/SitkaAmerica/St_BarthelemyAmerica/St_JohnsAmerica/St_KittsAmerica/St_LuciaAmerica/St_ThomasAmerica/St_VincentAmerica/Swift_CurrentAmerica/TegucigalpaAmerica/ThuleAmerica/TijuanaAmerica/TorontoAmerica/TortolaAmerica/VancouverAmerica/WhitehorseAmerica/WinnipegAmerica/Yakutat
Официальный список Америка; 144 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Антарктида: все поддерживаемые идентификаторы
Станции могут пользоваться разными правилами. Для расписания станции нужен согласованный идентификатор, а не вычисление по долготе или общему названию континента.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ANTARCTICA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Antarctica/CaseyAntarctica/DavisAntarctica/DumontDUrvilleAntarctica/MacquarieAntarctica/MawsonAntarctica/McMurdoAntarctica/PalmerAntarctica/RotheraAntarctica/SyowaAntarctica/TrollAntarctica/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/AdenAsia/AlmatyAsia/AmmanAsia/AnadyrAsia/AqtauAsia/AqtobeAsia/AshgabatAsia/AtyrauAsia/BaghdadAsia/BahrainAsia/BakuAsia/BangkokAsia/BarnaulAsia/BeirutAsia/BishkekAsia/BruneiAsia/ChitaAsia/ColomboAsia/DamascusAsia/DhakaAsia/DiliAsia/DubaiAsia/DushanbeAsia/FamagustaAsia/GazaAsia/HebronAsia/Ho_Chi_MinhAsia/Hong_KongAsia/HovdAsia/IrkutskAsia/JakartaAsia/JayapuraAsia/JerusalemAsia/KabulAsia/KamchatkaAsia/KarachiAsia/KathmanduAsia/KhandygaAsia/KolkataAsia/KrasnoyarskAsia/Kuala_LumpurAsia/KuchingAsia/KuwaitAsia/MacauAsia/MagadanAsia/MakassarAsia/ManilaAsia/MuscatAsia/NicosiaAsia/NovokuznetskAsia/NovosibirskAsia/OmskAsia/OralAsia/Phnom_PenhAsia/PontianakAsia/PyongyangAsia/QatarAsia/QostanayAsia/QyzylordaAsia/RiyadhAsia/SakhalinAsia/SamarkandAsia/SeoulAsia/ShanghaiAsia/SingaporeAsia/SrednekolymskAsia/TaipeiAsia/TashkentAsia/TbilisiAsia/TehranAsia/ThimphuAsia/TokyoAsia/TomskAsia/UlaanbaatarAsia/UrumqiAsia/Ust-NeraAsia/VientianeAsia/VladivostokAsia/YakutskAsia/YangonAsia/YekaterinburgAsia/Yerevan
Официальный список Азия; 82 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Атлантика: все поддерживаемые идентификаторы
Atlantic/Azores и Atlantic/Cape_Verde — самостоятельные идентификаторы. Группа — способ найти настройку, а не общая формула времени для всех островов.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::ATLANTIC). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Atlantic/AzoresAtlantic/BermudaAtlantic/CanaryAtlantic/Cape_VerdeAtlantic/FaroeAtlantic/MadeiraAtlantic/ReykjavikAtlantic/South_GeorgiaAtlantic/St_HelenaAtlantic/Stanley
Официальный список Атлантика; 10 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Австралия: все поддерживаемые идентификаторы
Разные штаты и острова следуют разным правилам. У Australia/Lord_Howe сезонное изменение в примере составляет полчаса, поэтому флаг DST не означает универсальное +3600.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::AUSTRALIA). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Australia/AdelaideAustralia/BrisbaneAustralia/Broken_HillAustralia/DarwinAustralia/EuclaAustralia/HobartAustralia/LindemanAustralia/Lord_HoweAustralia/MelbourneAustralia/PerthAustralia/Sydney
Официальный список Австралия; 11 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Европа: все поддерживаемые идентификаторы
Выбирайте регион филиала, например Europe/Berlin. Текущее совпадение часов с соседним городом не обещает одинаковую историю или будущие правила.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::EUROPE). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Europe/AmsterdamEurope/AndorraEurope/AstrakhanEurope/AthensEurope/BelgradeEurope/BerlinEurope/BratislavaEurope/BrusselsEurope/BucharestEurope/BudapestEurope/BusingenEurope/ChisinauEurope/CopenhagenEurope/DublinEurope/GibraltarEurope/GuernseyEurope/HelsinkiEurope/Isle_of_ManEurope/IstanbulEurope/JerseyEurope/KaliningradEurope/KirovEurope/KyivEurope/LisbonEurope/LjubljanaEurope/LondonEurope/LuxembourgEurope/MadridEurope/MaltaEurope/MariehamnEurope/MinskEurope/MonacoEurope/MoscowEurope/OsloEurope/ParisEurope/PodgoricaEurope/PragueEurope/RigaEurope/RomeEurope/SamaraEurope/San_MarinoEurope/SarajevoEurope/SaratovEurope/SimferopolEurope/SkopjeEurope/SofiaEurope/StockholmEurope/TallinnEurope/TiraneEurope/UlyanovskEurope/VaduzEurope/VaticanEurope/ViennaEurope/VilniusEurope/VolgogradEurope/WarsawEurope/ZagrebEurope/Zurich
Официальный список Европа; 58 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Индийский океан: все поддерживаемые идентификаторы
Indian/Maldives — строка идентификатора, а не ISO-код государства. Для поиска вариантов по стране предназначен отдельный режим PER_COUNTRY.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::INDIAN). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Indian/AntananarivoIndian/ChagosIndian/ChristmasIndian/CocosIndian/ComoroIndian/KerguelenIndian/MaheIndian/MaldivesIndian/MauritiusIndian/MayotteIndian/Reunion
Официальный список Индийский океан; 11 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Тихий океан: все поддерживаемые идентификаторы
Для встреч между Pacific/Auckland и America/New_York сравнивайте один момент, отображённый в двух поясах. Календарная дата на двух экранах тоже может отличаться.
Получить эту группу из установленной базы: DateTimeZone::listIdentifiers(DateTimeZone::PACIFIC). Полный контракт и запуск — DateTimeZone::listIdentifiers. Число пунктов в своей версии может отличаться от снимка.
Pacific/ApiaPacific/AucklandPacific/BougainvillePacific/ChathamPacific/ChuukPacific/EasterPacific/EfatePacific/FakaofoPacific/FijiPacific/FunafutiPacific/GalapagosPacific/GambierPacific/GuadalcanalPacific/GuamPacific/HonoluluPacific/KantonPacific/KiritimatiPacific/KosraePacific/KwajaleinPacific/MajuroPacific/MarquesasPacific/MidwayPacific/NauruPacific/NiuePacific/NorfolkPacific/NoumeaPacific/Pago_PagoPacific/PalauPacific/PitcairnPacific/PohnpeiPacific/Port_MoresbyPacific/RarotongaPacific/SaipanPacific/TahitiPacific/TarawaPacific/TongatapuPacific/WakePacific/Wallis
Официальный список Тихий океан; 38 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Другие и совместимые имена: все поддерживаемые идентификаторы
Этот список нужен для чтения наследованных настроек. PHP.net рекомендует не использовать его имена в новом коде, кроме UTC: совместимость может измениться, а некоторые названия ведут себя неожиданно.
- Обычное +08:00: восемь часов восточнее UTC
- Etc/GMT-8 означает ту же положительную поправку
- Для местных правил выбираем Asia/Shanghai или Australia/Perth
<?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/AsmeraAfrica/TimbuktuAmerica/Argentina/ComodRivadaviaAmerica/AtkaAmerica/Buenos_AiresAmerica/CatamarcaAmerica/Coral_HarbourAmerica/CordobaAmerica/EnsenadaAmerica/Fort_WayneAmerica/GodthabAmerica/IndianapolisAmerica/JujuyAmerica/Knox_INAmerica/LouisvilleAmerica/MendozaAmerica/MontrealAmerica/NipigonAmerica/PangnirtungAmerica/Porto_AcreAmerica/Rainy_RiverAmerica/RosarioAmerica/Santa_IsabelAmerica/ShiprockAmerica/Thunder_BayAmerica/VirginAmerica/YellowknifeAntarctica/South_PoleAsia/AshkhabadAsia/CalcuttaAsia/ChoibalsanAsia/ChongqingAsia/ChungkingAsia/DaccaAsia/HarbinAsia/IstanbulAsia/KashgarAsia/KatmanduAsia/MacaoAsia/RangoonAsia/SaigonAsia/Tel_AvivAsia/ThimbuAsia/Ujung_PandangAsia/Ulan_BatorAtlantic/FaeroeAtlantic/Jan_MayenAustralia/ACTAustralia/CanberraAustralia/CurrieAustralia/LHIAustralia/NorthAustralia/NSWAustralia/QueenslandAustralia/SouthAustralia/TasmaniaAustralia/VictoriaAustralia/WestAustralia/YancowinnaBrazil/AcreBrazil/DeNoronhaBrazil/EastBrazil/WestCanada/AtlanticCanada/CentralCanada/EasternCanada/MountainCanada/NewfoundlandCanada/PacificCanada/SaskatchewanCanada/YukonCETChile/ContinentalChile/EasterIslandCST6CDTCubaEETEgyptEireESTEST5EDTEtc/GMTEtc/GMT+0Etc/GMT+1Etc/GMT+10Etc/GMT+11Etc/GMT+12Etc/GMT+2Etc/GMT+3Etc/GMT+4Etc/GMT+5Etc/GMT+6Etc/GMT+7Etc/GMT+8Etc/GMT+9Etc/GMT-0Etc/GMT-1Etc/GMT-10Etc/GMT-11Etc/GMT-12Etc/GMT-13Etc/GMT-14Etc/GMT-2Etc/GMT-3Etc/GMT-4Etc/GMT-5Etc/GMT-6Etc/GMT-7Etc/GMT-8Etc/GMT-9Etc/GMT0Etc/GreenwichEtc/UCTEtc/UniversalEtc/UTCEtc/ZuluEurope/BelfastEurope/KievEurope/NicosiaEurope/TiraspolEurope/UzhgorodEurope/ZaporozhyeFactoryGBGB-EireGMTGMT+0GMT-0GMT0GreenwichHongkongHSTIcelandIranIsraelJamaicaJapanKwajaleinLibyaMETMexico/BajaNorteMexico/BajaSurMexico/GeneralMSTMST7MDTNavajoNZNZ-CHATPacific/EnderburyPacific/JohnstonPacific/PonapePacific/SamoaPacific/TrukPacific/YapPolandPortugalPRCPST8PDTROCROKSingaporeTurkeyUCTUniversalUS/AlaskaUS/AleutianUS/ArizonaUS/CentralUS/East-IndianaUS/EasternUS/HawaiiUS/Indiana-StarkeUS/MichiganUS/MountainUS/PacificUS/SamoaUTCW-SUWETZulu
Официальный список Другие и совместимые имена; 180 имён в сохранённом снимке 2026.5. Вернуться к выбору списка.
Практика: диагностика времени заказа без угадывания региона
Задача: заказ записан как 2024-07-15T12:00:00Z, филиал — Europe/Berlin. Получите местную строку, имя выбранного региона и его смещение. Затем замените регион на Asia/Kathmandu. До запуска предскажите, почему вариант «прибавить целое число часов» неверен для второй настройки.
- Исходный момент с Z → однозначная дата
- Настройка филиала → проверенный регион
- setTimezone → местная строка; getOffset → диагностика
- Сохраняем момент исходным, подпись создаём для читателя
<?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. Ошибку формы можно превратить в понятное сообщение, а сломанный объект требует исправления программы.
- Неразбираемая строка → DateMalformedStringException
- Неправильная зона → DateInvalidTimeZoneException
- Не вызван родительский конструктор → DateObjectError
- Выход за диапазон → 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 февраля способен нормализоваться. Отдельный строгий пример ниже проверяет именно календарную дату.
| Класс | Родитель |
|---|---|
| DateError | Error |
| DateObjectError | DateError |
| DateRangeError | DateError |
| DateException | Exception |
| DateInvalidOperationException | DateException |
| DateInvalidTimeZoneException | DateException |
| DateMalformedIntervalStringException | DateException |
| DateMalformedPeriodStringException | DateException |
| DateMalformedStringException | DateException |
Не все сбои date превращены в DateException: неправильный PHP-тип может дать TypeError, NULL-байт в некоторых API — ValueError, процедурные аналоги могут сохранить warning/false. Проверяйте договор конкретной функции. Как работают try/catch.
DateError: когда возникает и что делать
Сбой внутренней базы часовых поясов или родитель ошибок объекта/диапазона. Повреждение базы не имитируем изменением файлов PHP. Все девять классов этой иерархии введены в PHP 8.3. У них нет новых собственных публичных полей или методов; поведение унаследовано от Error.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateError
- 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 возвращает заданную строку для технического журнала, а не готовый текст публичной страницы.
<?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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateObjectError
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateRangeError
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateException
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateInvalidOperationException
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateInvalidTimeZoneException
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateMalformedIntervalStringException
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateMalformedPeriodStringException
- 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.
- Выполняется операция даты
- Нарушается её договор
- PHP создаёт DateMalformedStringException
- catch подходящего типа выбирает обработку
<?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
Процедурные функции сгруппированы здесь для поиска по имени. Часть является аналогами методов, но обработка ошибок и изменяемость могут отличаться. Каждая ссылка ведёт к собственным параметрам, результатам и примерам. Название функции не должно заставлять угадывать её связь с объектом.
- Импорт строки → date_create_immutable / date_parse
- Показать подпись → date_format / date / gmdate
- Работать с зонами → timezone_*
- Календарь и часы → остальные функции ниже
| Функция | Назначение |
|---|---|
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 для этого не подходит.
- 2024-06-15T07:30:00+00:00 пришло строкой JSON
- Строгий разбор и проверка диагностики
- Europe/Moscow → 10:30 для посетителя
- В ответе снова согласованная строка со смещением
<?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Создать дату из строки и явно выбранного часового пояса.
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 используются фактические микросекунды текущих часов. |
- Строка 2024-06-15 10:30:00 и zone=Europe/Moscow
- new DateTime создаёт meeting
- meeting->format(d.m.Y H:i:s P) возвращает label
- 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, сохранив дробную часть до микросекунд.
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 с отдельным договором. |
- Событие EVT-41 прислало 1704067200.125 секунд Unix
- DateTime::createFromTimestamp получает timestamp
- eventAt → 2024-01-01 00:00:00.125000 +00:00
- 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Создать дату из строки и явно выбранного часового пояса.
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 используются фактические микросекунды текущих часов. |
- Строка 2024-06-15 10:30:00 и zone=Europe/Moscow
- new DateTimeImmutable создаёт meeting
- meeting->format(d.m.Y H:i:s P) возвращает label
- 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, сохранив дробную часть до микросекунд.
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 с отдельным договором. |
- Событие EVT-41 прислало 1704067200.125 секунд Unix
- DateTimeImmutable::createFromTimestamp получает timestamp
- eventAt → 2024-01-01 00:00:00.125000 +00:00
- 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 с тем же моментом и зоной.
public static function createFromImmutable(DateTimeImmutable $object): staticПолучить независимый объект DateTime с тем же моментом и зоной.
| Часть вызова | Договор |
|---|---|
| object | Объект DateTimeImmutable. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется. |
| Возврат | Новый DateTime. С PHP 8 возвращается вызываемый класс; раньше базовый DateTime. |
| Смысл | Перейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом. |
- original: 15 июня, 10:30:00.125000, Europe/Moscow
- DateTime::createFromImmutable(original) создаёт независимый converted
- modify(+1 day) даёт reminder16 июня
- 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 с тем же моментом и зоной.
public static function createFromMutable(DateTime $object): staticПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.
| Часть вызова | Договор |
|---|---|
| object | Объект DateTime. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется. |
| Возврат | Новый DateTimeImmutable. С PHP 8 возвращается вызываемый класс; раньше базовый DateTimeImmutable. |
| Смысл | Перейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом. |
- original: 15 июня, 10:30:00.125000, Europe/Moscow
- DateTimeImmutable::createFromMutable(original) создаёт независимый converted
- modify(+1 day) даёт reminder16 июня
- 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 с тем же моментом и зоной.
public static function createFromInterface(DateTimeInterface $object): DateTimeПолучить независимый объект DateTime с тем же моментом и зоной.
| Часть вызова | Договор |
|---|---|
| object | Объект DateTimeInterface. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется. |
| Возврат | Новый DateTime. В опубликованной сигнатуре указан базовый тип. В проверенном PHP 8.5.11 вызов через подкласс возвращает подкласс: базовый возвращаемый тип допускает потомка. Не следует выводить обратное только из отсутствия static в сигнатуре. |
| Смысл | Перейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом. |
- original: 15 июня, 10:30:00.125000, Europe/Moscow
- DateTime::createFromInterface(original) создаёт независимый converted
- modify(+1 day) даёт reminder16 июня
- 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 с тем же моментом и зоной.
public static function createFromInterface(DateTimeInterface $object): DateTimeImmutableПолучить независимый объект DateTimeImmutable с тем же моментом и зоной.
| Часть вызова | Договор |
|---|---|
| object | Объект DateTimeInterface. Его дата, время, микросекунды и зона копируются в новый объект. Исходный объект не изменяется. |
| Возврат | Новый DateTimeImmutable. В опубликованной сигнатуре указан базовый тип. В проверенном PHP 8.5.11 вызов через подкласс возвращает подкласс: базовый возвращаемый тип допускает потомка. Не следует выводить обратное только из отсутствия static в сигнатуре. |
| Смысл | Перейти между изменяемой/неизменяемой моделью или сделать независимую копию известного интерфейса. DateTimeInterface нельзя реализовать обычным пользовательским классом. |
- original: 15 июня, 10:30:00.125000, Europe/Moscow
- DateTimeImmutable::createFromInterface(original) создаёт независимый converted
- modify(+1 day) даёт reminder16 июня
- 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 при неразбираемой строке.
function date_create(string $datetime = "now", ?DateTimeZone $timezone = null): DateTime|falseПроцедурно создать объект даты и проверить false при неразбираемой строке.
| Часть вызова | Договор |
|---|---|
| datetime/timezone | Те же правила строки/зоны, что у DateTime::__construct: now по умолчанию, явная зона в строке и @timestamp имеют приоритет. |
| Возврат | DateTime при успехе или false при ошибке разбора. Это важное отличие от new, который выбрасывает исключение. |
| Проверка | Используйте === false. Нормализация невозможной календарной даты может всё равно дать объект с предупреждением. |
- date_create получает известную дату и UTC
- Возврат DateTime попадает в date
- Проверка date===false ложна → else
- 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 при неразбираемой строке.
function date_create_immutable(string $datetime = "now", ?DateTimeZone $timezone = null): DateTimeImmutable|falseПроцедурно создать объект даты и проверить false при неразбираемой строке.
| Часть вызова | Договор |
|---|---|
| datetime/timezone | Те же правила строки/зоны, что у DateTimeImmutable::__construct: now по умолчанию, явная зона в строке и @timestamp имеют приоритет. |
| Возврат | DateTimeImmutable при успехе или false при ошибке разбора. Это важное отличие от new, который выбрасывает исключение. |
| Проверка | Используйте === false. Нормализация невозможной календарной даты может всё равно дать объект с предупреждением. |
- date_create_immutable получает известную дату и UTC
- Возврат DateTimeImmutable попадает в date
- Проверка date===false ложна → else
- 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Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
public static function createFromFormat(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|falseРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
| Часть вызова | Договор |
|---|---|
| format | Шаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы. |
| datetime | Входная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться. |
| timezone | DateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP. |
| Возврат | DateTime или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря. |
| Ошибки | NULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors. |
- input содержит 15.06.2024
- DateTime::createFromFormat читает !d.m.Y и получает UTC
- date содержит объект 2024-06-15 00:00:00
- 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.
<?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Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
function date_create_from_format(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTime|falseРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
| Часть вызова | Договор |
|---|---|
| format | Шаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы. |
| datetime | Входная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться. |
| timezone | DateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP. |
| Возврат | DateTime или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря. |
| Ошибки | NULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors. |
- input содержит 15.06.2024
- date_create_from_format читает !d.m.Y и получает UTC
- date содержит объект 2024-06-15 00:00:00
- 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.
<?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Разобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
function date_create_immutable_from_format(string $format, string $datetime, ?DateTimeZone $timezone = null): DateTimeImmutable|falseРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
| Часть вызова | Договор |
|---|---|
| format | Шаблон разбирается слева направо. ! сначала сбрасывает поля; | в конце сбрасывает непрочитанные. Полный список обозначений находится ниже и в отдельной таблице главы. |
| datetime | Входная строка, соответствующая шаблону. Числовые поля читаются жадно и могут переполняться. |
| timezone | DateTimeZone или null. Если строка задаёт зону/Unix timestamp, он имеет приоритет; иначе берётся переданная или текущая зона PHP. |
| Возврат | DateTimeImmutable или false. Объект может существовать при предупреждениях; это не автоматическая валидация календаря. |
| Ошибки | NULL-байт во входе вызывает ValueError с PHP 8.0.21 / 8.1.8 / 8.2.0. Обычная ошибка разбора возвращает false; диагностические массивы читаются через getLastErrors. |
- input содержит 15.06.2024
- date_create_immutable_from_format читает !d.m.Y и получает UTC
- date содержит объект 2024-06-15 00:00:00
- 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.
<?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Сразу прочитать предупреждения и ошибки последнего разбора даты.
public static function getLastErrors(): array|falseСразу прочитать предупреждения и ошибки последнего разбора даты.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. Это состояние последнего разбора, а не поле выбранного объекта. |
| Успех | С PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками. |
| Диагностика | warning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива. |
| Порядок вызовов | Сохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику. |
- createFromFormat читает невозможное 2024-02-30
- Объект становится 2024-03-01, появляется предупреждение
- DateTime::getLastErrors() сохраняет массив в issues
- 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Сразу прочитать предупреждения и ошибки последнего разбора даты.
public static function getLastErrors(): array|falseСразу прочитать предупреждения и ошибки последнего разбора даты.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. Это состояние последнего разбора, а не поле выбранного объекта. |
| Успех | С PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками. |
| Диагностика | warning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива. |
| Порядок вызовов | Сохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику. |
- createFromFormat читает невозможное 2024-02-30
- Объект становится 2024-03-01, появляется предупреждение
- DateTimeImmutable::getLastErrors() сохраняет массив в issues
- 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Сразу прочитать предупреждения и ошибки последнего разбора даты.
function date_get_last_errors(): array|falseСразу прочитать предупреждения и ошибки последнего разбора даты.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. Это состояние последнего разбора, а не поле выбранного объекта. |
| Успех | С PHP 8.2 false, когда нет предупреждений и ошибок. До 8.2 возвращалась структура с нулевыми счётчиками. |
| Диагностика | warning_count/warnings и error_count/errors. Ключи сообщений указывают позиции во входе; несколько проблем могут относиться к одной позиции, поэтому счётчик не обязан равняться count массива. |
| Порядок вызовов | Сохраняйте результат сразу. Следующий успешный разбор заменит прошлую диагностику. |
- createFromFormat читает невозможное 2024-02-30
- Объект становится 2024-03-01, появляется предупреждение
- date_get_last_errors() сохраняет массив в issues
- 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Разобрать строку в массив обнаруженных компонентов и диагностики.
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. Эти числа ещё надо применить к конкретной исходной дате. |
- Строка содержит дату, 10:30 и смещение +03:00
- date_parse возвращает ассоциативный parts
- year/month/day → sprintf → events-2024-06-15.csv
- 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Разобрать строку в массив обнаруженных компонентов и диагностики.
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. Эти числа ещё надо применить к конкретной исходной дате. |
- Строка содержит дату, 10:30 и смещение +03:00
- date_parse_from_format возвращает ассоциативный parts
- year/month/day → sprintf → events-2024-06-15.csv
- 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.
Время, часовые пояса и календарь. Практические задачи с датами.
<?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Получить строку даты для человека или согласованного внешнего формата.
public function format(string $format): stringПолучить строку даты для человека или согласованного внешнего формата.
| Часть вызова | Договор |
|---|---|
| format | Строка шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов. |
| Возврат | Строка; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону. |
| Язык | Названия дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter. |
| Доли секунды | u/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её. |
- date = 15 июня 2024, 10:30 UTC
- Шаблон d.m.Y H:i выбирает день, месяц, год и часы
- format возвращает строку 15.06.2024 10:30 в label
- 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 год нужен расширенный год.
<?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Получить строку даты для человека или согласованного внешнего формата.
public function format(string $format): stringПолучить строку даты для человека или согласованного внешнего формата.
| Часть вызова | Договор |
|---|---|
| format | Строка шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов. |
| Возврат | Строка; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону. |
| Язык | Названия дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter. |
| Доли секунды | u/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её. |
- date = 15 июня 2024, 10:30 UTC
- Шаблон d.m.Y H:i выбирает день, месяц, год и часы
- format возвращает строку 15.06.2024 10:30 в label
- 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 год нужен расширенный год.
<?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Получить строку даты для человека или согласованного внешнего формата.
function date_format(DateTimeInterface $object, string $format): stringПолучить строку даты для человека или согласованного внешнего формата.
| Часть вызова | Договор |
|---|---|
| object | DateTime или DateTimeImmutable через общий DateTimeInterface. Старое краткое описание процедурного стиля, упоминающее лишь date_create, не ограничивает сигнатуру одним изменяемым классом. |
| format | Строка шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов. |
| Возврат | Строка; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону. |
| Язык | Названия дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter. |
| Доли секунды | u/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её. |
- date = 15 июня 2024, 10:30 UTC
- Шаблон d.m.Y H:i выбирает день, месяц, год и часы
- format возвращает строку 15.06.2024 10:30 в label
- 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 год нужен расширенный год.
<?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 для момента, независимо от отображаемой зоны.
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 может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака. |
- expires:13:00 +03:00 =10:00 UTC
- Два вызова getTimestamp возвращают целые секунды
- deadlineSeconds − nowSeconds =900
- 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 для момента, независимо от отображаемой зоны.
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 может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака. |
- expires:13:00 +03:00 =10:00 UTC
- Два вызова getTimestamp возвращают целые секунды
- deadlineSeconds − nowSeconds =900
- 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 для момента, независимо от отображаемой зоны.
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 может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака. |
- expires:13:00 +03:00 =10:00 UTC
- Два вызова getTimestamp возвращают целые секунды
- deadlineSeconds − nowSeconds =900
- 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 для момента, независимо от отображаемой зоны.
function date_timestamp_get(DateTimeInterface $object): intПолучить целые секунды Unix для момента, независимо от отображаемой зоны.
| Часть вызова | Договор |
|---|---|
| object | DateTime или 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 может переполниться; отрицательные секунды с дробью требуют отдельного понимания знака. |
- expires:13:00 +03:00 =10:00 UTC
- Два вызова getTimestamp возвращают целые секунды
- deadlineSeconds − nowSeconds =900
- 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-смещение зоны именно на дату объекта.
public function getOffset(): intПрочитать UTC-смещение зоны именно на дату объекта.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int секунд от UTC: восточнее положительный, западнее отрицательный. |
| Граница | Это смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны. |
- Две встречи:15 января и 15 июля, обе 10:00 London
- getOffset читает правило на дату:0 /3600 секунд
- Отдельный setTimezone(UTC) даёт 10:00 /09:00
- Две строки объясняют участнику зимнее и летнее время
<?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-смещение зоны именно на дату объекта.
public function getOffset(): intПрочитать UTC-смещение зоны именно на дату объекта.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int секунд от UTC: восточнее положительный, западнее отрицательный. |
| Граница | Это смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны. |
- Две встречи:15 января и 15 июля, обе 10:00 London
- getOffset читает правило на дату:0 /3600 секунд
- Отдельный setTimezone(UTC) даёт 10:00 /09:00
- Две строки объясняют участнику зимнее и летнее время
<?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-смещение зоны именно на дату объекта.
public function getOffset(): intПрочитать UTC-смещение зоны именно на дату объекта.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int секунд от UTC: восточнее положительный, западнее отрицательный. |
| Граница | Это смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны. |
- Две встречи:15 января и 15 июля, обе 10:00 London
- getOffset читает правило на дату:0 /3600 секунд
- Отдельный setTimezone(UTC) даёт 10:00 /09:00
- Две строки объясняют участнику зимнее и летнее время
<?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-смещение зоны именно на дату объекта.
function date_offset_get(DateTimeInterface $object): intПрочитать UTC-смещение зоны именно на дату объекта.
| Часть вызова | Договор |
|---|---|
| object | DateTime или DateTimeImmutable. |
| Возврат | int секунд от UTC: восточнее положительный, западнее отрицательный. |
| Граница | Это смещение на конкретный момент. В одной именованной зоне оно может измениться из-за DST/исторических правил; не храните его вместо идентификатора зоны. |
- Две встречи:15 января и 15 июля, обе 10:00 London
- getOffset читает правило на дату:0 /3600 секунд
- Отдельный setTimezone(UTC) даёт 10:00 /09:00
- Две строки объясняют участнику зимнее и летнее время
<?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Получить объект часового пояса, связанный с датой.
public function getTimezone(): DateTimeZone|falseПолучить объект часового пояса, связанный с датой.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | DateTimeZone при успехе, в опубликованном договоре допускается false при неудаче. |
| Что хранится | Именованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона. |
- first хранит 15 января 10:00 и Europe/London
- getTimezone возвращает объект zone
- new дата 15 июля +тот же zone →10:00 +01:00
- 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Получить объект часового пояса, связанный с датой.
public function getTimezone(): DateTimeZone|falseПолучить объект часового пояса, связанный с датой.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | DateTimeZone при успехе, в опубликованном договоре допускается false при неудаче. |
| Что хранится | Именованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона. |
- first хранит 15 января 10:00 и Europe/London
- getTimezone возвращает объект zone
- new дата 15 июля +тот же zone →10:00 +01:00
- 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Получить объект часового пояса, связанный с датой.
public function getTimezone(): DateTimeZone|falseПолучить объект часового пояса, связанный с датой.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | DateTimeZone при успехе, в опубликованном договоре допускается false при неудаче. |
| Что хранится | Именованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона. |
- first хранит 15 января 10:00 и Europe/London
- getTimezone возвращает объект zone
- new дата 15 июля +тот же zone →10:00 +01:00
- 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Получить объект часового пояса, связанный с датой.
function date_timezone_get(DateTimeInterface $object): DateTimeZone|falseПолучить объект часового пояса, связанный с датой.
| Часть вызова | Договор |
|---|---|
| object | DateTime или DateTimeImmutable. |
| Возврат | DateTimeZone при успехе, в опубликованном договоре допускается false при неудаче. |
| Что хранится | Именованная зона либо фиксированное смещение/сокращение из исходного объекта. getName не обязан возвращать название географического региона. |
- first хранит 15 января 10:00 и Europe/London
- getTimezone возвращает объект zone
- new дата 15 июля +тот же zone →10:00 +01:00
- 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Получить целое число микросекунд внутри секунды.
public function getMicrosecond(): intПолучить целое число микросекунд внутри секунды.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах. |
| Различие | format("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления. |
| Версия | PHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением. |
- Два события в 12:34:56 UTC с долями 000123 /000323
- getMicrosecond возвращает int123 /323
- secondPart − firstPart →gap=200
- 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Получить целое число микросекунд внутри секунды.
public function getMicrosecond(): intПолучить целое число микросекунд внутри секунды.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах. |
| Различие | format("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления. |
| Версия | PHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением. |
- Два события в 12:34:56 UTC с долями 000123 /000323
- getMicrosecond возвращает int123 /323
- secondPart − firstPart →gap=200
- 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Получить целое число микросекунд внутри секунды.
public function getMicrosecond(): intПолучить целое число микросекунд внутри секунды.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | int дробной микросекундной части, 0…999999. Это не весь timestamp в микросекундах. |
| Различие | format("u") возвращает строку из шести цифр с нулями; getMicrosecond возвращает число без оформления. |
| Версия | PHP 8.4+. Для старого кода доступна строка format("u") с осознанным приведением. |
- Два события в 12:34:56 UTC с долями 000123 /000323
- getMicrosecond возвращает int123 /323
- secondPart − firstPart →gap=200
- 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Поставить конкретный год, месяц и день, сохранив время суток и зону.
public function setDate(int $year, int $month, int $day): DateTimeПоставить конкретный год, месяц и день, сохранив время суток и зону.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| year / month / day | Три целых числа календарной даты. Выход за диапазон нормализуется: это не автоматическая проверка поля формы. |
| Проверка | Для пользовательских частей сначала checkdate(month, day, year); порядок аргументов у checkdate другой. |
- date:15 января 2024, 10:30 UTC
- setDate получает 2024, 2, 29
- next:29 февраля 2024, 10:30 UTC
- 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Поставить конкретный год, месяц и день, сохранив время суток и зону.
public function setDate(int $year, int $month, int $day): DateTimeImmutableПоставить конкретный год, месяц и день, сохранив время суток и зону.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение. |
| year / month / day | Три целых числа календарной даты. Выход за диапазон нормализуется: это не автоматическая проверка поля формы. |
| Проверка | Для пользовательских частей сначала checkdate(month, day, year); порядок аргументов у checkdate другой. |
- date:15 января 2024, 10:30 UTC
- setDate получает 2024, 2, 29
- next:29 февраля 2024, 10:30 UTC
- 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Поставить конкретный год, месяц и день, сохранив время суток и зону.
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 другой. |
- date:15 января 2024, 10:30 UTC
- setDate получает 2024, 2, 29
- next:29 февраля 2024, 10:30 UTC
- 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-году, номеру недели и дню недели.
public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeНайти дату по ISO-году, номеру недели и дню недели.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| year | Год ISO-недели; около Нового года он может отличаться от календарного года даты. |
| week | Номер ISO-недели, обычно 1…52/53. |
| dayOfWeek | 1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы. |
| Часы | Время суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0). |
- Выбор:ISO-год 2025, неделя 1
- setISODate(2025,1) →понедельник 30.12.2024
- setTime(0,0) →начало дня
- 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-году, номеру недели и дню недели.
public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutableНайти дату по ISO-году, номеру недели и дню недели.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение. |
| year | Год ISO-недели; около Нового года он может отличаться от календарного года даты. |
| week | Номер ISO-недели, обычно 1…52/53. |
| dayOfWeek | 1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы. |
| Часы | Время суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0). |
- Выбор:ISO-год 2025, неделя 1
- setISODate(2025,1) →понедельник 30.12.2024
- setTime(0,0) →начало дня
- 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-году, номеру недели и дню недели.
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. |
| dayOfWeek | 1 — понедельник по умолчанию; 7 — воскресенье. Выход за диапазоны переносится в соседние недели/годы. |
| Часы | Время суток и зона исходного объекта сохраняются. Для начала дня отдельно setTime(0, 0). |
- Выбор:ISO-год 2025, неделя 1
- setISODate(2025,1) →понедельник 30.12.2024
- setTime(0,0) →начало дня
- 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Назначить часы, минуты, секунды и микросекунды на существующую дату.
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 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно. |
- date:15 июня,10:30:45.123456 UTC
- setTime получает 14,20 и defaults 0,0
- next:15 июня,14:20:00.000000 UTC
- 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Назначить часы, минуты, секунды и микросекунды на существующую дату.
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 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно. |
- date:15 июня,10:30:45.123456 UTC
- setTime получает 14,20 и defaults 0,0
- next:15 июня,14:20:00.000000 UTC
- 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Назначить часы, минуты, секунды и микросекунды на существующую дату.
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 неоднозначный повторный час при осеннем переводе выбирает первое вхождение; раньше выбиралось второе. Для важных встреч неоднозначность решают явно. |
- date:15 июня,10:30:45.123456 UTC
- setTime получает 14,20 и defaults 0,0
- next:15 июня,14:20:00.000000 UTC
- 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, сохранив зону отображения.
public function setTimestamp(int $timestamp): DateTimeУстановить точный момент по целым секундам Unix, сохранив зону отображения.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| timestamp | int секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int. |
| Доля | Установка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC. |
| Большие значения | Для значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона. |
- Источник:1704067200 секунд Unix
- setTimestamp заменяет момент, сохраняя Europe/Moscow
- next:1 января 2024,03:00 +03:00
- format →строка события EVT-41
У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.
Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.
<?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, сохранив зону отображения.
public function setTimestamp(int $timestamp): DateTimeImmutableУстановить точный момент по целым секундам Unix, сохранив зону отображения.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение. |
| timestamp | int секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int. |
| Доля | Установка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC. |
| Большие значения | Для значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона. |
- Источник:1704067200 секунд Unix
- setTimestamp заменяет момент, сохраняя Europe/Moscow
- next:1 января 2024,03:00 +03:00
- format →строка события EVT-41
У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.
Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.
<?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, сохранив зону отображения.
function date_timestamp_set(DateTime $object, int $timestamp): DateTimeУстановить точный момент по целым секундам Unix, сохранив зону отображения.
| Часть вызова | Договор |
|---|---|
| object | Именно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект. |
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| timestamp | int секунд от Unix epoch; это не миллисекунды. Ограничен разрядностью int. |
| Доля | Установка целых секунд сбрасывает прежнюю микросекундную часть. Для дробных секунд в PHP 8.4+ есть createFromTimestamp; он создаёт новый объект UTC. |
| Большие значения | Для значения за пределами int документация предлагает строковый @timestamp через modify. Не превращайте точную большую строку в float ради обхода диапазона. |
- Источник:1704067200 секунд Unix
- setTimestamp заменяет момент, сохраняя Europe/Moscow
- next:1 января 2024,03:00 +03:00
- format →строка события EVT-41
У immutable-объекта изменение создаёт следующий вариант, поэтому используйте $next = $date->setTimestamp(...). У изменяемого DateTime метод меняет сам объект. Присваивание $other = $date не делает копию; для независимого изменяемого варианта нужен clone. Как переменная связана с объектом.
Источник сообщил целое число секунд Unix. У объекта уже выбрана зона журнала Europe/Moscow. SetTimestamp принимает именно секунды: число миллисекунд без преобразования означало бы совершенно другой момент.
<?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Показать тот же момент на часах другого региона.
public function setTimezone(DateTimeZone $timezone): DateTimeПоказать тот же момент на часах другого региона.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| timezone | Подготовленный DateTimeZone с нужным идентификатором или смещением. |
| Инвариант | Момент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение. |
| Граница | Это не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной. |
- API:15 июня 07:00 UTC
- DateTimeZone(Europe/Moscow) →правила отображения
- setTimezone возвращает local:10:00 +03:00
- Подпись другая, прошедший сдвиг события =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Показать тот же момент на часах другого региона.
public function setTimezone(DateTimeZone $timezone): DateTimeImmutableПоказать тот же момент на часах другого региона.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение. |
| timezone | Подготовленный DateTimeZone с нужным идентификатором или смещением. |
| Инвариант | Момент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение. |
| Граница | Это не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной. |
- API:15 июня 07:00 UTC
- DateTimeZone(Europe/Moscow) →правила отображения
- setTimezone возвращает local:10:00 +03:00
- Подпись другая, прошедший сдвиг события =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Показать тот же момент на часах другого региона.
function date_timezone_set(DateTime $object, DateTimeZone $timezone): DateTimeПоказать тот же момент на часах другого региона.
| Часть вызова | Договор |
|---|---|
| object | Именно изменяемый DateTime. DateTimeImmutable не подходит: функция должна изменить переданный объект. |
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| timezone | Подготовленный DateTimeZone с нужным идентификатором или смещением. |
| Инвариант | Момент и Unix timestamp сохраняются. Меняются местные часы/дата/смещение. |
| Граница | Это не исправление ошибочно разобранного местного времени. Если 10:00 было в Москве, но прочитано как UTC, нужно заново интерпретировать исходную строку с верной зоной. |
- API:15 июня 07:00 UTC
- DateTimeZone(Europe/Moscow) →правила отображения
- setTimezone возвращает local:10:00 +03:00
- Подпись другая, прошедший сдвиг события =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Заменить только микросекундную часть, не меняя целые секунды.
public function setMicrosecond(int $microsecond): staticЗаменить только микросекундную часть, не меняя целые секунды.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же изменённый DateTime; возвращаемое значение удобно для цепочки методов. Все переменные, указывающие на этот объект, увидят изменение. |
| microsecond | int от 0 до 999999. Это число микросекунд, не миллисекунд. |
| Ошибка | DateRangeError при выходе за диапазон; в отличие от setTime, этот метод не нормализует 1000000 в следующую секунду. |
| Версия | PHP 8.4+. Возврат static сохраняет класс объекта. |
- date:15 июня 10:30:00.123456 UTC
- setMicrosecond получает 250000
- next:та же секунда с долей.250000
- 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Заменить только микросекундную часть, не меняя целые секунды.
public function setMicrosecond(int $microsecond): staticЗаменить только микросекундную часть, не меняя целые секунды.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый DateTimeImmutable; исходный объект остаётся прежним. Сохраните возвращённое значение. |
| microsecond | int от 0 до 999999. Это число микросекунд, не миллисекунд. |
| Ошибка | DateRangeError при выходе за диапазон; в отличие от setTime, этот метод не нормализует 1000000 в следующую секунду. |
| Версия | PHP 8.4+. Возврат static сохраняет класс объекта. |
- date:15 июня 10:30:00.123456 UTC
- setMicrosecond получает 250000
- next:та же секунда с долей.250000
- 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Изменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
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. |
| Месяцы | Несуществующий день после сдвига месяца переносится дальше; автоматического ограничения последним днём нет. |
- date:15.06.2024 10:30 UTC
- modify получает строку +1 day
- next:16.06.2024 10:30 UTC
- 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Изменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
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. |
| Месяцы | Несуществующий день после сдвига месяца переносится дальше; автоматического ограничения последним днём нет. |
- date:15.06.2024 10:30 UTC
- modify получает строку +1 day
- next:16.06.2024 10:30 UTC
- 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, а не надежда на строгость гибкого парсера.
<?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Прибавить подготовленный календарный интервал или прошедшую длительность.
public function add(DateInterval $interval): DateTimeПрибавить подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же DateTime после изменения; вторая переменная с этим объектом увидит изменения. |
| interval | DateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику. |
| Месяц | P1M — календарный месяц, не постоянное число секунд и не ограничение последним днём. |
| Текстовое правило | createFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- add(interval) →next:17 июня 10:30
- 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Прибавить подготовленный календарный интервал или прошедшую длительность.
public function add(DateInterval $interval): DateTimeImmutableПрибавить подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый объект; исходный не меняется. Сохраните результат. |
| interval | DateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику. |
| Месяц | P1M — календарный месяц, не постоянное число секунд и не ограничение последним днём. |
| Текстовое правило | createFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- add(interval) →next:17 июня 10:30
- 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Прибавить подготовленный календарный интервал или прошедшую длительность.
function date_add(DateTime $object, DateInterval $interval): DateTimeПрибавить подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| object | Изменяемый DateTime. Функция не принимает DateTimeImmutable. |
| Возврат | Тот же DateTime после изменения; вторая переменная с этим объектом увидит изменения. |
| interval | DateInterval: P2D — два календарных дня, PT24H — 24 прошедших часа. Свойство invert меняет направление. Созданные из текстовой строки интервалы могут иметь другую временную семантику. |
| Месяц | P1M — календарный месяц, не постоянное число секунд и не ограничение последним днём. |
| Текстовое правило | createFromDateString("last day of next month") с add не равнозначен modify с той же строкой: для 2026-01-15 получаются соответственно 2026-02-15 и 2026-02-28. Специальный компонент «последний день» не применяется add как в modify. Проверенный пример и ограничения текстовых интервалов. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- add(interval) →next:17 июня 10:30
- 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Вычесть подготовленный календарный интервал или прошедшую длительность.
public function sub(DateInterval $interval): DateTimeВычесть подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| Возврат | Тот же DateTime после изменения; вторая переменная с этим объектом увидит изменения. |
| interval | DateInterval: 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 и возвращает объект без такого изменения. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- sub(interval) →next:13 июня 10:30
- 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Вычесть подготовленный календарный интервал или прошедшую длительность.
public function sub(DateInterval $interval): DateTimeImmutableВычесть подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| Возврат | Новый объект; исходный не меняется. Сохраните результат. |
| interval | DateInterval: 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 и возвращает объект без такого изменения. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- sub(interval) →next:13 июня 10:30
- 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Вычесть подготовленный календарный интервал или прошедшую длительность.
function date_sub(DateTime $object, DateInterval $interval): DateTimeВычесть подготовленный календарный интервал или прошедшую длительность.
| Часть вызова | Договор |
|---|---|
| object | Изменяемый DateTime. Функция не принимает DateTimeImmutable. |
| Возврат | Тот же DateTime после изменения; вторая переменная с этим объектом увидит изменения. |
| interval | DateInterval: 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 и возвращает объект без такого изменения. |
- date:15 июня 10:30 UTC
- new DateInterval(P2D) →interval:два дня
- sub(interval) →next:13 июня 10:30
- 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Получить календарный интервал, переводящий исходную дату в целевую.
public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateIntervalПолучить календарный интервал, переводящий исходную дату в целевую.
| Часть вызова | Договор |
|---|---|
| targetObject | Целевая дата через общий DateTimeInterface. |
| absolute | По умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд. |
| Возврат | DateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются. |
| DST и обратимость | Разность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима. |
- checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
- diff возвращает DateInterval в stay
- stay->days =3 →nights
- 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Получить календарный интервал, переводящий исходную дату в целевую.
public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateIntervalПолучить календарный интервал, переводящий исходную дату в целевую.
| Часть вызова | Договор |
|---|---|
| targetObject | Целевая дата через общий DateTimeInterface. |
| absolute | По умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд. |
| Возврат | DateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются. |
| DST и обратимость | Разность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима. |
- checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
- diff возвращает DateInterval в stay
- stay->days =3 →nights
- 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Получить календарный интервал, переводящий исходную дату в целевую.
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. Календарная операция не всегда обратима. |
- checkIn:15 июня, checkOut:18 июня, обе 00:00 UTC
- diff возвращает DateInterval в stay
- stay->days =3 →nights
- 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.
public function __serialize(): arrayПодготовить внутренний массив даты для механизма serialize.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. Обычно вызов делает PHP при serialize($date). |
| Возврат | array состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление. |
| Ошибка | Неинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения. |
| Расширение | Если подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override. |
- appointment: 15 июня 10:30:00.125456 Europe/Moscow
- __serialize() → массив state
- date / timezone / timezone_type читаются по ключам
- Исходная дата всё ещё даёт подпись записи
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного 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Восстановить внутреннее состояние даты из согласованного массива сериализации.
public function __unserialize(array $data): voidВосстановить внутреннее состояние даты из согласованного массива сериализации.
| Часть вызова | Договор |
|---|---|
| data | array штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка. |
| Возврат | void. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода. |
| Ошибки | Повреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление. |
| Immutable | Прямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде. |
- source → state с датой 15 июня
- target пока содержит 1 января 2000 года
- target->__unserialize(state) меняет target
- target->format показывает 15 июня; result равен null
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.
<?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Распознать устаревший обработчик восстановления даты в старом коде.
public function __wakeup(): voidРаспознать устаревший обработчик восстановления даты в старом коде.
| Часть вызова | Договор |
|---|---|
| Параметры / возврат | Нет параметров, void; исторически инициализировал дату из восстановленных свойств. |
| Статус | Deprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату. |
| Граница | Прямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error. |
- Старый код содержит __wakeup
- PHP 8.5 помечает метод устаревшим
- Современный объект имеет __serialize/__unserialize
- Приложение пользуется согласованным маршрутом сохранения
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
<?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 этот атрибут отсутствует; проверяйте версию при анализе старого проекта.
<?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.
public function __serialize(): arrayПодготовить внутренний массив даты для механизма serialize.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. Обычно вызов делает PHP при serialize($date). |
| Возврат | array состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление. |
| Ошибка | Неинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения. |
| Расширение | Если подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override. |
- appointment: 15 июня 10:30:00.125456 Europe/Moscow
- __serialize() → массив state
- date / timezone / timezone_type читаются по ключам
- Исходная дата всё ещё даёт подпись записи
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного 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Восстановить внутреннее состояние даты из согласованного массива сериализации.
public function __unserialize(array $data): voidВосстановить внутреннее состояние даты из согласованного массива сериализации.
| Часть вызова | Договор |
|---|---|
| data | array штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка. |
| Возврат | void. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода. |
| Ошибки | Повреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление. |
| Immutable | Прямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде. |
- source → state с датой 15 июня
- target пока содержит 1 января 2000 года
- target->__unserialize(state) меняет target
- target->format показывает 15 июня; result равен null
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.
<?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Распознать устаревший обработчик восстановления даты в старом коде.
public function __wakeup(): voidРаспознать устаревший обработчик восстановления даты в старом коде.
| Часть вызова | Договор |
|---|---|
| Параметры / возврат | Нет параметров, void; исторически инициализировал дату из восстановленных свойств. |
| Статус | Deprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату. |
| Граница | Прямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error. |
- Старый код содержит __wakeup
- PHP 8.5 помечает метод устаревшим
- Современный объект имеет __serialize/__unserialize
- Приложение пользуется согласованным маршрутом сохранения
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
<?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 этот атрибут отсутствует; проверяйте версию при анализе старого проекта.
<?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.
public function __serialize(): arrayПодготовить внутренний массив даты для механизма serialize.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. Обычно вызов делает PHP при serialize($date). |
| Возврат | array состояния, не готовая строка. Встроенный объект включает date, timezone_type и timezone; serialize дополнительно кодирует этот массив в своё представление. |
| Ошибка | Неинициализированный объект даты в современной ветке PHP вызывает DateObjectError. Например, подкласс, не вызвавший родительский конструктор, ещё не имеет корректного внутреннего значения. |
| Расширение | Если подкласс меняет схему, ему нужен согласованный __unserialize. Одно переопределение, сохраняющее штатную схему parent::__serialize(), может работать с унаследованным обработчиком; ошибка определяется несовместимостью данных, а не одним фактом отсутствия override. |
- appointment: 15 июня 10:30:00.125456 Europe/Moscow
- __serialize() → массив state
- date / timezone / timezone_type читаются по ключам
- Исходная дата всё ещё даёт подпись записи
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного 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Восстановить внутреннее состояние даты из согласованного массива сериализации.
public function __unserialize(array $data): voidВосстановить внутреннее состояние даты из согласованного массива сериализации.
| Часть вызова | Договор |
|---|---|
| data | array штатного формата либо собственного формата, если подкласс реализует согласованную пару hooks. Это не сырая serialized-строка. |
| Возврат | void. Метод инициализирует объект; новый отдельный объект создаёт механизм unserialize, а не return этого метода. |
| Ошибки | Повреждённая штатная схема вызывает Error; неправильный тип аргумента — TypeError. Обрабатывайте некорректный доверенный снимок как повреждение данных, а не успешное восстановление. |
| Immutable | Прямой служебный вызов способен переинициализировать даже существующий DateTimeImmutable. Это не обычный метод редактирования даты: не используйте его для обхода ожидаемой неизменяемости в бизнес-коде. |
- source → state с датой 15 июня
- target пока содержит 1 января 2000 года
- target->__unserialize(state) меняет target
- target->format показывает 15 июня; result равен null
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
Теперь проследим обратный путь массива: что меняет __unserialize и где оказывается дата. Source отдаёт состояние, target принимает его. В отличие от add у Immutable здесь результат хранится в самом получателе, а не в возвращённом объекте.
<?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Распознать устаревший обработчик восстановления даты в старом коде.
public function __wakeup(): voidРаспознать устаревший обработчик восстановления даты в старом коде.
| Часть вызова | Договор |
|---|---|
| Параметры / возврат | Нет параметров, void; исторически инициализировал дату из восстановленных свойств. |
| Статус | Deprecated с PHP 8.5. У DateTime/Immutable есть __serialize и __unserialize с 8.2. Не вызывайте __wakeup вручную как способ «обновить» дату. |
| Граница | Прямой вызов у обычного уже созданного объекта не является корректным восстановлением: нужных восстановленных полей может не быть, возможен Error. |
- Старый код содержит __wakeup
- PHP 8.5 помечает метод устаревшим
- Современный объект имеет __serialize/__unserialize
- Приложение пользуется согласованным маршрутом сохранения
Это служебный протокол объекта для сохранения собственного доверенного состояния. Он не является форматом входного API. Никогда не передавайте пользовательскую строку в unserialize; allowed_classes не превращает недоверенную сериализацию в безопасную. Для API храните явно выбранную строку даты/зону и проверяйте их при чтении. Восстановление объектов и границы доверия.
<?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 этот атрибут отсутствует; проверяйте версию при анализе старого проекта.
<?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.
public static function __set_state(array $array): DateTimeСоздать дату из массива состояния, используемого экспортом var_export.
| Часть вызова | Договор |
|---|---|
| array | Массив инициализации даты. Форму для экспорта готовит PHP; это не свободная структура формы/API. |
| Возврат | Новый объект соответствующего класса. Старый объект не меняется. |
| Ошибки | Повреждённая штатная схема вызывает Error; аргумент, который не является массивом, — TypeError. |
| Применение | var_export($date, true) создаёт PHP-представление с вызовом __set_state. Не выполняйте внешний текст через eval; для обмена используйте явные данные. |
- date содержит дату и регион
- var_export(date,true) → строка PHP
- Строка содержит вызов DateTime::__set_state
- echo показывает текст; он пока не исполнялся
При чтении экспортированной PHP-конфигурации можно встретить Class::__set_state(array(...)). Это вызов фабрики: :: обращается к статическому методу, array(...) задаёт массив полей, результатом будет новый объект даты. Сначала выведем текст, затем вызовем фабрику с собственным массивом без исполнения строки.
<?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.
public static function __set_state(array $array): DateTimeImmutableСоздать дату из массива состояния, используемого экспортом var_export.
| Часть вызова | Договор |
|---|---|
| array | Массив инициализации даты. Форму для экспорта готовит PHP; это не свободная структура формы/API. |
| Возврат | Новый объект соответствующего класса. Старый объект не меняется. |
| Ошибки | Повреждённая штатная схема вызывает Error; аргумент, который не является массивом, — TypeError. |
| Применение | var_export($date, true) создаёт PHP-представление с вызовом __set_state. Не выполняйте внешний текст через eval; для обмена используйте явные данные. |
- date содержит дату и регион
- var_export(date,true) → строка PHP
- Строка содержит вызов DateTimeImmutable::__set_state
- echo показывает текст; он пока не исполнялся
При чтении экспортированной PHP-конфигурации можно встретить Class::__set_state(array(...)). Это вызов фабрики: :: обращается к статическому методу, array(...) задаёт массив полей, результатом будет новый объект даты. Сначала выведем текст, затем вызовем фабрику с собственным массивом без исполнения строки.
<?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Хранить правило изменения даты отдельно от конкретной даты доставки или встречи.
class DateIntervalХранить правило изменения даты отдельно от конкретной даты доставки или встречи.
| Часть вызова | Договор |
|---|---|
| y / m / d | Целые компоненты лет / месяцев / дней. m означает месяцы. |
| h / i / s | Часы / минуты / секунды; минуты обозначены i, не m. |
| f | Доля секунды float, например 0.125 означает 125000 микросекунд; свойство добавлено в PHP7.1. |
| invert | 1 означает отрицательное направление, 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 разобраны отдельно. |
- ordered:10 апреля 2026,09:00 UTC
- new DateInterval(P3D) →step:три дня
- ordered->add(step) →delivery:13 апреля 09:00
- Два 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.
new DateInterval(string $duration)Создать шаг по чёткой машинной записи ISO8601.
| Часть вызова | Договор |
|---|---|
| duration | Строка P…[T…] с целыми значениями и обозначениями единиц, в правильном порядке. |
| Результат new | Объект DateInterval; конструктор не возвращает дату. |
| Ошибка | DateMalformedIntervalStringException с PHP8.3, прежде Exception. Некорректный тип аргумента может дать TypeError. |
- start:10 апреля 09:00 UTC
- P1M /PT15M /P1W3DT2H →три объекта правил
- Каждое правило независимо передаётся start->add
- Полученные даты используются для трёх подписей
| Обозначение | Единица |
|---|---|
| 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Создать относительное календарное правило, например переход к следующему понедельнику.
DateInterval::createFromDateString(string $datetime): DateIntervalСоздать относительное календарное правило, например переход к следующему понедельнику.
| Часть вызова | Договор |
|---|---|
| datetime | Относительные части строки парсера DateTimeImmutable/DateTime/strtotime. Не ISO8601 P…; язык и грамматика парсера не являются произвольным русским текстом. |
| Возврат | DateInterval; доPHP8.3 при отказе мог возвращаться false. |
| Отказ | ООП с8.3 бросает DateMalformedIntervalStringException; до8.3 предупреждение+false. Процедурная функция ведёт себя по старому договору. |
- next monday →статическая фабрика
- Возврат DateInterval →rule
- 15 января 2026 +rule →19 января 2026
- 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 это разные результаты. Полный опыт на переходе времени.
<?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Сделать понятную подпись интервала, сохранив разницу между компонентом и общим числом дней.
DateInterval::format(string $format): stringСделать понятную подпись интервала, сохранив разницу между компонентом и общим числом дней.
| Часть вызова | Договор |
|---|---|
| format | Шаблон из буквального текста и %-спецификаторов ниже; регистр важен. |
| Возврат | Форматированная строка. Сам интервал не изменён. |
| Граница | Ни нормализации компонентов, ни универсального преобразования месяца в дни/секунды. |
- step хранит 32 дня и 90 минут
- format(%D дн.; %I мин.) →label
- echo показывает настройку без переносов
- 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Процедурный вход в парсер относительного интервала со своей политикой ошибки.
date_interval_create_from_date_string(string $datetime): DateInterval|falseПроцедурный вход в парсер относительного интервала со своей политикой ошибки.
| Часть вызова | Договор |
|---|---|
| datetime | Относительный текст того же парсера, что у статического метода; ISO P… сюда не передаётся. |
| Возврат | DateInterval при успехе; false при отказе. |
| Ошибка | При некорректной строке предупреждение сохраняется; изменение ООП-метода в PHP8.3 эту функцию не изменило. |
- Строка 2 weeks +1 day →функция
- Возврат DateInterval →step; false означает отказ
- 1 апреля 09:00 +15 дней →review
- 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 обычной функцией с явным аргументом-объектом.
date_interval_format(DateInterval $object, string $format): stringФорматировать DateInterval обычной функцией с явным аргументом-объектом.
| Часть вызова | Договор |
|---|---|
| object | Уже созданный DateInterval; строка P… не заменяет объект и даст TypeError. |
| format | Полный набор %-спецификаторов DateInterval::format, с теми же правилами знака/микросекунд/%a. |
| Возврат | Строка; компоненты не нормализуются, исходник не меняется. |
- step:2 часа 15 минут
- Функция получает step и %H:%I
- Возврат строки 02:15 →label
- 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Хранить ограниченную последовательность повторяющихся дат: дни отчёта, окна бронирования или календарь обслуживания.
class DatePeriod implements IteratorAggregateХранить ограниченную последовательность повторяющихся дат: дни отчёта, окна бронирования или календарь обслуживания.
| Часть вызова | Договор |
|---|---|
| EXCLUDE_START_DATE | int-константа: исключить начальную дату из выдачи. |
| INCLUDE_END_DATE | int-константа с PHP8.2: разрешить точку, равную конечной границе; не добавляет произвольную дату, не совпавшую с шагом. При форме с recurrences увеличивает минимальный счёт выдачи ещё на1. |
| start / end / interval | readonly nullable-свойства с исходной датой, конечной границей (null при счёте повторов) и объектом DateInterval шага. Обычный успешно созданный период имеет начало и шаг. |
| current | readonly ?DateTimeInterface: состояние даты при итерации; до обхода null. Не таблица всех дат и не гарантированная последняя дата после завершения. |
| include_start_date / include_end_date | readonly bool-флаги включения границ; include_end_date добавлен в8.2. |
| recurrences | readonly int: минимум выдаваемых точек. При заданном n: n +1 за включённое начало +1 за включённый конец. При форме end: 0 + те же добавки, а не подсчитанная длина всего диапазона. |
| Методы и версии | Конструктор, createFromISO8601String, getStartDate/getEndDate/getDateInterval/getRecurrences. Типизированные константы с8.4; это уточнение объявления, не смена смысла флагов. |
- start:1 апреля 09:00 UTC; step:7 дней
- new DatePeriod(start,step,2) →period
- foreach выдаёт 1,8,15 апреля
- Каждый 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Создать расписание либо по числу повторений, либо до конечной даты, явно выбрав границы.
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); недопустимый счёт тоже отклоняется. Неверный тип аргумента — ошибка вызова. |
- start:1 апреля; step:2 дня; end:5 апреля
- Конструктор получает выбранные flags
- foreach выдаёт допустимые точки 1/3/5
- Массив подписей →одна строка календаря обслуживания
После простого расписания по числу повторов уточним диапазон:обслуживание каждые два дня от 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-расписание из машинной настройки и получить период.
DatePeriod::createFromISO8601String(string $specification, int $options = 0): staticПрочитать ограниченное ISO8601-расписание из машинной настройки и получить период.
| Часть вызова | Договор |
|---|---|
| specification | Поддерживаемая форма Rn/start/P…; Rn означает повторы. Поддерживается UTC Z, не произвольное +02:00. |
| options | Маска границ как у конструктора. |
| Возврат | Новый DatePeriod/вызываемый подкласс; итерация выдаёт DateTimeImmutable. |
| Ошибка | DateMalformedPeriodStringException при неподдерживаемой/неверной строке; R0 и бесконечный R не поддержаны. |
- R2 /1 апреля 09:00 UTC /P7D
- Фабрика возвращает period
- foreach выдаёт начало и два повтора
- format →три даты отчётов
Настройка расписания может храниться одной машинной строкой:повторы/начало/шаг. Используем статическую фабрику createFromISO8601String через ::; устаревшую строковую форму new DatePeriod в новом коде не выбираем. Поддерживается ограниченное подмножество ISO8601, которое уточняется ниже.
<?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Получить объект шага периода для подписи или следующего расчёта.
DatePeriod::getDateInterval(): DateIntervalПолучить объект шага периода для подписи или следующего расчёта.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | DateInterval, представляющий шаг. |
| Граница | Это не суммарная длительность периода и не количество повторений. Поля DateInterval рассматривают как readonly по договору. |
- period хранит шаг P7D
- getDateInterval() →step
- step->format(%d) →строка 7
- 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Прочитать заданную границу периода, не перепутав её с последней выданной датой.
DatePeriod::getEndDate(): ?DateTimeInterfaceПрочитать заданную границу периода, не перепутав её с последней выданной датой.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | null при отсутствии end, например в форме recurrences или ISO без end. Если end был DateTimeImmutable — соответствующий immutable-объект; иначе клон DateTime. |
| Вопросительный знак | ?DateTimeInterface разрешает null; перед ->format проверьте его, если форма создания неизвестна. |
| Граница | Не вычисляет последний элемент; включение end и совпадение с шагом проверяются отдельно. |
- Период по 2 повторам →end не задавался
- getEndDate() возвращает null
- Период с границей 4 апреля →возвращает объект end
- Граница 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Отличить настройку числа повторов от размера последовательности.
DatePeriod::getRecurrences(): ?intОтличить настройку числа повторов от размера последовательности.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | Число повторений, заданное при создании; если период создан через конечную дату — null. |
| Не путать | Ни свойство recurrences, ни getRecurrences не являются универсальным count всех выдаваемых дат. |
| Версия | Добавлен в ветках PHP7.2.17/7.3.4. |
- Конструктору передано 2
- getRecurrences() возвращает 2 в repeats
- Флаги влияют на число выдаваемых дат
- В подписи отдельно настройка, минимум и реальные даты
Нужно восстановить в форме именно число повторов, которое передали конструктору. 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Получить исходную точку периода независимо от флага её включения.
DatePeriod::getStartDate(): DateTimeInterfaceПолучить исходную точку периода независимо от флага её включения.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | DateTimeImmutable для immutable-старта; DateTime в другом случае. |
| Граница | Начало настройки не обязано быть первой выданной датой при EXCLUDE_START_DATE. |
- period создан со стартом 1 апреля 09:00
- getStartDate() →start
- start->sub(P1D) →reminder:31 марта 09:00
- 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-сериализации объекта.
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-сериализацию не принимают из запроса. Служебные методы и сериализация. |
- object хранит P2DT3H
- __serialize() → массив state
- Чтение конкретных полей по ключам
- Исходное правило даёт доставку 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Восстановить объект из собственного массива состояния; это служебная операция изменения получателя.
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) ловит ошибку восстановления. |
- object->__serialize() → state
- clone object → отдельный restored
- restored->__unserialize(state) меняет restored; void
- Следующий код получает полезный результат из 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 восстановления и убрать ручное использование при миграции.
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-сериализацию не принимают из запроса. Служебные методы и сериализация. |
- Найден ручной __wakeup после восстановления
- PHP8.5 сообщает deprecation
- Для собственного payload используем обычный unserialize
- Современный hook восстановит объект автоматически
В первом опыте set_error_handler перехватывает только E_DEPRECATED и печатает факт устаревания; finally восстанавливает прежний handler. Это наблюдение старого API, не его рекомендация и не скрытое подавление всей диагностики.
<?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-экспорта; не выполнять посторонний код из настроек.
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); здесь такого исполнения нет. |
- object → собственный массив state
- DateInterval::__set_state(state) создаёт объект
- return → restored в месте вызова
- 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-сериализации объекта.
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-сериализацию не принимают из запроса. Служебные методы и сериализация. |
- object хранит 1 апреля 09:00 / P1D / 2 повтора
- __serialize() → массив state
- Чтение конкретных полей по ключам
- Исходный план выдаёт проверки 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Восстановить объект из собственного массива состояния; это служебная операция изменения получателя.
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) ловит ошибку восстановления. |
- object->__serialize() → state
- clone object → отдельный restored
- restored->__unserialize(state) меняет restored; void
- Следующий код получает полезный результат из 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 восстановления и убрать ручное использование при миграции.
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-сериализацию не принимают из запроса. Служебные методы и сериализация. |
- Найден ручной __wakeup после восстановления
- PHP8.5 сообщает deprecation
- Для собственного payload используем обычный unserialize
- Современный hook восстановит объект автоматически
В первом опыте set_error_handler перехватывает только E_DEPRECATED и печатает факт устаревания; finally восстанавливает прежний handler. Это наблюдение старого API, не его рекомендация и не скрытое подавление всей диагностики.
<?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-экспорта; не выполнять посторонний код из настроек.
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); здесь такого исполнения нет. |
- object → собственный массив state
- DatePeriod::__set_state(state) создаёт объект
- return → restored в месте вызова
- 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.
public function getIterator(): IteratorПолучить итератор дат, который обычно скрыт за foreach.
| Часть вызова | Договор |
|---|---|
| Параметры | Нет. |
| Возврат | Iterator для обхода этого периода, а не массив дат. |
| Протокол | rewind — начало; valid — есть ли элемент; current — значение; key — ключ; next — следующий. foreach делает эти шаги автоматически. |
| Версия | DatePeriod реализует IteratorAggregate сPHP8.0; метод подтверждён точным stub8.5.11. |
| Ограничения | Полный обход стоит времени по числу дат; не превращайте неограниченный внешний ввод в огромный диапазон. |
- getIterator() →механизм обхода в iterator
- rewind →первая позиция; valid →есть дата
- current →1 апреля 09:00
- 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Создать настройки часового пояса для показа встречи, заказа или расписания.
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, идентификатор с правилами по датам. |
- 12:00 UTC — момент встречи
- new DateTimeZone("Asia/Tokyo") — правила отображения
- 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Прочитать сохранённое описание пояса, например для настроек филиала.
public function DateTimeZone::getName(): stringПрочитать сохранённое описание пояса, например для настроек филиала.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет: используется уже созданный объект. |
| Результат | Строковое имя, сокращение или нормализованное смещение. Его можно снова передать конструктору. |
| Граница | Это не перевод названия города и не обещание канонизировать старый идентификатор. Метод не читает пояс браузера. |
- Объект Europe/Berlin
- getName() → Europe/Berlin
- Строка в конфигурации → новый объект при следующем запросе
Вызов без аргументов записывается с пустыми скобками: $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 для такой настройки не требуется.
<?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 в конкретный момент.
public function DateTimeZone::getOffset(DateTimeInterface $datetime): intУзнать смещение выбранного региона от UTC в конкретный момент.
| Часть вызова | Договор |
|---|---|
| datetime | DateTime или DateTimeImmutable, задающий момент. Его собственный пояс может отличаться от пояса, у которого вызван метод. |
| Результат | Целое число секунд: положительное восточнее UTC, отрицательное западнее. Это не количество часов и не разница между двумя переданными поясами. |
| Ошибки и состояние | Неверный тип — TypeError. Объект даты и объект пояса не изменяются. Смещение может меняться с датой; оно не всегда кратно часу. |
- zone: Europe/Berlin; meeting: 15 января 12:00 UTC
- getOffset(meeting) → offsetSeconds=3600
- setTimezone(zone) → местная дата 13:00
- Подпись соединяет дату и значение секунд; июль даёт 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Получить страну и справочную точку региона, чтобы подписать выбор часового пояса.
public function DateTimeZone::getLocation(): array|falseПолучить страну и справочную точку региона, чтобы подписать выбор часового пояса.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. |
| Результат | Массив country_code (строка ISO 3166-1), latitude и longitude (float), comments (строка); либо false, если у объекта нет таблицы региона. |
| Граница данных | У смещения и сокращения результат false. Координаты обозначают представительную точку, а не границы региона и не положение посетителя. Для служебных зон возможны условные значения, например страна ?? у UTC. |
- zone: Europe/Berlin
- getLocation() → массив location или false
- После проверки читаем country_code/latitude/longitude/comments
- Получаем подписанную справочную карточку региона
После вызова сначала сравните результат с 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Посмотреть, когда меняются правила часов региона, и объяснить скачок в расписании.
public function DateTimeZone::getTransitions(int $timestampBegin = PHP_INT_MIN, int $timestampEnd = 2147483647): array|falseПосмотреть, когда меняются правила часов региона, и объяснить скачок в расписании.
| Часть вызова | Договор |
|---|---|
| timestampBegin | Unix 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 января: действует +01:00, это снимок
- 31 марта: смещение меняется на +02:00
- 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Предложить человеку допустимые региональные настройки вместо ввода непонятных сокращений.
public static function DateTimeZone::listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): arrayПредложить человеку допустимые региональные настройки вместо ввода непонятных сокращений.
| Часть вызова | Договор |
|---|---|
| timezoneGroup | ALL — актуальные имена; 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. |
- Филиал в Японии → PER_COUNTRY + JP
- Список → Asia/Tokyo
- Сохранить выбранную строку, не её позицию в массиве
Это статический метод: запись 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Исследовать сокращение из старого журнала, не принимая его за уникальное название региона.
public static function DateTimeZone::listAbbreviations(): arrayИсследовать сокращение из старого журнала, не принимая его за уникальное название региона.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет; статический вызов у класса. |
| Результат | Ассоциативный массив: строчное сокращение → список вариантов. Каждый вариант содержит dst (bool), offset (секунды int), timezone_id (string или null). |
| Ограничение | Историческая предкомпилированная таблица неоднозначных сокращений. Обновление PECL timezonedb само по себе эту таблицу не обновляет. Не использовать её как источник вариантов современной формы. |
- DateTimeZone::listAbbreviations() → table
- table[cst] → rows с несколькими вариантами
- Выбраны два конкретных региона обычного времени
- Вывод показывает имена и разные смещения
Вложенный список означает, что одному короткому ключу соответствуют несколько записей. Получить первый элемент технически легко, но это может выбрать другой континент. Здесь сначала показываем сам факт неоднозначности, затем переносим его в обработчик импорта.
Поставщик прислал короткое сокращение вместо региона. Прежде чем импортировать время заказа, нужно увидеть неоднозначность. Функция возвращает вложенный массив; 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Получить объект часового пояса обычным вызовом функции и проверить отказ.
function timezone_open(string $timezone): DateTimeZone|falseПолучить объект часового пояса обычным вызовом функции и проверить отказ.
| Часть вызова | Договор |
|---|---|
| timezone | Поддерживаемое имя IANA, смещение (+02:00) либо сокращение. Для настройки профиля предпочтителен регион. |
| Результат | Объект DateTimeZone или false. В PHP 8.5 неверное имя также вызывает E_WARNING; неверный тип — TypeError, NUL-байт в строке — ValueError. |
| Отличие от new | new DateTimeZone при неверном имени выбрасывает исключение; процедурный вариант предупреждает и возвращает false. Не подменяйте один контракт другим. |
- timezone_open("Asia/Tokyo")
- Получили DateTimeZone или false
- Проверенный объект передали дате
После знака = функция выполняется, и её результат попадает в переменную. 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Получить имя пояса в процедурном коде, например перед сохранением настроек.
function timezone_name_get(DateTimeZone $object): stringПолучить имя пояса в процедурном коде, например перед сохранением настроек.
| Часть вызова | Договор |
|---|---|
| object | Инициализированный DateTimeZone, созданный new или timezone_open(). false от неудачного открытия сюда передавать нельзя. |
| Результат | Строковое описание пояса. Аналог $object->getName(), без определения геолокации и без гарантии перевода старого имени в каноническое. |
| Ошибки | Неверный тип — TypeError. Вызов не меняет объект и глобальный пояс приложения. |
- zone: Asia/Tokyo
- timezone_name_get(zone) → строка в settings[timezone]
- new DateTimeZone(settings[timezone]) →restored
- Встреча 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В процедурном отчёте прочитать смещение региона на дату события.
function timezone_offset_get(DateTimeZone $object, DateTimeInterface $datetime): intВ процедурном отчёте прочитать смещение региона на дату события.
| Часть вызова | Договор |
|---|---|
| object | Пояс, чьё правило требуется, а не исходный пояс события. |
| datetime | DateTime/DateTimeImmutable: момент, на который рассчитывается правило. |
| Результат | Знаковое целое число секунд относительно UTC. Никаких изменений объектов; неверный тип аргумента вызывает TypeError. |
- office: America/New_York; event: 15 января 12:00 UTC
- timezone_offset_get(office,event) → −18000
- setTimezone(office) →07:00−05:00
- Отчёт показывает местную встречу и правило
Порядок аргументов важен: первый сообщает «где», второй «когда». Для простого показа часов лучше сразу 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Получить справочную географию выбранного пояса для подписи в административном интерфейсе.
function timezone_location_get(DateTimeZone $object): array|falseПолучить справочную географию выбранного пояса для подписи в административном интерфейсе.
| Часть вызова | Договор |
|---|---|
| object | DateTimeZone. Типы смещения и сокращения не содержат географической структуры. |
| Результат | Массив country_code, latitude, longitude, comments или false; поля означают страну, представительную точку и пояснение из базы, не реальное местоположение клиента. |
| Ошибки и ограничения | Неверный тип — TypeError. Для служебных зон возможна страна ??; непустой массив не гарантирует полезную геолокацию. |
- Пользователь выбрал Asia/Tokyo
- Справочник сообщает JP
- Подписываем настройку; адрес доставки запрашиваем отдельно
Сначала получите массив, затем прочитайте нужный ключ. Для зоны без географии придумайте понятную подпись, а не обращайтесь к ключам значения 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Получить список разрешённых регионов для формы и серверной проверки.
function timezone_identifiers_list(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): arrayПолучить список разрешённых регионов для формы и серверной проверки.
| Часть вызова | Договор |
|---|---|
| timezoneGroup | ALL по умолчанию; региональные флаги можно объединять |. ALL_WITH_BC включает совместимые устаревшие имена. PER_COUNTRY задаёт отдельную фильтрацию. |
| countryCode | Двухбуквенный прописной код страны только для точного режима PER_COUNTRY. Nullable с PHP 7.1, но отсутствие кода в этом режиме — ошибка. |
| Результат и ошибки | Массив строк. В PHP 8 неправильный код по длине вызывает ValueError; до PHP 8 ошибка давала false. Неизвестный двухбуквенный код может дать []. Неверные типы — TypeError. |
- PER_COUNTRY, JP → allowed=[Asia/Tokyo]
- submitted=Asia/Tokyo проходит строгий поиск
- new DateTimeZone(submitted) → zone
- Момент 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Разобрать сокращение из импортированного журнала и увидеть варианты его значения.
function timezone_abbreviations_list(): arrayРазобрать сокращение из импортированного журнала и увидеть варианты его значения.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. |
| Результат | Массив сокращений в нижнем регистре. Каждое значение — список записей dst (bool), offset (секунды int), timezone_id (string|null). |
| Ограничения | Таблица включает историю и неоднозначные сокращения; её данные предкомпилированы и не обновляются установкой новой PECL timezonedb. Список для формы строится через timezone_identifiers_list(). |
- timezone_abbreviations_list() → table
- table[pst] → rows с несколькими вариантами
- Выбраны два конкретных региона обычного времени
- Вывод показывает имена и разные смещения
Сначала выбирается ключ сокращения, затем перебираются записи внутри него. Это как раз жизненный источник многомерного массива: встроенная функция возвращает несколько вариантов для одного запроса.
Поставщик прислал короткое сокращение вместо региона. Прежде чем импортировать время заказа, нужно увидеть неоднозначность. Функция возвращает вложенный массив; 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 для конкретной даты.
<?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Получить предположение о названии пояса из старого сокращения; подтвердить его до применения.
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 был строго соблюдён. |
- EST,−18000,0 → эвристический поиск
- return → guess=America/New_York или false
- Предложение показывается источнику
- Отдельно подтверждённый регион →конкретный момент заказа
Название функции может создать ложное ощущение точного преобразования. Это эвристика для совместимости: множество регионов имеют одинаковое смещение. Для новой формы лучше сразу попросить 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Прочитать историю правил региона в процедурном коде, например для диагностики расписания.
function timezone_transitions_get(DateTimeZone $object, int $timestampBegin = PHP_INT_MIN, int $timestampEnd = 2147483647): array|falseПрочитать историю правил региона в процедурном коде, например для диагностики расписания.
| Часть вызова | Договор |
|---|---|
| object | Объект регионального пояса. Фиксированное смещение и сокращение возвращают false. |
| timestampBegin / timestampEnd | Unix 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; если нужен строгий интервал, фильтруйте и исключайте начальное состояние явно. |
- Europe/London и ограниченный 2024 год
- timezone_transitions_get → rows
- rows[0] → initial на 1 января
- Подпись показывает 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Узнать версию правил времени, чтобы расследовать разницу расчётов между серверами.
function timezone_version_get(): stringУзнать версию правил времени, чтобы расследовать разницу расчётов между серверами.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. |
| Результат | Строка обычно вида YYYY.increment. Некоторые сборки Linux используют системную базу и возвращают 0.system. |
| Обновление | Правила обновляются вместе с PHP или через PECL timezonedb; у системной базы обновление зависит от пакетов ОС. Версия правил не равна версии PHP. |
| Граница проверки | Непустая версия не гарантирует актуальность будущих политических решений. Строка 0.system не означает отсутствие базы; тогда нужно проверить пакет tzdata ОС. |
- timezone_version_get() → строка version
- Контрольный момент и Europe/Berlin заданы явно
- format →местные 14:00
- Диагностика содержит реальную версию, регион и результат
Обычный вызов возвращает строку, которую полезно включить в техническую диагностику вместе с 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 сохраняет при сериализации объекта пояса; для настройки обычно достаточно имени.
public function DateTimeZone::__serialize(): arrayПонять, какие данные PHP сохраняет при сериализации объекта пояса; для настройки обычно достаточно имени.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. PHP вызывает этот специальный метод из serialize($zone). |
| Результат | Массив состояния: timezone_type (1–3), timezone (строка), а также пользовательские свойства при наличии. Это внутренний договор восстановления, не JSON-ответ API. |
| Версия и ошибки | Метод появился в PHP 8.2. Неинициализированный объект вызывает ошибку; в современной ветке — DateObjectError. Формат не заменяет переносимый контракт данных приложения. |
- zone хранит Europe/Berlin
- __serialize() → state: тип 3 и имя
- Поля прочитаны по ключам
- Исходный 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.
public function DateTimeZone::__unserialize(array $data): voidВосстановить внутренний объект из массива состояния, который ранее создал сам PHP.
| Часть вызова | Договор |
|---|---|
| data | Массив состояния с целым timezone_type в диапазоне 1–3 и строкой timezone. Строка не должна содержать NUL; действительный пояс должен распознаваться. |
| Результат | void: изменяется состояние получателя, новый объект этим методом не возвращается. PHP вызывает метод во время unserialize(). |
| Ошибки и версия | PHP 8.2+. Некорректная структура/неизвестный пояс — Error; неверный тип самого аргумента — TypeError. Не использовать как валидатор произвольного входного JSON. |
- state хранит собственное имя Asia/Tokyo
- receiver пока UTC →часы 12:00
- receiver->__unserialize(state) меняет receiver; void
- Следующий показ того же 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Узнать старый механизм восстановления, чтобы убрать его ручные вызовы из старого проекта.
public function DateTimeZone::__wakeup(): voidУзнать старый механизм восстановления, чтобы убрать его ручные вызовы из старого проекта.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет: исторически состояние читалось из восстановленных свойств объекта. |
| Результат и ошибка | void; при неподходящих свойствах — Error. Вызов у обычного нового объекта не является командой «обновить правила». |
| Версия | В PHP 8.5 конкретный встроенный метод помечен Deprecated и прямой вызов выдаёт E_DEPRECATED. Современный протокол использует __serialize/__unserialize с PHP 8.2. |
- Старый код вручную вызывает __wakeup
- Обычный объект не содержит ожидаемой старой раскладки свойств
- Удаляем ручной вызов: new для создания, новый протокол для локального снимка
Общая soft-deprecation магических __sleep/__wakeup и конкретное предупреждение встроенного DateTimeZone — разные уровни. Здесь речь именно о методе этого класса. Для первого знакомства с поясами он не нужен.
<?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().
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() над внешним текстом; для конфигурации пояса обычно проще хранить имя. |
Слово 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Получить читаемую дату события в выбранном часовом поясе.
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 даёт пустую строку. |
- receivedAt=1704067200; format=d.m.Y H:i
- date(format,receivedAt) → label
- label=01.01.2024 03:00
- echo строит подпись заказа; timestamp прежний
В первом аргументе d — день с нулём, m — месяц, Y — полный год; во втором передаётся готовый Unix timestamp. H:i — часы/минуты: m в этом языке никогда не означает минуты. Функция возвращает строку, а echo отдельно печатает её. полная таблица формата вывода охватывает каждый символ, включая X/x с 8.2, p с 8.0 и различие ISO-года o/календарного Y.
<?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; функция не прибавляла часы к сохранённой метке.
<?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-подпись для внешнего протокола.
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 даёт пустую строку. |
- receivedAt=1704067200; format=d.m.Y H:i
- gmdate(format,receivedAt) → label
- label=01.01.2024 00:00
- echo строит подпись заказа; timestamp прежний
В первом аргументе d — день с нулём, m — месяц, Y — полный год; во втором передаётся готовый Unix timestamp. H:i — часы/минуты: m в этом языке никогда не означает минуты. Функция возвращает строку, а echo отдельно печатает её. полная таблица формата вывода охватывает каждый символ, включая X/x с 8.2, p с 8.0 и различие ISO-года o/календарного Y.
<?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. Оно подходит для единообразного журнала.
<?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Построить момент отгрузки из отдельных календарных полей местного времени.
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. |
- 15 июня 2024,09:00 —входные календарные поля
- mktime(9,0,0,6,15,2024) → departure
- Проверка ===false перед использованием
- gmdate и date дают две подписи одного момента
Шесть полей записывают в порядке час, минута, секунда, месяц, день, год. Чтобы дата не зависела от момента запуска, передайте все шесть. mktime толкует поля в default timezone; это календарное локальное время, а результат — общий Unix timestamp.
<?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.
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-полей. |
- 15 июня 2024,09:00 —входные календарные поля
- gmmktime(9,0,0,6,15,2024) → departure
- Проверка ===false перед использованием
- gmdate и date дают две подписи одного момента
Шесть полей записывают в порядке час, минута, секунда, месяц, день, год. Чтобы дата не зависела от момента запуска, передайте все шесть. gmmktime толкует их в UTC, даже если default timezone другой.
<?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Прочитать текстовую настройку даты или относительный срок от явно заданной базы.
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. |
- Строка со всеми полями и UTC → strtotime
- return int →at либо false
- Успех →gmdate→подпись встречи
- Ошибка →сообщение; нулевой 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Получить одно числовое поле календаря для условия или группировки.
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. |
- at=1 января 2024 UTC
- idate(m,at) →month=1
- intdiv(month−1,3)+1 →quarter=1
- Год и квартал →reports/2024/Q1
В отличие от date, idate принимает ровно один поддерживаемый символ и возвращает int. Номер месяца 1 получается числом 1, без нуля. Для готовой подписи "01" нужен date("m", ...), а для числовой ветки условия удобно idate("m", ...).
| Символ | Целочисленное поле |
|---|---|
| B | Swatch beats 0…999 |
| d | День месяца 1…31 |
| h / H | Час 1…12 / 0…23 |
| i / s | Минуты / секунды 0…59 |
| I | DST:1 действует,0 нет |
| L | Високосный год:1/0 |
| m | Месяц 1…12 |
| N | ISO-день 1…7, понедельник 1; PHP 8.2+ |
| o | Год ISO-недели; PHP 8.2+ |
| t | Дней в месяце 28…31 |
| U | Целая Unix-секунда |
| w | День недели 0…6, воскресенье 0 |
| W | ISO-неделя 1…53 |
| y / Y | Двузначная часть года / полный год; int не сохраняет ведущие нули |
| z | Порядковый день года 0…365 |
| Z | UTC-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Получить календарные поля события для отчёта или простой ветки расписания.
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; корректная конфигурация зоны обязательна. |
- getdate(1704067200) →parts в UTC
- year=2024,mon=1,mday=1 →reportKey
- yday=0 →первый день года
- Подпись отчёта и исходный момент
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 для совместимости со старым интерфейсом.
localtime(?int $timestamp = null, bool $associative = false): arrayРазобрать timestamp в структуру tm для совместимости со старым интерфейсом.
| Часть вызова | Договор |
|---|---|
| timestamp | Целое число секунд от 1970-01-01 00:00:00 UTC. Не миллисекунды и не «секунды местного пояса». null/пропуск означает текущий момент; nullable с PHP 8.0. |
| associative | false — числовые индексы 0…8; true — ключи tm_*. |
| Возврат / ошибки | Массив 9 полей в default timezone. TypeError для неверного типа; зона должна быть корректна. |
- localtime(at,true) →tm с именованными полями
- tm_year124+1900 →2024
- tm_mon0+1 →1
- 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 — нет, отрицательное — неизвестно |
<?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 сейчас интерпретирует даты без явно заданного пояса.
date_default_timezone_get(): stringУзнать, в какой зоне PHP сейчас интерпретирует даты без явно заданного пояса.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. |
| Возврат | Строка timezone-ID действующего default. Не UTC-offset и не локаль. |
| Граница | Не узнаёт timezone клиента, не меняет системные часы и не возвращает обязательно исходное значение php.ini. |
- set задаёт Europe/Moscow
- get без аргументов →zoneName
- date читает тот же default →03:00
- Отчёт явно подписывает настройку
Пустые скобки означают, что аргументы не нужны. Это настройка интерпретации/вывода дат 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 скрипта до работы с датами.
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 задаёт зону конкретной операции. |
- existing создан в UTC
- set(Europe/Moscow) →true
- date показывает 03:00, existing остаётся 00:00 UTC
- Новый текст 00:00 без зоны теперь означает 00:00 Moscow
Функция меняет default в текущем PHP-контексте. Она не переводит хранимые timestamp, не меняет часы ОС и не изменяет уже созданные DateTime-объекты. Проверяйте true; внешний текст лучше выбирать из поддерживаемого списка IANA, а не подставлять сокращение или смещение случайного пользователя.
<?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Поставить в журнал отметку времени с дробной секундой, не путая её с секундомером.
microtime(bool $as_float = false): string|floatПоставить в журнал отметку времени с дробной секундой, не путая её с секундомером.
| Часть вызова | Договор |
|---|---|
| as_float | false — строка "доля секунды целые секунды"; true — один float Unix-seconds. |
| Возврат | string либо float, текущее wall-clock время. |
| Границы | Зависит от системных часов/gettimeofday; для измерения интервалов PHP рекомендует hrtime. Неверный тип параметра в строгом вызове — TypeError. |
- microtime(true) →receivedAt, float секунд
- retryAt=receivedAt+0.25
- Разница×1000 →250 миллисекунд
- Число идёт в понятную настройку повтора
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Получить секунды и микросекунды одним согласованным снимком часов.
gettimeofday(bool $as_float = false): array|floatПолучить секунды и микросекунды одним согласованным снимком часов.
| Часть вызова | Договор |
|---|---|
| as_float | false — массив 4 полей; true — float seconds. |
| Возврат | Интерфейс системного gettimeofday; структура ниже либо float. |
| Ошибки / границы | Неверный тип аргумента — TypeError; разрешение/вспомогательные timezone-поля платформозависимы. |
- gettimeofday() →clock с sec и usec
- К usec добавлено 250000
- intdiv переносит миллион в sec, %оставляет дробную часть
- Разница пар →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Получить расчётный дневной план восхода, заката и сумерек для координат.
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. |
- 15 июня 2024 UTC,55.75 N, 37.62 E
- Модель рассчитывает времена для 9 событий
- 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.
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-контексте. |
| returnFormat | SUNFUNCS_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. |
- 15 июня 2024, Москва, зенит 90.833333
- Явное utcOffset=0
- Получаем 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.
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-контексте. |
| returnFormat | SUNFUNCS_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. |
- 15 июня 2024, Москва, зенит 90.833333
- Явное utcOffset=0
- Получаем 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.
strftime(string $format, ?int $timestamp = null): string|falseПрочитать старый локальный формат даты по системной locale и заменить его явным formatter.
| Часть вызова | Договор |
|---|---|
| format | Строка с %спецификаторами из таблицы, поддерживаемыми именно C библиотекой платформы. |
| timestamp | int Unix-seconds; null/пропуск — текущий момент; nullable с PHP 8.0. |
| Зона / язык | Default timezone PHP. Имена дней/месяцев по LC_TIME. |
| Возврат / отказ | Строка либо false, может сопровождаться E_WARNING; вызов deprecated: E_DEPRECATED с 8.1. Для нового кода date/gmdate или IntlDateFormatter. |
- 1704067200; C-locale
- %Y-%m-%d %H:%M выбирает поля
- Получаем 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 / %w | ISO-понедельник 1…воскресенье 7 / воскресенье 0…суббота 6. У старого Solaris %u может отличаться. |
| %U / %W | Неделя года с первым воскресеньем / понедельником как началом недели 1; до неё неделя 00. |
| %V | ISO-неделя 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 / %P | AM/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 библиотеки. |
| %s | Unix 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.
gmstrftime(string $format, ?int $timestamp = null): string|falseПрочитать старый UTC формат даты по системной locale и заменить его явным formatter.
| Часть вызова | Договор |
|---|---|
| format | Строка с %спецификаторами из таблицы, поддерживаемыми именно C библиотекой платформы. |
| timestamp | int Unix-seconds; null/пропуск — текущий момент; nullable с PHP 8.0. |
| Зона / язык | UTC независимо от default timezone. Имена дней/месяцев по LC_TIME. |
| Возврат / отказ | Строка либо false, может сопровождаться E_WARNING; вызов deprecated: E_DEPRECATED с 8.1. Для нового кода date/gmdate или IntlDateFormatter. |
- 1704067200; C-locale
- %Y-%m-%d %H:%M выбирает поля
- Получаем 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 / %w | ISO-понедельник 1…воскресенье 7 / воскресенье 0…суббота 6. У старого Solaris %u может отличаться. |
| %U / %W | Неделя года с первым воскресеньем / понедельником как началом недели 1; до неё неделя 00. |
| %V | ISO-неделя 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 / %P | AM/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 библиотеки. |
| %s | Unix 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-парсер и заменить его контролируемым разбором даты.
strptime(string $timestamp, string $format): array|falseПонять старый locale-парсер и заменить его контролируемым разбором даты.
| Часть вызова | Договор |
|---|---|
| timestamp | Несмотря на имя, это строка даты, а не int Unix-seconds. |
| format | %формат strftime; реально поддерживаемое подмножество зависит от ОС/C-библиотеки. |
| Locale | LC_TIME влияет на имена месяцев, дней и другие языковые части. |
| Возврат / ошибки | Массив tm_* плюс unparsed либо false. TypeError для неверных типов; deprecated; на Windows отсутствует. |
| Альтернативы | date_parse_from_format для locale-independent, IntlDateFormatter::parse для locale-dependent. |
- 15.06.2024 и шаблон!d.m.Y
- date_parse_from_format →parts
- Проверены error_count и warning_count
- 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 | Необработанный остаток исходного текста: его нельзя молча игнорировать для строгого поля. |
$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 умеет печатать этот спецификатор.
<?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.