Aller au contenu
OneKitly

Horodatages Unix, secondes intercalaires et le problème de 2038

Publié le 20/05/2025 · 18 min de lecture · Outils pour développeurs

Daniel Okonkwo

Daniel OkonkwoDéveloppeur front-end et rédacteur Tech chez OneKitly

Performance web · Formats de fichiers

Vérifié à partir de 7 sources

Voir le profil
En bref

Le temps Unix est le nombre de secondes depuis le 1970-01-01T00:00:00Z, calculé comme si chaque jour contenait exactement 86 400 secondes. Les secondes intercalaires sont exclues : ce n'est donc pas un décompte de secondes physiques écoulées. 86 400 secondes Unix séparent 2016-12-31T00:00:00Z de 2017-01-01T00:00:00Z alors que 86 401 secondes SI se sont réellement écoulées, car UTC a inséré 23:59:60 entre les deux. C'est le compromis délibéré : le compteur perd l'accord avec la physique et gagne la propriété que diviser par 86 400 donne toujours la bonne date UTC, sans aucune table de secondes intercalaires. Vingt-sept secondes intercalaires ont été insérées depuis 1972, la dernière le 2016-12-31, et une horloge POSIX ne sait pas représenter :60 : elle répète ou fige une seconde à la place. Le problème de 2038 est distinct : un compteur 32 bits signé plafonne à 2 147 483 647, soit 2038-01-19T03:14:07Z. Une seconde plus tard, il bascule à −2 147 483 648, soit 1901-12-13T20:45:52Z. Un time_t 64 bits va jusqu'à 9 223 372 036 854 775 807 secondes, environ 292 milliards d'années, et clôt définitivement l'affaire. Le bogue le plus courant n'est pas 2038 mais la confusion secondes/millisecondes : pour le 2026-08-15, la valeur en secondes lue comme des millisecondes donne le 1970-01-21, et la valeur en millisecondes lue comme des secondes donne l'an 58589.

Le temps Unix compte les secondes depuis 1970 en ignorant délibérément les secondes intercalaires, raison pour laquelle ce n'est pas un décompte de secondes physiques écoulées. Voici ce que ce compromis achète, ce qu'une seconde intercalaire fait au compteur, l'instant exact où un compteur 32 bits signé déborde, et le bogue du facteur 1000 qui t'expédie en 1970 ou en l'an 58589.

Ce que le compteur compte réellement

Le temps Unix, ou temps POSIX, est défini comme le nombre de secondes écoulées depuis le 1970-01-01T00:00:00Z en supposant que chaque jour contient exactement 86 400 secondes. La définition est arithmétique, pas observationnelle : la spécification donne une formule bâtie sur l'année, le quantième, l'heure, la minute et la seconde, et cette formule n'a aucun terme pour les secondes intercalaires, car elles n'existent pas à l'intérieur.

La conséquence mérite d'être dite crûment, car c'est là que naît le plus gros de la confusion : un horodatage Unix n'est pas un décompte de secondes physiques. C'est une représentation compressée d'une date et d'une heure du calendrier UTC. Deux horodatages distants de 86 400 sont séparés d'exactement un jour UTC par construction, toujours, quoi qu'ait mesuré une horloge au césium sur le même intervalle.

Une réserve, car la propriété est souvent surinterprétée. Elle vaut en UTC, pas en heure locale. Mesuré sur des données de fuseaux réelles, de minuit local à minuit local à Europe/Paris, cela faisait 82 800 secondes Unix le 2026-03-29 et 90 000 le 2026-10-25, contre 86 400 un jour ordinaire ; America/New_York donnait 82 800 le 2026-03-08. L'heure d'été ne change pas le compteur — elle change le nombre de tics qui tiennent dans un jour calendaire local, ce qui est exactement pourquoi les horodatages doivent être stockés comme instants UTC et convertis seulement au moment de l'affichage.

Le compromis : pas de secondes intercalaires, pas de table

