Horodatages, fuseaux horaires et l'heure qui arrive deux fois
Un horodatage répond à l'une de deux questions, et la plupart des bogues de dates viennent de leur confusion. Il désigne soit un instant, un point de la ligne du temps sur lequel tous les observateurs s'accordent, soit une lecture d'horloge murale, qui ne veut rien dire tant qu'on ne sait pas de quel mur il s'agit. Un paiement a eu lieu à un instant ; un rendez-vous chez le dentiste est une lecture d'horloge murale. Stockez l'un comme l'autre et cela casse plus tard, d'exactement une heure, un dimanche matin.
Le temps Unix compte, il ne donne pas l'heure
Le temps Unix est le nombre de secondes écoulées depuis le 1970-01-01 00:00:00 UTC. Ce UTC indique d'où part le comptage ; la valeur elle-même ne porte aucun fuseau horaire. Le même instant donne le même nombre à Amsterdam, à Auckland et à Lima.
Le problème est l'unité : comptez donc les chiffres.
| Chiffres | Exemple | Unité |
|---|---|---|
| 10 | 1756080000 | secondes |
| 13 | 1756080000000 | millisecondes |
Toute date actuelle compte 10 chiffres en secondes et 13 en millisecondes, et ce sera le cas jusqu'en 2286. Les deux erreurs de lecture sont bruyantes : des millisecondes lues comme des secondes atterrissent en l'an 57626, des secondes lues comme des millisecondes en janvier 1970. Un convertisseur d'horodatage qui affiche la date interprétée rend la chose évidente.
L'autre limite est le contenant. Un compte de secondes signé sur 32 bits s'arrête à 2147483647, c'est-à-dire le 19 janvier 2038 à 03:14:07 UTC, puis passe en négatif et se lit décembre 1901. Tout ce qui stocke une échéance à 30 ans calcule déjà au-delà. Le non signé sur 32 bits atteint 2106 mais perd toute date antérieure à 1970 ; le 64 bits n'a pas de plafond en pratique.
ISO 8601 et RFC 3339
La forme à mémoriser est 2026-08-25T14:30:00Z : une date, un T, une heure, puis un désignateur de zone. La RFC 3339 est un profil plus strict de l'ISO 8601 destiné aux protocoles Internet, et elle exige ce désignateur. L'ISO 8601 elle-même autorise en plus les dates par semaine, une forme sans séparateurs comme 20260825T143000Z, et des horodatages sans décalage, si bien qu'une API qui promet de l'« ISO 8601 » désigne en général le sous-ensemble RFC 3339.
Z signifie un décalage nul. +02:00 signifie que l'horloge locale est deux heures en avance sur UTC : on retrouve donc l'UTC en soustrayant, et inverser ce sens est une erreur de deux heures fréquente.
| Chaîne | Ce qu'elle désigne |
|---|---|
2026-08-25T14:30:00Z | Un instant, sans ambiguïté |
2026-08-25T16:30:00+02:00 | Le même instant, sur une horloge en avance de deux heures |
2026-08-25T14:30:00 | Une lecture d'horloge murale, aucun instant |
2026-08-25 | Une date, longue de 23, 24 ou 25 heures |
Une chaîne dépourvue de Z et de décalage est ambiguë, et les analyseurs divergent : certains supposent l'UTC, d'autres le fuseau du système. C'est pourquoi une valeur ne contenant qu'une date, comme un anniversaire, s'affiche souvent un jour trop tôt à l'ouest de Greenwich.
Un décalage n'est pas un fuseau horaire
+02:00 est un fait portant sur un instant en un lieu. Europe/Amsterdam est un ensemble de règles : +01:00 en hiver, +02:00 en été, et une autre allure au cours des décennies précédentes. Stocker le décalage conserve la réponse pour l'instant où elle a été mesurée et abandonne la capacité d'en calculer une autre.
Les décalages ne sont pas uniques non plus. Un après-midi d'été, +02:00 couvre Amsterdam, Lagos et Johannesburg : à partir du seul décalage, il n'existe donc aucun chemin de retour vers les règles ni aucun moyen de savoir ce qu'affichera cette horloge en décembre prochain. Les abréviations sont pires encore, puisque CST nomme trois fuseaux différents. Stockez l'identifiant IANA sous la forme Area/Location.
L'heure qui n'existe pas et l'heure qui arrive deux fois
Dans toute l'Union européenne, les horloges avancent le dernier dimanche de mars et reculent le dernier dimanche d'octobre. À Amsterdam, 02:00 devient 03:00 au printemps : toute heure locale de 02:00:00 à 02:59:59 n'a donc pas lieu ce jour-là. À l'automne, 03:00 devient 02:00 : 02:30 arrive donc deux fois, une première à +02:00 puis une heure plus tard à +01:00.
- Alarmes récurrentes. Une tâche réglée sur 02:30 en heure locale n'a aucun moment valide le dimanche de printemps. Les ordonnanceurs diffèrent : la sauter, la décaler à 03:00, ou la décaler à 01:30. Le dimanche d'automne, la même tâche peut se déclencher une ou deux fois, alors vérifiez les prochaines exécutions avec un lecteur d'expression cron.
- Fichiers journaux. Une heure d'horodatages locaux sans décalage ne peut pas être triée, et deux événements espacés d'une heure portent un texte identique. Journalisez en UTC, ou mettez le décalage sur chaque ligne.
- Durées. De 20:00 le 24 octobre 2026 à 20:00 le lendemain, en heure locale d'Amsterdam, il s'écoule 25 heures. « Ajouter un jour » et « ajouter 24 heures » sont deux opérations différentes, et une différence entre dates exprimée en jours calendaires répond à une autre question qu'un convertisseur de durée travaillant en heures.
Les règles changent, et le stockage doit y survivre
La base de données des fuseaux horaires de l'IANA, tzdata, contient ces règles et paraît plusieurs fois par an, car les règles sont des textes de loi : les gouvernements suppriment l'heure d'été, l'adoptent, ou déplacent les dates de bascule, parfois avec quelques semaines de préavis. Une image de conteneur figée, un navigateur et l'environnement d'exécution d'un langage embarquent chacun leur propre copie et peuvent se contredire sur une même machine. Convertir une heure locale future en instant est une prédiction, qui ne sera tranchée qu'à son arrivée.
| Type de valeur | À stocker | Raison |
|---|---|---|
| Quelque chose qui a eu lieu | Un instant en UTC | Le passé ne peut pas être relégiféré |
| Rendez-vous futur sur une horloge murale | Date et heure locales plus identifiant de fuseau IANA | L'intention est la lecture de l'horloge |
| Instant futur, fixe pour tous les fuseaux | UTC | L'intention est l'instant |
| Date sans heure | Une date simple, sans fuseau ni minuit | Y attacher un fuseau la fait dériver |
C'est pourquoi « il suffit de stocker en UTC » est faux pour une réunion l'an prochain. Convertissez aujourd'hui 09:00 le 30 mars 2027 en Europe/Amsterdam vers UTC et la réponse est 07:00Z. Si la date de bascule bouge d'ici là, la réunion reste à 09:00 en heure locale, mais 07:00Z s'affiche désormais comme 08:00 : la valeur stockée a figé une prédiction et jeté l'intention. Gardez l'heure locale et le fuseau comme source de vérité ; toute colonne UTC à côté n'est qu'un cache.
Montrer des dates à des humains
03/04 ne peut pas être levé de son ambiguïté à partir de la chaîne seule. C'est le 3 avril pour la plus grande partie du monde et le 4 mars aux États-Unis ; 03/04/05 ajoute une troisième inconnue. Écrivez le mois en toutes lettres pour une sortie destinée à un humain (4 Apr 2026) et employez l'ISO pour les échanges entre machines. Une balise d'horodatage Discord contourne le problème dans une conversation en s'affichant dans le fuseau et la locale de celui qui lit : stockez une valeur sans ambiguïté, formatez au bord.
Les numéros de semaine ont leur propre règle. Les semaines ISO commencent le lundi, et la semaine 1 est celle qui contient le premier jeudi de janvier, ce qui revient à celle qui contient le 4 janvier. Le 1er janvier peut donc tomber en semaine 52 ou 53 de l'année ISO précédente, et le 29 décembre en semaine 1 de la suivante. Une convention nord-américaine répandue numérote plutôt à partir de la semaine commençant un dimanche qui contient le 1er janvier, si bien que la même date reçoit deux numéros, et la semaine ISO d'un calculateur de jour de semaine mérite d'être confrontée à un tableur.
Durées, mois et années bissextiles
« Un mois plus tard » n'est pas un nombre fixe de jours ; c'est 28, 29, 30 ou 31. Ajouter un mois au 31 janvier n'a pas de réponse correcte, et les bibliothèques rabattent en général sur la fin de février, ce qui rend l'opération non réversible : ajoutez un mois, retirez-le, et le 31 janvier est devenu le 28 janvier. L'arrondi est le même piège en miniature : tronquer une heure au quart d'heure le plus proche et l'y arrondir diffèrent d'un intervalle entier au maximum.
La règle des années bissextiles en entier : une année est bissextile si elle est divisible par 4, sauf si elle est divisible par 100, à moins qu'elle ne soit aussi divisible par 400. Ainsi 1900 ne l'était pas, 2000 l'était, et 2100 ne le sera pas, ce qui donne une année moyenne de 365,2425 jours. Un code qui ne teste que la divisibilité par 4 est correct pour toutes les années de mémoire d'homme, et c'est pourquoi il survivra jusqu'en 2100. L'anniversaire d'un 29 février est la nuisance associée : l'année suivante propose le 28 février et le 1er mars, aucune règle ne tranche entre les deux, alors choisissez délibérément.