Comment stocker une date et heure en base de données
Stocker une date en heure locale est une faute professionnelle. Voici la règle d'or : tout stocker en UTC avec le bon type colonne, et convertir à l'affichage.
Stocker une date ou une heure en base de données est l'une des décisions les plus chargées de conséquences à long terme. Une mauvaise modélisation rend impossible la prise en compte de l'heure d'été, des fuseaux ou des migrations de serveur.
La règle d'or
Stockez tout en UTC. Convertissez à l'affichage. C'est la seule stratégie qui résiste aux migrations de serveur, aux utilisateurs multi-fuseaux et aux changements de DST.
Types de colonnes : ce qu'il faut savoir
| Type | SGBD | Comportement | Recommandé ? |
|---|---|---|---|
| TIMESTAMPTZ | PostgreSQL | Convertit en UTC à l'insertion, restitue dans le fuseau de session | ✅ Oui (défaut) |
| TIMESTAMP | PostgreSQL | Stocke tel quel, sans fuseau | ❌ Évitez (sauf cas spécifique) |
| DATETIME | MySQL | Stocke tel quel, pas de fuseau | ⚠️ À convertir en UTC en amont |
| TIMESTAMP | MySQL | Convertit en UTC, mais limité à 2038 | ⚠️ À éviter (bug 2038) |
| DATETIMEOFFSET | SQL Server | Stocke avec offset (pas la zone) | ✅ Oui |
| ISODate | MongoDB | UTC en interne (BSON) | ✅ Oui |
PostgreSQL : la référence
CREATE TABLE events (
id SERIAL PRIMARY KEY,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
timezone TEXT NOT NULL DEFAULT 'UTC' -- nom IANA si l'utilisateur en a besoin
);
INSERT INTO events (occurred_at, timezone)
VALUES ('2026-05-19T14:30:00+02:00', 'Europe/Paris');
-- Stocké en interne : 2026-05-19 12:30:00 UTC
SELECT occurred_at AT TIME ZONE 'Asia/Tokyo' FROM events;
-- → 2026-05-19 21:30:00
Pourquoi conserver le nom IANA séparément ?
Pour les événements futurs (réunion dans 6 mois), stocker uniquement l'UTC est insuffisant : si le pays modifie ses règles DST entre-temps, l'heure locale aura changé. Stocker occurred_at TIMESTAMPTZ + timezone TEXT (ex. Europe/Paris) permet de recalculer l'heure locale exacte avec les règles à jour.
Convertir entre fuseaux pour vos données
MySQL : les pièges
TIMESTAMPest limité à 2038. PréférezDATETIME(6)avec une convention claire : toutes les valeurs en UTC.- Configurer
time_zone = '+00:00'côté serveur évite que MySQL « aide » en convertissant. - Ne mélangez jamais
TIMESTAMP(UTC) etDATETIME(local) dans la même base.
MongoDB
Le type BSON Date est un entier 64 bits en millisecondes UTC depuis l'epoch. Toute date stockée est donc déjà en UTC. Le driver convertit en Date JS côté client.
Côté application : 4 règles
- Toujours convertir en UTC avant l'insertion.
- Stocker le fuseau utilisateur dans un champ séparé (table users).
- Convertir à l'affichage avec
Intl.DateTimeFormatou équivalent. - Jamais de calcul de durée sur heure locale : une journée DST a 23 ou 25 heures.
Exemple JavaScript / Node
// À l'insertion
const occurredAtUTC = new Date().toISOString(); // toujours UTC
await db.query(
'INSERT INTO events(occurred_at) VALUES($1)',
[occurredAtUTC]
);
// À l'affichage
const row = await db.query('SELECT occurred_at FROM events WHERE id=$1', [id]);
const formatted = new Intl.DateTimeFormat('fr-FR', {
dateStyle: 'long',
timeStyle: 'short',
timeZone: userTimezone // ex. 'Europe/Paris'
}).format(new Date(row.occurred_at));