Exclure les secondes intercalaires ressemble à un défaut jusqu'à ce qu'on regarde l'alternative. Si le compteur suivait les secondes physiques, convertir un horodatage en date exigerait de connaître chaque seconde intercalaire insérée entre l'époque et cet horodatage. Les secondes intercalaires sont annoncées par l'IERS quelques mois à l'avance, sur la base de mesures de la rotation terrestre : cette table ne peut donc pas être calculée, seulement distribuée. Chaque appareil en aurait besoin, tenue à jour, et tout appareil à table périmée calculerait une date calendaire différente à partir du même nombre.

La conception a choisi le déterminisme plutôt que l'exactitude physique, et le bénéfice est que l'arithmétique des horodatages fonctionne partout sans état partagé. Un téléphone qui n'a jamais eu de connexion calcule exactement la même date à partir de 1786752000 qu'une horloge de centre de données. La division par 86 400 est correcte. Ajouter un jour, c'est ajouter 86 400. Trier par horodatage trie par temps. Rien de tout cela ne survivrait à une définition en secondes physiques.

Le prix se paie à un seul endroit : tout calcul de durée physique traversant une seconde intercalaire est faux du nombre de secondes intercalaires franchies. Entre 2016-12-31T00:00:00Z et 2017-01-01T00:00:00Z, l'écart Unix vaut 86 400 alors que 86 401 secondes SI se sont réellement écoulées. Pour presque toute application, cette erreur est sans importance. Pour la télémétrie satellitaire, le séquencement d'échanges financiers à granularité inférieure à la seconde et la métrologie physique, elle ne l'est pas, et ces domaines utilisent le TAI ou le temps GPS — des échelles monotones sans aucune seconde intercalaire.

Ce qu'une seconde intercalaire fait au compteur

L'UTC insère une seconde intercalaire en autorisant une minute de 61 secondes, la seconde en trop étant étiquetée 23:59:60. Vingt-sept l'ont été depuis 1972 — TAI moins UTC valait 10 secondes le 1972-01-01 et vaut 37 secondes depuis le 2017-01-01, et la différence est exactement le compte. Toutes étaient positives ; une seconde intercalaire négative, retirant une seconde, est permise par la norme et n'a jamais servi.

Une horloge POSIX n'a aucune représentation pour la seconde numérotée 60 : quelque chose doit céder. Le comportement classique est la répétition : le compteur émet deux fois la même valeur, une seconde n'est pas monotone, et tout code supposant des horodatages strictement croissants voit un doublon. Certains systèmes figent plutôt le compteur pendant une seconde. Les deux sont visibles des applications, et les deux ont causé de vraies pannes dans des systèmes qui traitaient un horodatage comme clé unique ou comme numéro de séquence strictement croissant.

La réponse pragmatique adoptée par les grands opérateurs est l'étalement : répartir la seconde en trop sur une fenêtre de plusieurs heures, pour qu'aucune horloge ne répète ni ne s'arrête, au prix d'une légère erreur sur toutes les horloges de la fenêtre. Cela compte de moins en moins, car en novembre 2022 la 27e Conférence générale des poids et mesures a décidé de cesser d'insérer des secondes intercalaires d'ici 2035 au plus tard, laissant UTC s'écarter du temps solaire au-delà de la limite actuelle. Le mécanisme n'est pas encore arrêté, mais la direction l'est : la seconde intercalaire prend sa retraite.

2038, calculé exactement

Un entier 32 bits signé va de −2 147 483 648 à 2 147 483 647. Interprété en secondes depuis l'époque, le maximum vaut 2038-01-19T03:14:07Z. Ranger 2 147 483 648 dans une vraie case 32 bits signée puis la relire donne −2 147 483 648, soit 1901-12-13T20:45:52Z. Ranger 2 147 483 649 donne 1901-12-13T20:45:53Z. La défaillance n'est ni un plantage ni une erreur : c'est une date 136 ans dans le passé, livrée en silence et utilisée.

Deux faits voisins méritent d'être connus. Un compteur 32 bits non signé atteint 4 294 967 295, soit 2106-02-07T06:28:15Z — contournement courant dans le micrologiciel embarqué, qui ne fait que reporter le problème tout en rendant irreprésentables les dates antérieures à 1970. Et un time_t 64 bits signé atteint 9 223 372 036 854 775 807 secondes, environ 292 milliards d'années, soit près de vingt et une fois l'âge actuel de l'univers. Ce n'est pas un report : c'est une correction définitive.

