PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 23 · С нуля → практика
Дата и время: часовые пояса, интервалы и реальные сроки
Не путаем момент времени с календарной датой и явно определяем, что означает «через месяц».
Показать покупателю известную дату отправки
Одно событие, разные показания часов
Заказ создан в один момент, но покупатель и склад могут находиться в разных часовых поясах.
- Храним момент: 09:00 UTC
Дата и время с зоной однозначно определяют событие.
- Показываем: 12:00 в Москве
Для UTC+03:00 изменилось представление того же момента.
Преобразование зоны не переносит событие на три часа. Календарные действия вроде «плюс один месяц» имеют отдельные правила; их нельзя всегда заменить фиксированным числом секунд.
Магазин подтвердил отправку на12 октября2026 года. Нужно получить подпись «Дата отправки:12.10.2026» и затем уметь рассчитывать соседние даты. Простая строка хранит буквы, но сама не знает календаря. Готовый класс DateTimeImmutable предоставляет объект даты с операциями форматирования и расчёта; собственный класс писать не нужно.
- zone содержит правила UTC
- dispatch получает12 октября2026,00:00:00 в этой зоне
- dispatch->format("d.m.Y") возвращает строку12.10.2026
- label хранит эту строку → echo показывает дату отправки
New создаёт готовый объект. В new DateTimeZone('UTC') строка UTC выбирает пояс без сезонного сдвига. В следующем вызове два аргумента разделены запятой: известная дата/время и объект zone. Знак -> выбирает действие объекта слева; круглые скобки передают аргумент этому действию. У format аргумент — строка шаблона: d означает день из двух цифр, m — месяц, Y — год из четырёх цифр; точки выводятся буквально. = сохраняет результат, ; завершает инструкцию.
<?php
$zone = new DateTimeZone('UTC');
$dispatch = new DateTimeImmutable('2026-10-12 00:00:00', $zone);
$label = $dispatch->format('d.m.Y');
echo 'Дата отправки: ', $label, "\n";
Ожидаемый результат
Дата отправки: 12.10.2026
Dispatch хранит объект, label — возвращённую строку. Вызов format не печатает самостоятельно: вывод выполняет echo. Шаблон меняет вид подписи, а не дату отправки. UTC выбран явно для воспроизводимости. Для реальной местной встречи позднее укажем её регион; для календарного дня не будем выдумывать перевод в другой пояс.
DateTimeImmutable означает, что обычная операция изменения создаёт другой объект. В нём есть год, месяц, день, время и зона. Модель «карточка календаря» помогает читать пример, но объект не является календарём сайта, записью БД или запущенным таймером. Все примеры здесь — полные отдельные CLI-файлы с заданными датами; строка now пока не используется.
Получить следующий день и сохранить исходную дату
Покупателю нужны обе подписи: отправка12 октября и ожидаемый следующий календарный день13 октября. Метод modify получает текст календарного правила и возвращает новую дату. В '+1 day' знак плюс, число и английское слово day — содержимое одной строки правила, а не отдельные операторы PHP.
- dispatch →12 октября
- modify(+1 day) → новая дата13 октября
- nextDay хранит возвращённый объект
- format каждой даты → две отдельные подписи
<?php
$dispatch = new DateTimeImmutable('2026-10-12 00:00:00', new DateTimeZone('UTC'));
$nextDay = $dispatch->modify('+1 day');
echo 'Отправка: ', $dispatch->format('d.m.Y'), "\n";
echo 'Следующий календарный день: ', $nextDay->format('d.m.Y'), "\n";
Ожидаемый результат
Отправка: 12.10.2026
Следующий календарный день: 13.10.2026
Вторая строка получает объект13 октября в nextDay. Исходный dispatch остаётся12 октября. Далее каждый format возвращает свою строку прямо в ожидающий echo. Вложенный new DateTimeZone выполняет уже знакомое создание зоны как второй аргумент; он не получает результат modify. Календарный день пока не означает рабочий день: выходные, праздники и расписание склада требуют отдельной политики.
Сохраняйте возвращённый объект. В PHP 8.5 у обычных изменяющих методов Immutable действует NoDiscard: игнорирование результата может дать предупреждение. Для самостоятельной пробы замените правило на +2 days и оставьте присваивание nextDay: получите 14.10.2026, исходная отправка останется 12.10.2026. Полный договор правил и ошибок — в modify.
Вернуть рассчитанный срок в вызывающий код
Если одинаковое правило используется в нескольких местах, функция получает дату отправки и возвращает дату готовности. Здесь выбран учебный срок «три календарных дня». Тип перед shipped разрешает объект DateTimeImmutable, а тип после двоеточия обещает такой же вид результата. Как и у обычного числа, return отдаёт одно значение в точное место вызова.
- Аргумент dispatch12 октября → местный shipped
- shipped->modify(+3 days) создаёт15 октября → местный ready
- return ready возвращает объект15 октября в readyDate(dispatch)
- pickup получает этот объект → format → подпись даты готовности
<?php
function readyDate(DateTimeImmutable $shipped): DateTimeImmutable {
$ready = $shipped->modify('+3 days');
return $ready;
}
$dispatch = new DateTimeImmutable('2026-10-12 00:00:00', new DateTimeZone('UTC'));
$pickup = readyDate($dispatch);
echo 'Отправка: ', $dispatch->format('d.m.Y'), "\n";
echo 'Готовность к выдаче: ', $pickup->format('d.m.Y'), "\n";
Ожидаемый результат
Отправка: 12.10.2026
Готовность к выдаче: 15.10.2026
Объявление функции само ничего не считает. Выполнение начинается в readyDate($dispatch). Shipped обозначает переданную дату; modify создаёт отдельный результат, return передаёт его наружу. После присваивания pickup программа продолжает два echo. Функция не ставит задание планировщику и не отправляет уведомление; она только вычисляет дату по заданному правилу.
Возвращённый объект можно передать другой функции или отформатировать для подтверждения. Если нужен только текст, можно объявить результат string и вернуть format; тогда дальнейшая календарная арифметика потребует исходной даты. Выбирайте договор по тому, что должен делать вызывающий код.
Отличить момент события, календарную дату и местное расписание
Оплата произошла в конкретный момент: в UTC часы показывали12:00, в Москве в этот же момент —15:00. У дня рождения иной смысл: это календарная дата, а не придуманная полночь UTC. «Встреча в09:00 следующего понедельника в Берлине» — местное расписание с правилами региона.
| Задача | Какие сведения сохраняют | Что показывают |
|---|---|---|
| Совершившаяся оплата | Момент UTC/Unix и согласованную точность | Местную подпись в поясе посетителя |
| День рождения или календарный день отправки | Саму календарную дату по договору | Эту дату без ненужного перевода полуночи в другой пояс |
| Будущая встреча/повторение | Местную дату, часы, идентификатор зоны и правило неоднозначных часов | Время региона; при обмене — также определённый момент |
- paidAt:12:00 UTC
- setTimezone(Europe/Moscow) сохраняет момент
- moscow:15:00 +03:00
- Формат P показывает выбранное смещение
<?php
$paidAt = new DateTimeImmutable('2026-10-12 12:00:00', new DateTimeZone('UTC'));
$moscow = $paidAt->setTimezone(new DateTimeZone('Europe/Moscow'));
echo 'В журнале UTC: ', $paidAt->format('d.m.Y H:i P'), "\n";
echo 'Для покупателя в Москве: ', $moscow->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
В журнале UTC: 12.10.2026 12:00 +00:00
Для покупателя в Москве: 12.10.2026 15:00 +03:00
H даёт часы00–23, i — минуты, P — смещение вида+03:00. SetTimezone возвращает новый вариант представления того же момента; не прибавляет три часа длительности. PaidAt остался в UTC. Региональное имя хранит правила зоны, а фиксированное+03:00 описывает только смещение. Для другого региона или исторической даты смещение может отличаться.
Unix timestamp — число секунд от1970-01-01 00:00:00 UTC. Оно одинаково для одного момента в разных зонах. Ручное прибавление10800 к timestamp создаст другой момент. Неоднозначные и отсутствующие местные часы при DST рассматриваются после обычных сроков; полный справочник зон — DateTimeZone.
Сначала разобрать одну дату, затем проверить результат
Поле формы обещает формат ГГГГ-ММ-ДД. CreateFromFormat получает именно этот шаблон, исходный текст и зону. Запись DateTimeImmutable::createFromFormat(...) вызывает фабрику класса: готового объекта слева ещё нет, поэтому используется ::. Возврат — новый объект или false. До проверки false метод объекта вызывать нельзя.
Шаблон !Y-m-d читает год, месяц, день; дефисы должны быть на своих местах. Восклицательный знак сбрасывает неуказанные части, поэтому часы не берутся из текущего времени. Сразу после разбора getLastErrors читает диагностику: в PHP8.2+ false означает её отсутствие, иначе возвращается массив с warning_count/error_count и сообщениями.
- Вход2026-02-28 → createFromFormat
- Объект или false: удалось ли разобрать?
- warnings/errors: не исправлял ли парсер невозможные компоненты?
- format(Y-m-d) совпал с исходным текстом → допустим именно наш формат
<?php
$input = '2026-02-28';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$errors = DateTimeImmutable::getLastErrors();
$hasProblems = $errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0);
if ($date === false || $hasProblems || $date->format('Y-m-d') !== $input) {
echo "Дата не принята\n";
} else {
echo 'Выбран день: ', $date->format('d.m.Y'), "\n";
}
Ожидаемый результат
Выбран день: 28.02.2026
Разбор дал объект28 февраля00:00, диагностикаfalse, поэтому hasProblems=false. В условии || ищется хотя бы одна причина отказа. При date=false короткое замыкание не допускает вызов format на false. Здесь причин нет: else форматирует объект для посетителя. Вход в этом опыте уже известная корректная строка; защиту произвольного текста добавим в следующей функции.
<?php
$input = '2026-02-30';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$errors = DateTimeImmutable::getLastErrors();
echo 'Введено: ', $input, "\n";
if ($date !== false) { echo 'Парсер построил: ', $date->format('Y-m-d'), "\n"; }
if ($errors !== false && $errors['warning_count'] > 0) {
echo "Дата отклонена: календарные части были исправлены\n";
}
Ожидаемый результат
Введено: 2026-02-30
Парсер построил: 2026-03-02
Дата отклонена: календарные части были исправлены
Парсер смог создать объект, но перенёс два лишних дня в март. Поэтому существование объекта не означает корректность исходного дня. Показанный мартовский объект используется только для объяснения отказа; сохранять его как будто пользователь выбрал2 марта нельзя.
Именно ожидание «несуществующая дата должна дать false» встречается в вопросе о createFromFormat. Здесь решение строится из разных проверок: разбираемость, отсутствие исправлений и точная форма. Свободный strtotime не заменяет этот договор ввода.
Вернуть только принятую дату из функции проверки
Теперь объединим уже разобранные проверки в parseCalendarDate. На входе string input, на выходе DateTimeImmutable. Длина нашего ASCII-формата ровно10 байтов. Strlen возвращает эту длину, str_contains проверяет запрещённый нулевой байт; такие входы отклоняются до парсера, чтобы не пропустить ValueError. Исключение означает отсутствие принятой даты, а не запасной календарный день.
- parseCalendarDate(2026-02-28) → местный input
- Проверка формы, разбор, диагностика и обратный формат
- return date → переменная chosen → подпись28.02.2026
- Для30 февраля throw → catch; присваивания chosen в этом вызове нет
<?php
function parseCalendarDate(string $input): DateTimeImmutable {
if (strlen($input) !== 10 || str_contains($input, "\0")) {
throw new InvalidArgumentException('Нужна дата ГГГГ-ММ-ДД без лишних байтов');
}
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, new DateTimeZone('UTC'));
$errors = DateTimeImmutable::getLastErrors();
if ($date === false || ($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))
|| $date->format('Y-m-d') !== $input) {
throw new InvalidArgumentException('Нужна существующая дата ГГГГ-ММ-ДД');
}
return $date;
}
foreach (['2026-02-28', '2026-02-30', '28.02.2026'] as $input) {
try {
$chosen = parseCalendarDate($input);
echo '[', $input, '] принято: ', $chosen->format('d.m.Y'), "\n";
} catch (InvalidArgumentException $error) {
echo '[', $input, '] отклонено: ', $error->getMessage(), "\n";
}
}
try { parseCalendarDate("2026-02-2\0"); }
catch (InvalidArgumentException $error) { echo 'Нулевой байт отклонён: ', $error->getMessage(), "\n"; }
Ожидаемый результат
[2026-02-28] принято: 28.02.2026
[2026-02-30] отклонено: Нужна существующая дата ГГГГ-ММ-ДД
[28.02.2026] отклонено: Нужна существующая дата ГГГГ-ММ-ДД
Нулевой байт отклонён: Нужна дата ГГГГ-ММ-ДД без лишних байтов
Три прохода foreach независимо передают input в локальный параметр. Только первый return отдаёт объект в chosen, затем внешний format возвращает строку для echo. Два других входа направляются в catch и не используются для заказа. Отдельный последний вызов показывает раннее отклонение нулевого байта. Настоящее поле формы сначала проверяют на is_string; массив вместо поля не является строкой. Клиентская подсказка формата не заменяет серверную проверку.
У DateTimeImmutable::createFromFormat есть дополнительные параметры шаблона, дроби секунды и правила зоны: они перечислены в полной карточке. Здесь намеренно выбран один конкретный формат формы. Проверка календаря ещё не отвечает, разрешена ли доставка в этот день.
Применить к правильной дате правила доставки
Пусть для воспроизводимого расчёта «сегодня» —12 октября2026, доставка доступна с этого дня по19 октября включительно. Все входные даты ниже уже календарно корректны и стоят в одной зоне в00:00. Следующая проверка отвечает о допустимом диапазоне. Для реальной формы сначала вызывают parseCalendarDate из предыдущего шага.
<?php
$zone = new DateTimeZone('UTC');
$today = new DateTimeImmutable('2026-10-12 00:00:00', $zone);
$lastDay = $today->modify('+7 days');
echo 'Доступно: ', $today->format('d.m.Y'), ' — ', $lastDay->format('d.m.Y'), "\n";
foreach (['2026-10-11', '2026-10-14', '2026-10-21'] as $text) {
$requested = new DateTimeImmutable($text . ' 00:00:00', $zone);
if ($requested < $today) {
echo $text, ': отказ — день уже прошёл', "\n";
} elseif ($requested > $lastDay) {
echo $text, ': отказ — ещё не открыт для заказа', "\n";
} else {
echo $text, ': доставку можно предложить', "\n";
}
}
Ожидаемый результат
Доступно: 12.10.2026 — 19.10.2026
2026-10-11: отказ — день уже прошёл
2026-10-14: доставку можно предложить
2026-10-21: отказ — ещё не открыт для заказа
Объекты даты поддерживают сравнение моментов. При одинаковой зоне и00:00 оно соответствует порядку наших календарных дней. Границы включены: отклоняются только даты строго раньше today и строго позже lastDay. Наличие свободного курьера, выходные и праздничный календарь — отдельные проверки, которых этот пример не обещает.
Выбрать, что означает продление на месяц
Подписка началась31 января. У февраля нет31-го числа, поэтому фраза «на месяц» требует правила продукта. PHP умеет переносить лишние дни, но не знает, нужен ли вам3 марта,28 февраля или постоянное число суток. Сначала сравним два известных календарных правила.
- start:31 января2026
- +1 month → условное31 февраля →3 марта
- last day of next month → последний день февраля →28 февраля
- Оба результата отдельно подписаны; исходная дата не изменена
<?php
$start = new DateTimeImmutable('2026-01-31 10:00:00', new DateTimeZone('UTC'));
$overflow = $start->modify('+1 month');
$monthEnd = $start->modify('last day of next month');
echo 'Начало: ', $start->format('d.m.Y'), "\n";
echo 'Перенос лишних дней: ', $overflow->format('d.m.Y'), "\n";
echo 'Конец следующего месяца: ', $monthEnd->format('d.m.Y'), "\n";
Ожидаемый результат
Начало: 31.01.2026
Перенос лишних дней: 03.03.2026
Конец следующего месяца: 28.02.2026
Каждый modify вызывается у исходного start. Сдвиг месяца сохраняет номер дня и затем нормализует невозможное31 февраля. Второе правило прямо просит последний день следующего месяца. Это не две реализации одинаковой операции: по ним покупатель получит разные сроки.
Другой договор: «сохранить номер дня, но если его нет — взять последний». Он отличается от постоянного выбора конца месяца:30 апреля должно перейти в30 мая, хотя в мае31 день. Сначала найдём первый день следующего месяца, затем выберем меньший номер из исходного дня и длины следующего месяца.
<?php
function nextMonthlyDate(DateTimeImmutable $start): DateTimeImmutable {
$nextMonth = $start->modify('first day of next month');
$originalDay = (int) $start->format('j');
$daysInNextMonth = (int) $nextMonth->format('t');
$day = min($originalDay, $daysInNextMonth);
return $nextMonth->setDate((int) $nextMonth->format('Y'), (int) $nextMonth->format('n'), $day);
}
$zone = new DateTimeZone('UTC');
foreach (['2026-01-31 10:00:00', '2026-04-30 10:00:00'] as $text) {
$start = new DateTimeImmutable($text, $zone);
$renewal = nextMonthlyDate($start);
echo $start->format('d.m.Y H:i'), ' → ', $renewal->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
31.01.2026 10:00 → 28.02.2026 10:00
30.04.2026 10:00 → 30.05.2026 10:00
Format всегда возвращает текст: j даёт номер дня, t — число дней в месяце, Y — год, n — номер месяца. (int) готовит целые для min и setDate. При первом вызове min(31,28) даёт28; при втором min(30,31) даёт30. SetDate получает год, месяц, выбранный день и возвращает новую дату; return передаёт её в renewal. Время10:00 сохранено. Это один шаг: повторный вызов от28 февраля уже считает исходным днём28. Если расписание должно навсегда помнить31-е, храните отдельный исходный номер дня, а не выводите его из предыдущего результата.
Для независимых календарей выходных и праздничных дней нужна дополнительная модель. Не называйте +1 month «30 днями», а +1 day — «рабочим днём». Полные add/sub, DateInterval и последствия DST собраны в календарной арифметике.
Различить ровно24 часа и завтра в09:00
Промокод действует24 часа от15:00, а напоминание нужно показать завтра в09:00 по местным часам. Получатся разные сроки. DateInterval хранит описание интервала: P начинает период, T отделяет временную часть, 24H задаёт24 часа. Add применяет интервал и возвращает новую дату. SetTime задаёт часы, минуты и необязательные секунды в уже выбранном календарном дне.
- start:12 октября15:00, Europe/Moscow
- add(PT24H) →13 октября15:00: конец действия
- modify(+1 day), затем setTime(9,0) →13 октября09:00: напоминание
- Две переменные сохраняют два разных момента
<?php
$start = new DateTimeImmutable('2026-10-12 15:00:00', new DateTimeZone('Europe/Moscow'));
$duration = new DateInterval('PT24H');
$expiresAt = $start->add($duration);
$tomorrow = $start->modify('+1 day');
$remindAt = $tomorrow->setTime(9, 0);
echo 'Начало: ', $start->format('d.m.Y H:i P'), "\n";
echo 'Действует до: ', $expiresAt->format('d.m.Y H:i P'), "\n";
echo 'Напомнить: ', $remindAt->format('d.m.Y H:i P'), "\n";
Ожидаемый результат
Начало: 12.10.2026 15:00 +03:00
Действует до: 13.10.2026 15:00 +03:00
Напомнить: 13.10.2026 09:00 +03:00
ExpiresAt получил результат прибавления24 часов. RemindAt получил другой объект с часами09:00 следующего дня. Ни add, ни setTime не запланировали отправку: эти значения нужно передать реальному механизму уведомлений. Здесь нет ожидания или обращения к внешнему сервису.
DateTime::diff возвращает календарное описание разницы с учётом зон, а разница getTimestamp даёт число прошедших секунд для моментов с целой секундой. Это разные ответы. Для точного измерения времени выполнения кода нужен монотонный hrtime: системные часы могут корректироваться. Доли секунды и переполнение timestamp на32-битной платформе имеют отдельные ограничения в справочнике.
Посмотреть, почему календарный день иногда короче24 часов
В Europe/Berlin весной2026 часы переходят вперёд. От28 марта12:00 до29 марта12:00 проходит23 часа. Для встречи «завтра в то же местное время» это верно, а для срока «ровно24 часа» требуется другой момент. Здесь правила берутся из установленной базы часовых поясов PHP; обновление базы может изменить будущие региональные расписания.
- 28 марта12:00 +01:00
- Следующий местный день:29 марта12:00 +02:00
- Между моментами23 часа
- Полные24 часа закончатся29 марта13:00 +02:00
<?php
$start = new DateTimeImmutable('2026-03-28 12:00:00', new DateTimeZone('Europe/Berlin'));
$sameLocalTime = $start->modify('+1 day');
$after24Hours = $start->add(new DateInterval('PT24H'));
$calendarHours = ($sameLocalTime->getTimestamp() - $start->getTimestamp()) / 3600;
$elapsedHours = ($after24Hours->getTimestamp() - $start->getTimestamp()) / 3600;
echo 'Начало: ', $start->format('d.m.Y H:i P'), "\n";
echo 'Завтра по часам: ', $sameLocalTime->format('d.m.Y H:i P'), '; прошло ', $calendarHours, ' ч', "\n";
echo 'Через24 часа: ', $after24Hours->format('d.m.Y H:i P'), '; прошло ', $elapsedHours, ' ч', "\n";
Ожидаемый результат
Начало: 28.03.2026 12:00 +01:00
Завтра по часам: 29.03.2026 12:00 +02:00; прошло 23 ч
Через24 часа: 29.03.2026 13:00 +02:00; прошло 24 ч
GetTimestamp возвращает int секунд для каждого готового момента. Вычитание даёт82800 или86400 секунд; деление на3600 переводит их в часы. Метод setTimezone здесь не вызывался: изменилось региональное смещение на новой календарной дате. При осеннем переводе есть обратная ситуация с повторением местных часов. Несуществующее или повторённое время встречи требует отдельного правила принятия ввода, а не молчаливого согласия с парсером.
Для самостоятельного опыта выберите обычные12 и13 октября2026 в том же регионе: и следующий местный день, и24 часа дадут13 октября12:00. Смысл операций остаётся разным даже тогда, когда результаты временно совпадают.
Проверить истечение срока с заданным «сейчас»
В рабочем приложении часы меняются постоянно. Для объяснения и теста передадим текущее значение аргументом: тогда можно проверить момент до границы и саму границу. Принято правило «в момент expiresAt срок уже закончился», поэтому сравнение использует >=.
<?php
function expiryLabel(DateTimeImmutable $expiresAt, DateTimeImmutable $now): string {
if ($now >= $expiresAt) { return 'Срок закончился'; }
$secondsLeft = $expiresAt->getTimestamp() - $now->getTimestamp();
return 'Осталось ' . $secondsLeft . ' секунд';
}
$zone = new DateTimeZone('Europe/Moscow');
$expiresAt = new DateTimeImmutable('2026-10-13 15:00:00', $zone);
$before = new DateTimeImmutable('2026-10-13 14:00:00', $zone);
$atBoundary = new DateTimeImmutable('2026-10-13 15:00:00', $zone);
echo 'В14:00: ', expiryLabel($expiresAt, $before), "\n";
echo 'В15:00: ', expiryLabel($expiresAt, $atBoundary), "\n";
Ожидаемый результат
В14:00: Осталось 3600 секунд
В15:00: Срок закончился
Первый вызов получает срок15:00 и местный now14:00; сравнение ложно, разница3600 становится частью возвращённой строки в echo. Второй вызов получает now15:00, первая ветка возвращает «Срок закончился» и остальные строки тела пропускаются. Текущая дата компьютера нигде не читается. В рабочей точке входа создают now один раз и передают дальше; сама функция остаётся проверяемой.
Следующий шаг — расписание из нескольких дат: DatePeriod и границы повторения. Для передачи срока API документируйте формат, обязательную зону и точность долей секунды. Для формы показывайте ожидаемый формат рядом с полем и различайте «невозможный день» и «пока недоступная дата доставки».
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
DateTimeImmutable::createFromFormatРазобрать дату в заранее известном формате, не полагаясь на свободное угадывание.
PHP 5.5+ · date
public static function createFromFormat(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
- DateTimeImmutable::createFromFormat читает !d.m.Y и получает UTC
- date содержит объект 2024-06-15 00:00:00
- format возвращает строку для подписи заявки
Шаблон ввода описывает, какую часть строки читать: d — день, m — месяц, Y — год. ! предотвращает наследование случайных часов текущего момента. Без !/| незаданные поля берутся из текущего времени; если разобрано хотя бы одно поле времени, остальные временные поля обнуляются. Не переносите буквально все символы форматирования вывода: наборы похожи, но договоры различаются.
<?php
$input = '15.06.2024';
$date = DateTimeImmutable::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. ! даёт предсказуемые нулевые часы. Фабрика возвращает DateTimeImmutable в date, else вызывает format и использует его строку как подпись заявки. Этот первый вход задан автором; проверка false ещё не достаточна для произвольного текста формы.
<?php
$input = '30.02.2024';
$date = DateTimeImmutable::createFromFormat('!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 = DateTimeImmutable::createFromFormat('Y-m-!d', '2024-06-15', new DateTimeZone('UTC'));
echo $date->format('Y-m-d H:i:s'), "\n";
$kept = DateTimeImmutable::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 = 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-06-15 imported
Распознан день: 2024-06-15
Импорт отложен: в поле остался лишний хвост
Плюс в конце шаблона разрешил создать объект при лишнем хвосте, заменив ошибку предупреждением. Он не проверил смысл слова imported и не сделал его допустимым по договору импорта. Здесь выбран отказ. Если ваш формат намеренно хранит дату и отметку, разберите и проверьте оба поля по отдельности вместо добавления+ ради исчезновения false.
<?php
declare(strict_types=1);
try { DateTimeImmutable::createFromFormat('!Y-m-d', "2024-06-15\0"); }
catch (ValueError $error) { echo "Недопустимый байт во входе\n"; }
Ожидаемый результат
Недопустимый байт во входе
Это отдельный путь исключения поверх возвращаемого false. Проверка типов, ограничение длины и контроль допустимых байтов делают обработчик предсказуемым.
Полный набор символов: таблица разбора формата. Если нужны русские названия месяцев, используйте осмысленный локализованный парсер, например IntlDateFormatter, с явной локалью; замена регистра не превращает русский текст в английский.
Сравните: DateTimeImmutable::__construct, DateTimeImmutable::getLastErrors, date_parse_from_format, checkdate.
Время, часовые пояса и календарь. Практические задачи с датами.
Если разбирается день рождения или другая календарная дата, UTC может быть техническим носителем её компонентов: не переводите такую полночь в зону посетителя как момент оплаты. Checkdate проверяет три отдельных календарных компонента; конструктор со свободной строкой подходит для контролируемого текста, а не обещанного формата поля.
DateTimeImmutable::formatПолучить строку даты для человека или согласованного внешнего формата.
PHP 5.5+ · date
public function format(string $format): stringПолучить строку даты для человека или согласованного внешнего формата.
| Часть вызова | Договор |
|---|---|
| format | Строка шаблона. Y/m/d — год/месяц/день, H/i/s — часы/минуты/секунды. Полная таблица ниже по ссылке; константы DATE_* — готовые строки шаблонов. |
| Возврат | Строка; исходный объект и момент не меняются. format сам ничего не выводит и не переводит дату в другую зону. |
| Язык | Названия дней и месяцев английские, независимо от setlocale. Для локализованного вывода есть IntlDateFormatter. |
| Доли секунды | u/v работают с долей, сохранённой объектом. Целый timestamp в date() не содержит её. |
- 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('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.
Время, часовые пояса и календарь. Практические задачи с датами.
DateTimeImmutable::modifyИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
PHP 5.5+ · date
public function modify(string $modifier): DateTimeImmutableИзменить дату по понятному парсеру текстовому правилу, например «следующий понедельник».
| Часть вызова | Договор |
|---|---|
| Возврат | Новый объект; исходный не меняется. Сохраните результат. |
| 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 часа», «последний день месяца» означают разное. Сравнение календарной и прошедшей длительности. Здесь каждый результат сохраняется в отдельную переменную: immutable не изменяет исходник.
Бронь уже заканчивается 15 июня в 10:30 UTC. Нужно оставить часы и перенести окончание на следующий календарный день. Modify получает строку с правилом; сама строка не является PHP-выражением. Вызываем метод у конкретной даты через ->.
<?php
$date = new DateTimeImmutable('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
Дата в исходной переменной: 15.06.2024 10:30
В строке +1 day знак+ означает движение вперёд,1 — число дней, day — английское слово грамматики даты. Правило применяется к date; возвращённая дата попадает в next и затем в подпись нового срока. Исходный date остаётся 15 июня; next — новый объект. Результат Immutable надо сохранить: в PHP8.5 это дополнительно отмечено атрибутом NoDiscard.
<?php
$date = new DateTimeImmutable('2023-01-31 10:30:00', new DateTimeZone('UTC'));
$plain = $date->modify('+1 month');
$monthEnd = $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; лишние три дня переносятся в март. Второе правило сразу выбирает последний день февраля. Оба вызова начинают с неизменённого date. Для подписки результат выбирает договор. «Сохранить номер дня, ограничив концом месяца» — ещё одно правило: для 15 января оно даст 15 февраля, а не 28-е.
<?php
$date = new DateTimeImmutable('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 DateTimeImmutable('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.
Время, часовые пояса и календарь. Практические задачи с датами.
<?php
$date = new DateTimeImmutable('2024-01-31 10:00:00', new DateTimeZone('UTC'));
for ($number = 1; $number <= 3; $number++) {
$date = $date->modify('last day of next month');
echo 'Списание ', $number, ': ', $date->format('d.m.Y H:i'), "\n";
}
Ожидаемый результат
Списание 1: 29.02.2024 10:00
Списание 2: 31.03.2024 10:00
Списание 3: 30.04.2024 10:00
Договор здесь явно задаёт последний день каждого месяца. На каждом шаге for number увеличивается, modify создаёт следующий месяц с его настоящим последним днём, присваивание date сохраняет новый объект для следующего шага. Часы 10:00 остаются. Это подготовка расписания, деньги не списываются. Для договора «30-е число, а в коротком месяце последний день» нужно хранить исходное число 30 отдельно: после февраля его нельзя восстановить из одного объекта даты.
<?php
$current = new DateTimeImmutable('2024-06-15 10:00:00', new DateTimeZone('UTC'));
$day = $current->modify('first day of next month');
$startsAt = $day->setTime(0, 0);
echo 'Следующий период начинается: ', $startsAt->format('d.m.Y H:i'), "\n";
echo 'Исходная проверка: ', $current->format('d.m.Y H:i'), "\n";
Ожидаемый результат
Следующий период начинается: 01.07.2024 00:00
Исходная проверка: 15.06.2024 10:00
First day of next month выбирает 1 июля, сохраняя 10:00. Отдельный setTime(0,0) устанавливает полночь, потому что договор периода требует её явно. Первый возврат хранится в day, второй — в startsAt; current остался 15 июня 10:00. Так не теряется полезное различие между выбором календарного дня и выбором времени начала.
DateTimeImmutable::diffПолучить календарный интервал, переводящий исходную дату в целевую.
PHP 5.5+ · date
public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateIntervalПолучить календарный интервал, переводящий исходную дату в целевую.
| Часть вызова | Договор |
|---|---|
| targetObject | Целевая дата через общий DateTimeInterface. |
| absolute | По умолчанию false. true только сбрасывает знак invert; это не переключатель UTC/прошедших секунд. |
| Возврат | DateInterval с календарными компонентами и общим числом полных дней days. Даты не изменяются. |
| DST и обратимость | Разность учитывает часовые переходы. Для абсолютной длительности переведите обе даты в UTC; для секунд можно вычесть timestamps. Календарная операция не всегда обратима. |
- 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.
Время, часовые пояса и календарь. Практические задачи с датами.
timeЗафиксировать момент создания записи и вычислить простой срок действия.
PHP 4+ · date
time(): intЗафиксировать момент создания записи и вычислить простой срок действия.
| Часть вызова | Договор |
|---|---|
| Аргументы | Нет. |
| Возврат | Текущая Unix-секунда с эпохи 1970-01-01 00:00 UTC, int. Разрешение ответа — целая секунда. |
| Границы | Ни миллисекунды, ни местное время, ни гарантия точности/монотонности системных часов. |
- time() →createdAt с текущей Unix-секундой
- expiresAt=createdAt+300
- checkAt=createdAt+120 —модель проверки
- remaining=180 →код ещё можно предъявить
Скобки time() пусты: функция читает текущие системные часы и возвращает int секунд. $createdAt сохраняет один снимок. Не вызывайте time снова для каждой части одной записи, если они должны иметь общий момент; вторая секунда может уже отличаться.
Для кода подтверждения нужен момент создания и срок действия. Пустые скобки time() означают, что аргументы не передаются; результат —целое число секунд от Unix-эпохи, а не текст даты. Например, число 1704067200 обозначает 1 января 2024 00:00 UTC. В первом расчёте используем настоящее чтение часов, а следующая функция получает фиксированный снимок, чтобы показать точные даты.
<?php
$createdAt = time();
$expiresAt = $createdAt + 5 * 60;
$checkAt = $createdAt + 2 * 60;
$remaining = max(0, $expiresAt - $checkAt);
echo 'Полный срок кода: ', $expiresAt - $createdAt, " секунд\n";
echo 'При проверке через две минуты осталось: ', $remaining, " секунд\n";
if ($remaining > 0) { echo "Код ещё можно предъявить\n"; }
Ожидаемый результат
Полный срок кода: 300 секунд
При проверке через две минуты осталось: 180 секунд
Код ещё можно предъявить
Time прочитал системные часы один раз и вернул целую Unix-секунду в createdAt. Её конкретное число меняется при запуске; относительный результат не меняется. ExpiresAt —срок через 300 секунд. CheckAt явно моделирует проверку через 120 секунд, это не второй вызов часов и не ожидание. Вычитание даёт 180, max не допускает отрицательный остаток. Пример только рассчитывает срок: он не отправляет код и не реализует весь процесс подтверждения.
<?php
function codeStatus(int $expiresAt, int $now): string
{
if ($now >= $expiresAt) { return 'Код истёк; запросите новый'; }
return 'Осталось ' . ($expiresAt - $now) . ' секунд';
}
$createdAt = 1704067200;
$expiresAt = $createdAt + 300;
echo 'Создан: ', gmdate('d.m.Y H:i:s', $createdAt), " UTC\n";
echo 'Действует до: ', gmdate('d.m.Y H:i:s', $expiresAt), " UTC (граница исключена)\n";
echo 'Проверка 00:04:30: ', codeStatus($expiresAt, 1704067470), "\n";
echo 'Проверка 00:05:00: ', codeStatus($expiresAt, 1704067500), "\n";
Ожидаемый результат
Создан: 01.01.2024 00:00:00 UTC
Действует до: 01.01.2024 00:05:00 UTC (граница исключена)
Проверка 00:04:30: Осталось 30 секунд
Проверка 00:05:00: Код истёк; запросите новый
CreatedAt здесь намеренно фиксированный снимок, не новое чтение текущих часов. В рабочем месте создания вместо него приходит time(), а в месте проверки отдельный time() передают вторым аргументом codeStatus. В первом вызове локальные параметры получают 1704067500 и 1704067470: return возвращает строку с 30 секундами прямо во внешний echo. Во втором now равен expiresAt, поэтому ранний return выбирает отказ. Такая явная передача now позволяет проверять границы без sleep.
Timestamp не содержит timezone и не меняется от date_default_timezone_set. Системные часы могут корректироваться: для длительности измерения используйте монотонный hrtime, а не обещайте неубывание time. Значение не уникальный ID и не источник случайности. $_SERVER["REQUEST_TIME"] — снимок начала запроса при соответствующей SAPI, а не новый вызов часов. На 32-bit действует диапазон PHP int около 1901…2038; на 64-bit он шире, но тоже конечен.
Сравните: microtime, gettimeofday, date, DateTimeImmutable::getTimestamp.
Время, часовые пояса и календарь. Практические задачи с датами.
Возвращает момент по системным часам. У timestamp нет собственного часового пояса; пояс применяется при представлении календарной даты.
<?php
declare(strict_types=1);
$now = time();
$expiresAt = $now + 300;
echo $expiresAt - $now, "\n";
Ожидаемый результат
300
Получаем один now и строим TTL5 минут. Повторный time посреди расчета мог бы дать другую секунду и сделать граничный тест нестабильным.
<?php
declare(strict_types=1);
function isExpired(int $expiresAt, int $now): bool
{
return $now >= $expiresAt;
}
$now = time();
echo isExpired($now - 1, $now) ? "Истек\n" : "Действует\n";
Ожидаемый результат
Истек
Проверка границы>= задана явно. В тесте можно заменить now фиксированным числом; реальный токен дополнительно проверяет подпись и назначение.
Сравните. DateTimeImmutable удобен для календарной логики; time — простой timestamp. В примерах текущее значение не печатается, чтобы результат был воспроизводимым.
checkdateОтклонить несуществующую дату доставки до её сохранения.
PHP 4+ · date
checkdate(int $month, int $day, int $year): boolОтклонить несуществующую дату доставки до её сохранения.
| Часть вызова | Договор |
|---|---|
| month | 1…12. |
| day | 1…число дней именно в этом месяце/году. |
| year | 1…32767; год 0 и отрицательные не принимаются. |
| Возврат / ошибки | true/false для допустимых int; неправильный тип аргумента — TypeError. Календарь григорианский, зона не нужна. |
- month=2,day=29,year=2024
- checkdate(month,day,year) →true
- if выбирает подтверждение 29.02.2024
- Для 2023 возврат false →доставка не назначается
checkdate принимает отдельные целые числа в порядке месяц, день, год. Она не разбирает строку "29.02.2024" и не проверяет часы. В boolean-выводе используйте условие ? "да" : "нет": сам false при echo не даст видимого слова.
<?php
$year = 2024;
$month = 2;
$day = 29;
if (checkdate($month, $day, $year)) {
echo 'Дата доставки принята: ', sprintf('%02d.%02d.%04d', $day, $month, $year), "\n";
} else { echo "Выберите существующий день доставки\n"; }
$year = 2023;
if (!checkdate($month, $day, $year)) {
echo 'Доставка 29.02.', $year, " не назначена: такого дня нет\n";
}
Ожидаемый результат
Дата доставки принята: 29.02.2024
Доставка 29.02.2023 не назначена: такого дня нет
В месте вызова значения 2,29,2024 передаются в параметры month,day,year именно в таком порядке. True выбирает ветку с подписью даты. После изменения year на 2023 ответ false и знак! превращает его в условие отказа. Sprintf только собирает уже проверенные числа в строку; функция checkdate сама ничего не выводит и не переносит день на март.
<?php
declare(strict_types=1);
function calendarDate(int $year, int $month, int $day): ?string
{
if (!checkdate($month, $day, $year)) { return null; }
return sprintf('%04d-%02d-%02d', $year, $month, $day);
}
echo calendarDate(2024, 4, 30) ?? 'Ошибка', "\n";
echo calendarDate(2024, 4, 31) ?? 'Ошибка', "\n";
Ожидаемый результат
2024-04-30
Ошибка
! отрицает ответ; ?string разрешает null. sprintf формирует строку уже проверенной даты. Эта функция принимает int: HTTP-поля сначала нужно проверить как строки и безопасно преобразовать.
Дата — ещё не момент: день рождения можно хранить без timezone и timestamp. mktime/strtotime нормализуют часть ошибочных дат, поэтому их успешный результат не заменяет checkdate. Для фиксированного строкового формата используйте createFromFormat с проверкой warnings и обратного форматирования. Попробуйте годы 1900 и 2000 с 29 февраля: первый false, второй true.
Сравните: mktime, strtotime, date_parse_from_format.
Время, часовые пояса и календарь. Практические задачи с датами.
Возвращает bool с учетом длины месяца и високосных годов. Не разбирает строки и не проверяет бизнес-диапазон дат.
<?php
declare(strict_types=1);
echo checkdate(2, 29, 2024) ? "Да\n" : "Нет\n";
echo checkdate(2, 29, 2026) ? "Да\n" : "Нет\n";
Ожидаемый результат
Да
Нет
Удобная граничная проверка для трех отдельных полей формы. Сами поля сначала должны пройти проверку целых и диапазона.
<?php
declare(strict_types=1);
$month = 4;
$day = 31;
$year = 2026;
echo checkdate($month, $day, $year) ? "Дата принята\n" : "Нет такого дня\n";
Ожидаемый результат
Нет такого дня
Проверка не нормализует 31 апреля в 1 мая, что удобно для пользовательского ввода. Для прибавления периода применяется другая операция.
Сравните. Для строгой строки Y-m-d используйте createFromFormat с getLastErrors и обратной проверкой формата.
Во втором полном примере вызов calendarDate(2024,4,30) передаёт год, месяц и день в одноимённые локальные параметры. Внутри порядок меняется для checkdate(month,day,year). При отказе return null сразу завершает функцию; ?? в месте вызова выбирает «Ошибка». Для 30 апреля sprintf возвращает 2024-04-30, этот return попадает прямо в echo. Функция не печатает сама, поэтому возвращённую строку можно вместо echo сохранить в поле заявки.
Источники и обсуждения
- PHP Manual: class.datetimeimmutable
- PHP Manual: datetimeimmutable.createfromformat
- PHP Manual: datetime.diff
- PHP Manual: datetimeimmutable.modify
- Разбор реальной проблемы на Stack Overflow — обсуждение проблемы
- PHP Manual: datetime.format
- PHP: datetimeimmutable.construct
- PHP: datetime.examples-arithmetic
- PHP: datetime.getlasterrors
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


