Проблема с датами обычно не в синтаксисе, а в том, что в трёх местах живёт разное время: в ОС сервера, в настройках PHP и в MySQL. Из‑за этого одна и та же запись может «ехать» на часы вперёд или назад — особенно когда проект выходит за пределы одного часового пояса.
Какой тип колонки выбрать: DATETIME или TIMESTAMP
Коротко:
DATETIME— хранит дату и время ровно так, как ты её записал. Никаких автоматических конвертаций. Это удобно, когда важно зафиксировать «момент по часам» (дата публикации статьи, время заказа, дата подписания документа).TIMESTAMP— внутри всегда хранится в UTC, а при чтении/записи конвертируется под текущую зону сессии. Удобно для логов и событий, где важна привязка к реальному моменту, но зона может меняться.
Практическое правило: для публикаций, заказов, любых «фиксированных» дат бери DATETIME. Для логов и системных меток можно TIMESTAMP, но только если зоны настроены явно.
Пример таблицы:
CREATE TABLE articles ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, published_at DATETIME NOT NULL, -- фиксированная дата публикации created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- авто-время БД updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP );
Где и как задавать часовой пояс
PHP
Лучше всего задать зону в одном месте при старте приложения:
date_default_timezone_set('UTC');
Дальше все объекты DateTime создавай с явной зоной. Если пользователь из Москвы — конвертируй в Europe/Moscow только для отображения.
Пример:
$dt = new DateTime('2025-12-10 14:30:00', new DateTimeZone('Europe/Moscow')); $utc = (clone $dt)->setTimezone(new DateTimeZone('UTC')); echo $utc->format('Y-m-d H:i:s'); // строка для записи в DATETIME
Никогда не передавай в SQL строки вида 10.12.2025 14:30 — формат d.m.Y ломается на разных локалях. Для базы используй ISO‑8601: Y-m-d H:i:s.
MySQL
Проверь текущие зоны:
SELECT @@global.time_zone, @@session.time_zone;
Если видишь SYSTEM — MySQL берёт зону ОС. Это нестабильно. Лучше задать UTC в конфиге my.cnf:
[mysqld] default_time_zone = '+00:00'
После этого перезапусти MySQL.
Для сессии можно установить зону прямо из PHP:
$pdo->exec("SET time_zone = '+00:00';");
Тогда NOW() и DEFAULT CURRENT_TIMESTAMP будут в UTC — предсказуемо и одинаково на всех серверах.
Как правильно сохранять дату из PHP
Используй подготовленные выражения и ISO‑формат.
$pdo = new PDO( 'mysql:host=localhost;dbname=blog;charset=utf8mb4', 'user', 'pass', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); $pdo->exec("SET time_zone = '+00:00';"); // сессия в UTC $published = new DateTime('2025-11-20 10:00:00', new DateTimeZone('Europe/Moscow')); $publishedUtc = (clone $published)->setTimezone(new DateTimeZone('UTC')); $sql = 'INSERT INTO articles (title, published_at) VALUES (?, ?)'; $stmt = $pdo->prepare($sql); $stmt->execute([ 'Моя статья', $publishedUtc->format('Y-m-d H:i:s') ]);
Если хочешь, чтобы время ставила сама БД, используй UTC_TIMESTAMP() — это надёжнее, чем NOW():
INSERT INTO articles (title, created_at) VALUES ('Статья без времени PHP', UTC_TIMESTAMP());
Как правильно читать и показывать дату
Храни в UTC (или как есть в DATETIME), а конвертируй в нужную зону только на выводе.
$stmt = $pdo->query('SELECT id, title, published_at FROM articles ORDER BY published_at DESC'); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $dt = new DateTime($row['published_at'], new DateTimeZone('UTC')); $userZone = new DateTimeZone('Europe/Moscow'); echo $dt->setTimezone($userZone)->format('d.m.Y H:i') . ' — ' . $row['title'] . PHP_EOL; }
Так ты получаешь «абсолютный момент» в базе и показываешь его в нужной зоне. Рассинхрон исчезает.
Полезные функции MySQL для работы с датами
NOW()— текущее время сессии (зависит от зоны).UTC_TIMESTAMP()— всегда UTC.DATE_ADD(date, INTERVAL 1 DAY)— прибавить интервал.DATEDIFF(date1, date2)— разница в днях.CONVERT_TZ(dt, from_tz, to_tz)— явная конвертация зон.
Пример с явной конвертацией:
SELECT CONVERT_TZ(published_at, '+00:00', 'Europe/Moscow') AS local_time FROM articles;
Но лучше делать конвертацию в PHP: там проще управлять зонами и тестировать.