La date à craindre n'est pas 2038 mais aujourd'hui, car les premiers systèmes à casser sont ceux qui calculent des instants futurs. Depuis le 2026-08-15, le débordement est à 360 731 647 secondes — 4 175 jours, soit 11,43 ans. Un horizon de dix ans depuis cette date finit le 2036-08-14 et tient encore. Un horizon de quinze ans finit le 2041-08-14 et ne tient pas. Tout ce qui stocke une échéance, un tableau d'amortissement, une durée de conservation ou une validité de certificat de plus d'environ onze ans et demi produit déjà des valeurs qu'un champ 32 bits ne peut contenir.

Ce qui est menacé et ce qui ne l'est pas en 2026

Les systèmes d'exploitation 64 bits grand public vont bien, et depuis des années. Sur la machine utilisée pour cet article, sizeof(time_t) vaut 8 octets et la commande date -u -r 2147483648 affiche Tue Jan 19 03:14:08 UTC 2038 sans le moindre débordement. Il en va de même de tout Linux, macOS et Windows 64 bits actuel. JavaScript n'a jamais été exposé : un Date contient un décompte float64 de millisecondes et la spécification borne sa portée à plus ou moins 8 640 000 000 000 000 millisecondes, ce qui va de −271821-04-20 à +275760-09-13.

L'exposition restante est étroite mais réelle, et elle se situe surtout dans les champs, pas dans les processeurs. Les types de colonne à portée 32 bits documentée sont les plus courants : le TIMESTAMP de MySQL est spécifié pour s'arrêter au 2038-01-19 03:14:07 UTC, alors que son type DATETIME n'est pas concerné. Les formats réseau et de fichier qui spécifient un champ 32 bits ne peuvent être élargis sans changement de version. Le micrologiciel embarqué sur microcontrôleurs 32 bits utilise fréquemment un compteur 32 bits délibérément, pour des raisons de mémoire. Et toute base de code qui range une seconde d'époque dans un type explicitement 32 bits — colonne int32, enregistrement binaire à largeur fixe, struct C compilée pour une cible 32 bits — porte la limite quel que soit le système d'exploitation dessous.

L'audit pratique est court. Cherche les colonnes int32 et INTEGER contenant des secondes d'époque ; vérifie la portée documentée de chaque type de colonne temporelle que tu emploies ; repère les enregistrements binaires à largeur fixe et toute struct compilée pour une cible 32 bits ; et teste avec la valeur 2147483648 plutôt que d'attendre. Si un champ ne peut être élargi, stocker une chaîne ISO 8601 ou un décompte de millisecondes 64 bits sont deux alternatives valables, et toutes deux coûtent plus d'octets qu'elles n'économisent de discussions.

Secondes ou millisecondes : le bogue du facteur 1000

C'est de loin le bogue d'horodatage le plus fréquent, et c'est purement un problème d'unités. Les outils Unix, la plupart des API et la définition POSIX utilisent des secondes. JavaScript, Java et quantité d'API web utilisent des millisecondes. Les deux sont le même nombre à un facteur 1 000 près, et aucun n'est étiqueté dans le format de transport : une inadéquation reste invisible jusqu'à l'affichage d'une date.

Les deux sens produisent un résultat absurde, et c'est la bonne nouvelle. Prends le 2026-08-15T00:00:00Z : en secondes c'est 1786752000, en millisecondes 1786752000000. Donne la valeur en secondes à un code qui attend des millisecondes et tu obtiens 1970-01-21T16:19:12Z — trois semaines après l'époque, car 1,79 milliard de millisecondes ne fait qu'environ vingt jours. Donne la valeur en millisecondes à un code qui attend des secondes et tu obtiens l'an 58589, précisément +058589-12-01T00:00:00Z. Les deux sont si loin du plausible qu'une seule vérification les attrape.

L'heuristique qui marche : un horodatage courant en secondes a dix chiffres, en millisecondes treize. Dix chiffres resteront corrects jusqu'en 2286. Mieux qu'une heuristique : nommer le champ pour que l'unité soit impossible à mal lire — expiresAtSeconds plutôt qu'expiresAt — ou faire passer une chaîne RFC 3339 à la frontière et l'analyser à l'arrivée, ce qui est autodescriptif et coûte quelques dizaines d'octets.

