D'où vient l'écart
PHP utilise le fuseau défini par date.timezone dans sa configuration, et UTC lorsqu'il n'est pas défini. MySQL et MariaDB, eux, utilisent par défaut le fuseau du système (time_zone = SYSTEM), souvent Europe/Paris sur un serveur français.
Résultat en été : date('Y-m-d H:i:s') côté PHP et NOW() côté base renvoient deux heures différentes à la même seconde. Une heure d'écart en hiver.
Où cela casse
Tant que PHP et la base ne se comparent jamais, tout va bien. Le problème apparaît dès qu'une date calculée en PHP est comparée à une date produite par la base :
// Jeton valable 1 heure : l'expiration est calculée en PHP (UTC)…
$expire = date('Y-m-d H:i:s', time() + 3600);
$pdo->prepare('INSERT INTO jetons (jeton, expire_le) VALUES (?, ?)')
->execute([$jeton, $expire]);
// …et vérifiée par MySQL (heure de Paris) : en été, le jeton est
// déjà expiré depuis une heure au moment où on le crée.
SELECT * FROM jetons WHERE jeton = ? AND expire_le > NOW(); DATETIME ou TIMESTAMP : ils ne se comportent pas pareil
DATETIME stocke la valeur telle quelle, sans notion de fuseau. TIMESTAMP la convertit en UTC à l'écriture puis dans le fuseau de la session à la lecture. Deux connexions réglées différemment liront donc deux heures différentes pour la même ligne TIMESTAMP.
À savoir aussi : sous MySQL, et sous MariaDB jusqu'à ses versions 11.4, TIMESTAMP ne va pas au-delà du 19 janvier 2038. Pour une date d'échéance lointaine ou une date de naissance, préférez DATETIME ou DATE.
La correction
1. Fixer le fuseau de PHP, explicitement, au démarrage de l'application :
date_default_timezone_set('Europe/Paris');2. Aligner la session MySQL sur PHP, à chaque connexion :
$pdo->exec("SET time_zone = '" . date('P') . "'"); // ex. +02:00Le décalage numérique fonctionne partout. Un nom de fuseau comme 'Europe/Paris' est plus juste lors des changements d'heure, mais il exige que les tables de fuseaux soient chargées dans MySQL (mysql_tzinfo_to_sql), ce qui n'est pas toujours le cas.
3. Mieux encore : faire l'arithmétique de dates d'un seul côté. Si l'expiration est vérifiée par la base, calculez-la aussi dans la base :
INSERT INTO jetons (jeton, expire_le) VALUES (?, NOW() + INTERVAL 1 HOUR);Une seule horloge, aucune conversion, aucun piège.
Comment vérifier votre serveur
php -r 'echo date_default_timezone_get(), " ", date("H:i"), PHP_EOL;'
mysql -e "SELECT @@global.time_zone, @@session.time_zone, NOW();"Si les deux heures affichées diffèrent, chaque comparaison entre une date PHP et une date SQL de votre application mérite d'être relue.
Publié le 08/09/2026.