Quel format pour les horodatages en JSON
L’essentiel : Les nombres epoch et les chaînes ISO ont chacun leurs compromis, mais c'est les mélanger qui casse réellement les choses. Utilisez ISO 8601 avec un offset, stockez en UTC, affichez dans le fuseau de l'utilisateur.

L'essentiel : utilisez ISO 8601 avec un offset explicite sur le fil, stockez en UTC avec un Z final, et convertissez seulement à l'affichage ; si vous choisissez plutôt des nombres epoch, fixez globalement secondes contre millisecondes et mettez l'unité dans le nom du champ.
Pourquoi 2026-10-30 14:30:00 ne suffit pas
Cette chaîne ne porte aucun fuseau. 14h30 à Shanghai et 14h30 à Londres sont à huit heures d'écart, et il n'y a aucun moyen de savoir laquelle est visée. new Date('2026-10-30 14:30:00') parse dans le fuseau local du runtime, donc le serveur et le navigateur dérivent deux instants différents d'une seule charge utile — le genre de bug qui ne se reproduit jamais.
Une date nue 2026-10-30 n'est pas un instant du tout. Bien pour les anniversaires et périodes de facturation, faux pour une heure de création.
Deux formats viables
Chaînes ISO 8601
{
"createdAt": "2026-10-30T14:30:00+08:00",
"updatedAt": "2026-10-30T06:30:00Z"
}
Lisible, triable quand le format est cohérent, précision à la milliseconde si nécessaire, et auto-descriptif.
Nombres epoch
{ "createdAt": 1793519400 }
Compact et sans ambiguïté, mais illisible, et secondes contre millisecondes est un danger permanent. Si vous choisissez les epochs, standardisez l'unité et mettez-la dans le nom du champ : createdAtMs est bien plus sûr que createdAt.
Conventions à fixer tôt
| Élément | Convention |
|---|---|
| Format sur le fil | ISO 8601 avec Z ou +08:00 |
| Fuseau de stockage | UTC, pour que l'heure d'été n'entre jamais en jeu |
| Précision | La milliseconde suffit |
| Nommage des champs | createdAt, updatedAt, expiresAt |
| Dates pures | Un champ date séparé, par ex. birthday: "1990-01-01" |
| Durées | Entier plus une unité, par ex. timeoutMs: 3000 |
Affichage
new Intl.DateTimeFormat('en-GB', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'Europe/London',
}).format(new Date('2026-10-30T06:30:00Z'));
Spécifiez timeZone explicitement quand l'affichage ne doit pas suivre l'appareil. Se fier au défaut du navigateur est correct pour les heures destinées à l'utilisateur et faux pour les rapports.
Trois bugs récurrents
Les dates nues se parsent comme minuit UTC. Selon la spécification ECMAScript, les chaînes date-seule sont UTC tandis que les chaînes date-heure sont locales. Cette incohérence est une source fiable de bugs de décalage d'un jour.
Le fuseau de la base ne concorde pas avec le sérialiseur. Le datetime de MySQL n'a pas de fuseau ; écrivez local, lisez en UTC, et tout se décale de l'offset.
L'heure d'été. Stocker des heures locales futures crée des instants qui n'existent pas ou se produisent deux fois. UTC n'a pas de tels trous.
FAQ
Comment choisir entre ISO 8601 et les nombres epoch ?
Utilisez ISO 8601 quand des gens lisent la charge utile, quand vous voulez un ordre de tri lexical, ou quand la valeur doit porter son propre fuseau ; le coût est environ une douzaine d'octets supplémentaires et une bibliothèque de parsing. Utilisez les nombres epoch quand la compacité et un traitement inter-langages trivial comptent plus, mais fixez globalement secondes contre millisecondes et dites quelle unité le champ contient.
Une date nue et un horodatage doivent-ils partager le même champ ?
Non. Une date nue 2026-10-30 est un jour calendaire sans heure ni fuseau, tandis qu'un champ d'horodatage est censé porter un instant exact. Gardez les anniversaires et périodes de facturation dans un champ date séparé, et utilisez un champ ISO 8601 At pour les heures de création ou de mise à jour.