Stocker le temps pour qu'il survive

Stocke l'instant, pas le rendu. Un instant est un point de la ligne du temps, entièrement spécifié par un horodatage UTC de largeur suffisante — une seconde d'époque 64 bits, un décompte de millisecondes 64 bits, ou une chaîne RFC 3339 terminée par Z. Un rendu est ce que verrait une personne à un endroit donné, et il dépend de règles de fuseau que les gouvernements changent avec quelques semaines de préavis. Stocker le rendu, c'est stocker une réponse qui peut devenir fausse rétroactivement.

Une exception mérite d'être nommée, car stocker en UTC est souvent vendu comme un conseil universel. Un rendez-vous futur dans un lieu nommé n'est pas un instant : c'est une heure d'horloge murale dans une juridiction, et si cette juridiction déplace ses horloges, l'instant correct change. Une réunion à 09:00 à Berlin en novembre prochain doit être stockée avec la date locale, l'heure locale et l'identifiant de fuseau IANA, puis résolue en instant seulement au besoin. Stocke en UTC ce qui s'est produit, et en heure locale plus nom de fuseau ce qui est planifié.

Au-delà, trois habitudes retirent l'essentiel de la douleur restante. Donne à chaque champ d'horodatage une unité dans son nom pour qu'un lecteur n'ait jamais à deviner entre secondes et millisecondes. Utilise un type 64 bits partout, y compris dans la colonne de base de données, pour que 2038 soit une curiosité historique plutôt qu'une échéance. Et ne traite jamais un horodatage comme identifiant unique ou numéro de séquence monotone : gestion des secondes intercalaires, corrections d'horloge et migrations de machines virtuelles peuvent toutes faire apparaître deux fois la même valeur ou faire brièvement reculer le temps.

Représentations du temps et leurs limites, chaque instant frontière calculé et non cité
ReprésentationUnitéLa plus ancienneLa plus récenteOù on la rencontre encore
time_t 32 bits signéSecondes1901-12-13T20:45:52Z2038-01-19T03:14:07ZCibles embarquées 32 bits, colonnes int32, enregistrements binaires à largeur fixe
Compteur 32 bits non signéSecondes1970-01-01T00:00:00Z2106-02-07T06:28:15ZContournements de micrologiciel ; aucune date avant 1970 possible
time_t 64 bits signéSecondesEnviron 292 milliards d'années avant 1970An 292 277 026 596 (2^63 − 1 secondes)Toute version 64 bits actuelle de Linux, macOS et Windows
Date JavaScriptMillisecondes (float64)−271821-04-20+275760-09-13 (±8 640 000 000 000 000 ms)Navigateurs et Node ; n'ont jamais eu de problème 2038
TIMESTAMP de MySQLSecondes1970-01-01 00:00:01 UTC2038-01-19 03:14:07 UTCTrès largement déployé ; DATETIME est l'alternative non concernée
Chaîne RFC 3339Texte, autodescriptifAucune borne inférieure dans le formatAucune borne supérieure dans le formatAPI et journaux ; coûte des octets, supprime l'ambiguïté secondes/millisecondes
Convertisseur d'horodatage UnixConvertis un horodatage Unix (secondes ou millisecondes) en date lisible et inversement, avec l'horodatage actuel en direct.Essayer l'outil

Questions fréquentes

