Работа с датами в MySQL и PHP

Проблема с датами обычно не в синтаксисе, а в том, что в трёх местах живёт разное время: в ОС сервера, в настройках PHP и в MySQL. Из‑за этого одна и та же запись может «ехать» на часы вперёд или назад — особенно когда проект выходит за пределы одного часового пояса.


Какой тип колонки выбрать: DATETIME или TIMESTAMP

Коротко:

  • DATETIME — хранит дату и время ровно так, как ты её записал. Никаких автоматических конвертаций. Это удобно, когда важно зафиксировать «момент по часам» (дата публикации статьи, время заказа, дата подписания документа).
  • TIMESTAMP — внутри всегда хранится в UTC, а при чтении/записи конвертируется под текущую зону сессии. Удобно для логов и событий, где важна привязка к реальному моменту, но зона может меняться.

Практическое правило: для публикаций, заказов, любых «фиксированных» дат бери DATETIME. Для логов и системных меток можно TIMESTAMP, но только если зоны настроены явно.

Пример таблицы:

sql
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

Лучше всего задать зону в одном месте при старте приложения:

php
date_default_timezone_set('UTC');

Дальше все объекты DateTime создавай с явной зоной. Если пользователь из Москвы — конвертируй в Europe/Moscow только для отображения.

Пример:

php
$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

Проверь текущие зоны:

sql
SELECT @@global.time_zone, @@session.time_zone;

Если видишь SYSTEM — MySQL берёт зону ОС. Это нестабильно. Лучше задать UTC в конфиге my.cnf:

ini
[mysqld]
default_time_zone = '+00:00'

После этого перезапусти MySQL.

Для сессии можно установить зону прямо из PHP:

php
$pdo->exec("SET time_zone = '+00:00';");

Тогда NOW() и DEFAULT CURRENT_TIMESTAMP будут в UTC — предсказуемо и одинаково на всех серверах.


Как правильно сохранять дату из PHP

Используй подготовленные выражения и ISO‑формат.

php
$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():

sql
INSERT INTO articles (title, created_at) VALUES ('Статья без времени PHP', UTC_TIMESTAMP());

Как правильно читать и показывать дату

Храни в UTC (или как есть в DATETIME), а конвертируй в нужную зону только на выводе.

php
$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) — явная конвертация зон.

Пример с явной конвертацией:

sql
SELECT CONVERT_TZ(published_at, '+00:00', 'Europe/Moscow') AS local_time FROM articles;

Но лучше делать конвертацию в PHP: там проще управлять зонами и тестировать.

Добавить комментарий