Работа с датами — одна из самых коварных вещей в коде: на секунду ошибся, часовой пояс не учёл — и логика ломается. В реальных задачах (логи, сессии, сроки действия токенов, пагинация по дате) лучше не полагаться на date() и ручное склеивание строк, а использовать DateTime и DateTimeZone.
Зачем нужен DateTime вместо date()/time()
time() даёт timestamp, date() делает строку. Этого хватает, чтобы «быстро вывести», но не хватает для:
- сравнения дат («эта дата раньше той?»),
- арифметики («плюс 3 дня», «минус 2 часа»),
- корректной работы с часовыми поясами,
- валидации формата ввода.
DateTime хранит состояние, умеет менять часовой пояс, считать разницу, модифицировать дату и форматировать. Это именно то, что нужно в плагинах, интеграции и любой нормальной бизнес‑логике.
Как создать DateTime
Текущее время
$now = new DateTime(); // время сервера с его часовым поясом по умолчанию
Из строки (самый частый кейс)
Если приходит дата из формы или API, всегда указывай формат:
$format = 'Y-m-d H:i:s'; $input = '2024-10-05 14:30:00'; $dt = DateTime::createFromFormat($format, $input); if (!$dt) { // здесь обрабатываем ошибку: массив ошибок покажет, что не так $errors = DateTime::getLastErrors(); throw new Exception('Неверная дата: ' . implode(', ', $errors['errors'])); }
Это надёжнее, чем пытаться распарсить строку вручную.
Из timestamp
$ts = 1700000000; $dt = (new DateTime())->setTimestamp($ts);
Часовые пояса: как не запутаться
Правило: хранить в UTC, показывать в локальном поясе. Это стандарт, который спасает от проблем при переездах серверов и смене настроек.
Создать сразу с нужным поясом
$utc = new DateTime('now', new DateTimeZone('UTC')); $msk = new DateTime('now', new DateTimeZone('Europe/Moscow'));
Сменить пояс у существующего объекта
$dt = new DateTime('2024-10-05 12:00:00', new DateTimeZone('UTC')); $dt->setTimezone(new DateTimeZone('Europe/Moscow')); // момент времени тот же, но теперь это 15:00 по Москве
Важно: setTimezone() не «переводит» время, он меняет интерпретацию момента. Это как посмотреть на одни и те же часы из разных городов.
Получить список поясов
$zones = DateTimeZone::listIdentifiers(); // массив строк: 'UTC', 'Europe/Moscow', 'Asia/Yekaterinburg' и т.д.
Форматирование: как вывести дату
Используй format(). Спецификаторы те же, что и в date():
echo $dt->format('Y-m-d H:i:s'); // 2024-10-05 15:30:00 echo $dt->format('d.m.Y'); // 05.10.2024 echo $dt->format('j F Y, H:i'); // 5 October 2024, 15:30 echo $dt->format('c'); // ISO 8601: 2024-10-05T15:30:00+03:00
Для API и обмена данными лучше брать однозначные форматы: c или Y-m-d\TH:i:sP.
Арифметика: плюс, минус, разница
Изменить дату
$dt = new DateTime('2024-10-01'); $dt->modify('+7 days'); // 2024-10-08 $dt->modify('-1 month'); // 2024-09-08 $dt->modify('next Monday'); // ближайший понедельник вперёд $dt->modify('last Friday'); // последняя пятница назад
modify() меняет сам объект. Если нужно оставить оригинал — клонируй:
$original = new DateTime('2024-10-01'); $copy = clone $original; $copy->modify('+1 day');
Разница между двумя датами
$start = new DateTime('2024-10-01 10:00:00'); $end = new DateTime('2024-10-03 14:30:00'); $interval = $start->diff($end); echo $interval->days; // полные дни echo $interval->h; // часы (в пределах суток) echo $interval->i; // минуты echo $interval->format('%a дней'); // общее число дней echo $interval->format('%h часов'); // часы
$interval — это отдельный объект DateInterval. Он умеет форматироваться и считать дни, месяцы, годы.
Сравнение дат
DateTime можно сравнивать напрямую:
$a = new DateTime('2024-10-01'); $b = new DateTime('2024-10-05'); if ($a < $b) { /* $a раньше */ } if ($a > $b) { /* $a позже */ } if ($a == $b) { /* равны */ }
Работает даже с разными часовыми поясами: сравнение идёт по моменту времени (UTC‑эквиваленту).
Практические кейсы и типовые решения
Кейс 1: срок действия токена истёк?
$expiresAt = new DateTime($token['expires_at']); // из БД, уже в UTC $now = new DateTime('now', new DateTimeZone('UTC')); if ($expiresAt <= $now) { // токен истёк }
Главное: обе даты в одном поясе (лучше UTC).
Кейс 2: «через N дней» для напоминания
$base = new DateTime('now', new DateTimeZone('UTC')); $remind = clone $base; $remind->modify('+3 days'); $remindString = $remind->format('Y-m-d H:i:s'); // для сохранения в БД
Кейс 3: валидация диапазона дат из формы
$format = 'Y-m-d'; $from = DateTime::createFromFormat($format, $_POST['from']); $to = DateTime::createFromFormat($format, $_POST['to']); if (!$from || !$to) { throw new Exception('Некорректный формат даты'); } if ($from > $to) { throw new Exception('Дата "от" не может быть позже даты "до"'); }
Частые ошибки и как их избежать
- Смешивают строки и объекты. Не делайте
$date = date('Y-m-d') + 1. Для арифметики всегда используйтеDateTime. - Не проверяют результат
createFromFormat. Если формат не совпал, вернётсяfalse. Всегда проверяйте и смотритеgetLastErrors(). - Хранят локальное время вместо UTC. Потом при смене сервера или пояса всё «уедет». Храните в UTC, конвертируйте только на выводе.
- Забывают клонировать объект.
modify()меняет оригинал. Если оригинал нужен — делайтеclone. - Думали, что
setTimezone«переводит» часы. Он только меняет интерпретацию. Момент времени остаётся тем же.
Мини‑шпаргалка по форматам
| Формат | Пример | Где удобно |
|---|---|---|
Y-m-d H:i:s |
2024-10-05 15:30:00 | БД, логи, внутренний обмен |
d.m.Y |
05.10.2024 | Вывод пользователю |
c |
2024-10-05T15:30:00+03:00 | JSON, API, машиночитаемые данные |
j F Y, H:i |
5 October 2024, 15:30 | Читаемый вывод в интерфейсе |