Un horodatage Unix est-il en UTC ou dans mon fuseau local ?
Ni l'un ni l'autre, à strictement parler, et c'est la bonne façon d'y penser. Un horodatage Unix désigne un point de la ligne du temps. Il ne porte aucun fuseau, car il n'en a pas besoin — le nombre 1786752000 désigne le même moment partout sur Terre. Ce qui est vrai, c'est que le convertir en date lisible exige un fuseau, et que l'époque est ancrée au 1970-01-01T00:00:00 UTC : convertir sans fuseau précisé donne donc UTC. D'où le fait qu'un horodatage soit la bonne chose à stocker et à transmettre et la mauvaise chose à afficher : la couche de stockage a besoin d'un instant non ambigu, la couche de présentation d'un fuseau, d'une locale et d'un calendrier. Le bogue courant est de convertir en heure locale au milieu d'une chaîne de traitement puis de stocker le résultat, ce qui fige le fuseau d'une machine et décale silencieusement chaque valeur de son décalage. Convertis une fois, au dernier moment possible, dans l'interface.
Que se passe-t-il exactement à 03:14:07 le 19 janvier 2038 ?
Dans tout système gardant l'horodatage en entier 32 bits signé, le compteur atteint 2 147 483 647 et l'incrément suivant déborde. Simulé dans une vraie case 32 bits signée, ranger 2 147 483 648 se relit en −2 147 483 648, qui se convertit en 1901-12-13T20:45:52Z. Ce n'est ni une exception ni un plantage : la valeur devient simplement une date 136 ans dans le passé et est utilisée. Ce que cela provoque dépend du code au-dessus. Les tris s'inversent. Les calculs d'âge et de durée deviennent énormément négatifs. Certificats et sessions semblent expirés depuis longtemps, ou ne jamais expirer. Les ordonnanceurs de type cron se déclenchent en continu ou s'arrêtent. Les enregistrements atterrissent dans la mauvaise partition. Le danger vient précisément de ce que rien ne lève d'erreur : chaque couche reçoit un nombre bien formé et se comporte correctement pour ce nombre. C'est aussi pourquoi le test est facile — mets 2147483648 dans un champ aujourd'hui et relis-le, plutôt que d'attendre la date.
Mes serveurs sont en 64 bits. Suis-je à l'abri du problème de 2038 ?
Ton système d'exploitation l'est, ton application peut-être pas. Sur une machine 64 bits moderne, sizeof(time_t) vaut 8 octets — vérifié ici — et le shell affiche Tue Jan 19 03:14:08 UTC 2038 pour la valeur 2147483648, sans débordement. Mais le noyau est rarement le lieu de la limite. L'exposition est dans les champs que tu as choisis : une colonne INTEGER ou int32 contenant des secondes d'époque, un protocole ou format de fichier à champ d'horodatage 32 bits fixe, une struct compilée pour une cible embarquée 32 bits, un enregistrement binaire à largeur fixe écrit il y a des années. Le type de colonne TIMESTAMP de MySQL en est un exemple documenté, spécifié pour s'arrêter au 2038-01-19 03:14:07 UTC quel que soit le nombre de bits du serveur, tandis que son type DATETIME n'est pas concerné. Deuxième exposition, moins évidente : tout appareil tiers du parc — imprimantes, caméras, automates, capteurs — dont le micrologiciel est 32 bits par conception et ne sera peut-être jamais mis à jour. L'audit vaut la peine maintenant plutôt qu'en 2037, car les défaillances commencent par les valeurs futures et 2026 est déjà dans la fenêtre de onze ans et demi.
Comment savoir si un nombre est en secondes ou en millisecondes ?
Compte les chiffres. Un horodatage actuel en secondes a dix chiffres et en aura dix jusqu'en 2286 ; le même instant en millisecondes en a treize. Pour 2026-08-15T00:00:00Z, les deux valeurs sont 1786752000 et 1786752000000. Dans le doute, convertis et regarde le résultat, car les deux erreurs produisent quelque chose de manifestement absurde : lire la valeur en secondes comme des millisecondes donne 1970-01-21T16:19:12Z, trois semaines après l'époque, et lire la valeur en millisecondes comme des secondes donne l'an 58589. Une date de janvier 1970 ou d'un futur lointain, c'est presque toujours ce bogue plutôt que de mauvaises données. Le correctif durable n'est pas la détection mais le nommage. Appelle le champ expiresAtSeconds ou createdAtMillis pour que l'unité voyage avec la valeur, ou envoie une chaîne RFC 3339 comme 2026-08-15T00:00:00Z entre services — autodescriptive, elle se trie correctement comme texte, survit à un copier-coller dans un journal et coûte une vingtaine d'octets.
Les secondes intercalaires faussent-elles mes calculs de durée ?
Techniquement oui, en pratique presque jamais assez pour compter. Soustraire deux horodatages Unix donne la différence du compteur, qui omet toute seconde intercalaire intermédiaire. Autour de la seconde intercalaire de 2016, le compteur dit que 86 400 secondes séparent 2016-12-31T00:00:00Z de 2017-01-01T00:00:00Z, alors que 86 401 secondes SI se sont écoulées. Comme seulement vingt-sept secondes intercalaires ont été insérées, l'erreur maximale sur une durée couvrant toute la période depuis 1972 est de vingt-sept secondes — sans importance pour la facturation, la durée de session, l'expiration de cache, la latence à la milliseconde ou toute mesure d'entreprise. Cela devient pertinent en télémétrie satellitaire, en physique de haute précision et pour le séquencement financier sous la seconde, et ces domaines utilisent le TAI ou le temps GPS, sans aucune seconde intercalaire. Il existe pourtant un danger pratique bien plus grand : mesurer une durée avec l'horloge murale. Corrections NTP, migrations de machines virtuelles et changements manuels peuvent déplacer une horloge murale de bien plus d'une seconde, en avant ou en arrière. Pour mesurer des durées, utilise une horloge monotone — celle que ton langage expose sous un nom comme performance.now ou une horloge stable — et réserve les horodatages muraux à l'enregistrement du moment où quelque chose s'est produit.
Faut-il stocker les horodatages en UTC ou avec un fuseau ?
Cela dépend si tu enregistres quelque chose qui a eu lieu ou si tu planifies quelque chose à venir. Pour tout ce qui est passé — ligne de journal, commande, paiement, enregistrement d'audit — stocke l'instant en UTC et convertis seulement à l'affichage. L'instant est un fait et ne change jamais ; la façon dont il se rend dans l'heure locale d'un utilisateur relève de la présentation et peut être recalculée à tout moment. Pour tout ce qui est planifié dans le futur, l'UTC est le mauvais choix, et c'est le cas le plus souvent raté. Une réunion à 09:00 à Berlin en novembre prochain n'est pas un instant : c'est une heure d'horloge murale dans une juridiction. Si l'Allemagne change ses règles d'horloge d'ici là, l'instant correct se déplace, et une valeur UTC stockée aujourd'hui deviendrait un rendez-vous à la mauvaise heure locale. Stocke la date locale, l'heure locale et l'identifiant de fuseau IANA comme Europe/Berlin, et résous en instant quand il en faut un. Les gouvernements changent bel et bien les règles de fuseau, souvent avec quelques semaines de préavis, et la base tz est mise à jour plusieurs fois par an pour les suivre.

Articles qui pourraient t'intéresser

Tous les guides
ExplicationQu'est-ce qu'un timestamp Unix ?Un timestamp Unix compte les secondes depuis le 1er janvier 1970 UTC. Pourquoi les ordinateurs l'utilisent, comment le convertir, et le bug de 2038.ExplicationQu'est-ce qu'une année bissextile ? La règle /4, /100, /400Pourquoi certaines années ont un 29 février, comment se combinent les règles de divisibilité par 4, 100 et 400, et comment vérifier une année rapidement.ExplicationAnnées bissextiles : la règle, l'exception, et l'exception à l'exceptionDivisible par quatre, sauf les siècles, sauf les siècles divisibles par quatre cents. Cette règle en trois lignes existe parce que l'année tropique ne fait pas 365,25 jours, et son arithmétique explique dix jours supprimés en 1582 et un calendrier qui se répète exactement tous les 400 ans.TutorielQuel jour de la semaine suis-je né ? Trouve-le en quelques secondesTrouve le jour de la semaine de n'importe quelle date de naissance. Découvre la méthode rapide par calculateur et une brève introduction à la règle du Doomsday pour le calculer de tête.ExplicationSecondes, minutes et heures : comment les unités de temps s'articulentLes relations derrière 60 secondes, 60 minutes, 24 heures et 86 400 secondes par jour, avec des conversions rapides réutilisables.ExplicationLire et écrire l'heure militaire américaine0800 n'est ni « huit heures » ni « 08:00 » : c'est « zero eight hundred », avec une lettre de fuseau à la fin. Les quatre chiffres, la prononciation, minuit à 0000, la querelle du 2400, et pourquoi la notation sur 12 heures ne peut pas trancher midi.

Outils similaires

Sources

Tu as repéré une erreur dans cet article ?