Aller au contenu
OneKitly

L'arithmétique des dates est plus difficile qu'elle n'en a l'air

Publié le 06/06/2025 · 16 min de lecture · Calculateurs du quotidien

Lena Hoffmann

Lena HoffmannRédactrice Sciences & Éducation chez OneKitly

Mathématiques · Physique

Vérifié à partir de 5 sources

Voir le profil
En bref

Demande ce que vaut le 31 janvier plus un mois : aucune réponse ne s'impose mathématiquement. La plupart des systèmes tronquent à la fin du mois le plus court et renvoient le 28 février 2026 — ou le 29 février dans une année bissextile comme 2024. L'arithmétique naïve en jours renvoie tout autre chose : ajouter 31 jours donne le 3 mars, ajouter 30 jours donne le 2 mars. Les trois se défendent, et c'est précisément le problème. Cette troncature a une conséquence qu'on remarque rarement : l'addition de mois n'est ni inversible ni associative. Le 31 mars moins un mois donne le 28 février, et rajouter un mois donne le 28 mars, pas le 31 mars. Le 31 janvier plus un mois plus un mois donne le 28 mars, mais le 31 janvier plus deux mois donne le 31 mars. L'arithmétique horaire casse dans une autre direction. À Europe/Paris, le 29 mars 2026 dure 23 heures et le 25 octobre 25 heures : ajouter 86 400 secondes à un rendez-vous de 9 h le 28 mars atterrit à 10 h le lendemain. Et l'âge est une comparaison de calendrier, pas une division : sur toutes les dates de naissance de 1930 à 2020, jours ÷ 365 se trompe d'âge dans 3,49 % des cas. La règle qui résout tout cela : fais l'arithmétique de calendrier dans les champs de calendrier, l'arithmétique d'instants en UTC, et ne mélange jamais les deux.

« Un mois plus tard » n'a pas de réponse unique, et chaque bibliothèque de dates a dû en choisir une. L'addition de mois n'est ni associative ni inversible, un jour ne fait pas toujours 24 heures, et l'âge n'est pas le nombre de jours divisé par 365,25.

Aucune arithmétique n'impose de réponse

Ajouter un à un nombre est sans ambiguïté. Ajouter un mois à une date ne l'est pas, car les mois ne sont pas une unité — ce sont des étiquettes de longueur inégale, de 28 à 31 jours, et la longueur de celui où l'on atterrit dépend de celui d'où l'on part. Le 31 janvier plus un mois doit atterrir quelque part en février, et février n'a pas de 31. Quelque chose doit céder. Le choix quasi universel est la troncature : garder le mois, garder l'année, et ramener le jour au dernier valide. Cela donne le 28 février 2026, et le 29 février 2024. java.time en Java, l'API Temporal d'ECMAScript, l'addition d'intervalles de PostgreSQL et dateutil en Python se comportent tous ainsi, et ils le font parce que l'alternative est pire.

L'alternative est de traiter un mois comme un nombre fixe de jours. Choisis 30 et le 31 janvier plus un mois devient le 2 mars 2026 ; choisis 31 et il devient le 3 mars. Les deux sautent février entièrement, ce que l'utilisateur qui demande « un mois plus tard » ne veut précisément pas. Le tableau ci-dessus applique les trois définitions à cinq dates de départ, et elles ne concordent que lorsque le quantième est assez petit pour exister partout. Voilà l'enseignement pratique : toutes les méthodes sont identiques du 1er au 28, et toutes divergent quelque part les 29, 30 et 31. Environ une date sur dix est en zone dangereuse, et c'est pourquoi le bug survit si facilement aux tests.

La troncature te coûte l'associativité et l'inversibilité

Exécute ceci et regarde disparaître une propriété que tu croyais acquise. Le 31 mars 2026 moins un mois se tronque au 28 février. Rajoute un mois et tu obtiens le 28 mars — trois jours avant ton point de départ. L'addition de mois n'est pas inversible : soustraire puis ajouter n'est pas l'identité. Le même défaut apparaît comme un échec de l'associativité. Le 31 janvier 2026 plus un mois plus un mois donne le 28 mars, parce que la valeur intermédiaire a été tronquée au 28 février et que la troncature est définitive. Le 31 janvier plus deux mois, calculé en une étape, donne le 31 mars. Deux expressions censées valoir la même chose diffèrent de trois jours.

Ce n'est un bug d'aucune bibliothèque en particulier — c'est une conséquence du calendrier, et toute bibliothèque qui tronque en hérite. La règle pratique qui en découle mérite un pense-bête : ne construis jamais une série mensuelle en ajoutant un mois au résultat précédent. Ajoute toujours n mois à la date d'ancrage d'origine. Un abonnement qui démarre le 31 janvier et se renouvelle par itération dérivera vers le 28 et y restera à jamais ; le même abonnement ancré au 31 janvier et calculé comme début + n mois tombe le 28 février, le 31 mars, le 30 avril, le 31 mai — ce que le client attend et ce que le processeur de paiement facturera. Le bug est invisible onze mois par an, puis débarque d'un coup.

Un jour ne fait pas 24 heures — Europe/Paris, mars et octobre 2026

Prends les règles réelles de la base tz plutôt qu'une supposition. En 2026, Europe/Paris passe d'UTC+1 à UTC+2 le 29 mars et revient le 25 octobre. Mesure la distance entre minuit local le 29 mars et minuit local le 30 mars : c'est 2026-03-28T23:00Z à 2026-03-29T22:00Z, soit 23 heures. Fais de même autour du 25 octobre et tu obtiens 2026-10-24T22:00Z à 2026-10-25T23:00Z, soit 25 heures. Le jour calendaire et le jour de 86 400 secondes sont deux objets distincts, et deux fois par an ils se désolidarisent visiblement. America/New_York fait la même chose à d'autres dates — 23 heures le 8 mars 2026 et 25 heures le 1er novembre.

La conséquence retombe sur de vrais rendez-vous. Un créneau de 9 h le 28 mars 2026 à Paris est l'instant 2026-03-28T08:00Z. Ajoute exactement 24 heures écoulées et tu obtiens 2026-03-29T08:00Z, qui se lit 10 h à Paris — la réunion a glissé d'une heure. Fais-le en octobre et elle recule d'une heure : 9 h le 24 octobre plus 24 heures donne 8 h le 25 octobre. Ni l'un ni l'autre n'est le sens de « même heure demain ». « Même heure demain » est une opération de calendrier : incrémenter le champ date, garder le champ horloge murale, puis re-résoudre l'instant contre le fuseau. Ajouter une durée est une opération de physique. Elles coïncident 363 jours par an, ce qui suffit exactement à faire passer la panne pour un hasard.

Le débordement silencieux : le 30 février ne lève aucune erreur

En JavaScript, new Date(2026, 1, 30) ne lève pas d'exception. Il renvoie le 2 mars 2026. Le constructeur accepte n'importe quel entier et normalise en reportant l'excédent sur le mois suivant : un champ jour à 30 dans un février de 28 jours devient discrètement le 2 du mois suivant. La même normalisation transforme l'indice de mois 12 en janvier de l'année suivante, et un jour à 0 en dernier jour du mois précédent — c'est l'astuce derrière l'idiome courant pour « jours dans ce mois », new Date(y, m, 0).getDate(). C'est un comportement utile quand on le veut et une corruption silencieuse de données quand on ne le veut pas, et rien dans la valeur de retour ne te dit dans quel cas tu es.

C'est pourquoi valider une date, c'est plus que vérifier que l'analyseur n'a pas protesté. Un formulaire qui accepte 30/02/2026 et enregistre 2026-03-02 a perdu l'erreur de l'utilisateur au lieu de la signaler, et l'enregistrement dit désormais quelque chose que l'utilisateur n'a jamais saisi. Le contrôle défensif tient en une ligne : construis la date, puis vérifie que l'année, le mois et le jour renvoyés sont bien les trois que tu as fournis. Sinon, la saisie n'était pas une date réelle. L'autre moitié de la défense consiste à connaître la longueur de chaque mois avant même de construire la saisie — 31, 28 ou 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 — ce à quoi le calculateur de jours dans le mois répond pour l'année de ton choix.

L'âge est une comparaison, pas une division

Le raccourci tentant consiste à compter les jours écoulés et à diviser par 365,25, au motif que l'année moyenne dure 365,25 jours. Teste-le. Une personne née le 23 août 2000, interrogée le 22 août 2026, a vécu 9 495 jours. Divise par 365 et tu obtiens 26,0137, dont la partie entière est 26 — mais la personne a 25 ans et son anniversaire est demain. Divise par 365,25 et tu obtiens 25,9959, dont la partie entière est 25, et là c'est juste. Alors parcours tout l'espace plutôt qu'un exemple : chaque date de naissance du 1er janvier 1930 au 1er janvier 2020, évaluée au 22 août 2026, cela fait 32 873 dates. Jours ÷ 365 donne un âge faux sur 1 148 d'entre elles, soit 3,49 %. Jours ÷ 365,25 fait bien mieux mais se trompe encore sur 44, soit 0,13 %.

Les échecs résiduels sont les plus cruels : ils tombent le jour même de l'anniversaire. Une personne née le 22 août 1932 a vécu 34 333 jours au 22 août 2026, et 34 333 ÷ 365,25 = 93,9986 : la division annonce 93 le matin de ses 94 ans. Aucun affinage du diviseur ne corrige cela, parce qu'aucun diviseur unique ne le peut, et c'est tout le propos — le calendrier n'est pas une échelle uniforme. L'algorithme correct ne comporte aucune division. Soustrais l'année de naissance de l'année courante, puis retranche encore un si le mois et le jour courants n'ont pas encore atteint le mois et le jour de naissance. Trois comparaisons d'entiers, exactes partout, et jamais besoin de savoir combien dure une année.

La règle : champs de calendrier pour le calendrier, UTC pour les instants

Presque tous les bugs de dates relèvent de deux erreurs. Soit une question de calendrier a reçu une réponse en temps écoulé — « un mois » devenu 30 jours, « demain » devenu 86 400 secondes, « l'âge » devenu une division — soit une question d'instant a reçu une réponse en champs locaux, si bien qu'un horodatage stocké a bougé quand le décalage a changé. Le remède est de décider, avant d'écrire une ligne, quel type de grandeur tu manipules. Dates de renouvellement, anniversaires, échéances, périodes de facturation et horaires d'ouverture sont des grandeurs de calendrier : garde-les en année, mois, jour et heure murale avec un fuseau nommé, et fais l'arithmétique sur ces champs. Délais d'expiration, ordre des journaux, péremption de cache, limitation de débit et durées sont des grandeurs d'instant : garde-les en horodatages UTC et ajoute des secondes.

Là où les deux doivent se rencontrer — un rappel à 9 h locales, envoyé par un serveur qui ne comprend que les instants — convertis à la frontière, et seulement à la frontière. Fais d'abord l'étape de calendrier dans le fuseau nommé de l'utilisateur, résous une fois le résultat en instant UTC, puis confie cet instant au planificateur. Stocker le décalage au lieu du nom du fuseau casse dès que les règles politiques changent, ce qui arrive plusieurs fois par an ; la base tz publie des versions précisément parce que les gouvernements n'arrêtent pas de déplacer leurs transitions. Et ne stocke jamais un rendez-vous local futur sous forme d'instant UTC seul : si les règles du fuseau sont modifiées avant l'échéance, l'instant enregistré ne correspondra plus à 9 h du matin pour personne.

La même date de départ, trois définitions défendables de « un mois plus tard » — et les réponses divergent
Date de départ+ 1 mois, tronqué+ 30 jours+ 31 jours
31 janvier 202628 février 20262 mars 20263 mars 2026
31 janvier 2024 (année bissextile)29 février 20241er mars 20242 mars 2024
31 mars 202630 avril 202630 avril 20261er mai 2026
31 août 202630 septembre 202630 septembre 20261er octobre 2026
30 novembre 202630 décembre 202630 décembre 202631 décembre 2026
Nombre de jours dans un moisLe nombre de jours de n'importe quel mois de n'importe quelle année de 1 à 9999, la règle bissextile déroulée ligne à ligne pour février, le jour de la semaine du 1er et du dernier, le nombre de chaque jour de la semaine, et le calendrier du mois.Essayer l'outil

Questions fréquentes

Que vaut le 31 janvier plus un mois ?
Cela dépend de la définition utilisée par ton système, et aucune réponse ne s'impose mathématiquement. La troncature — comportement de java.time, de l'API Temporal, des intervalles PostgreSQL et de dateutil en Python — garde le mois et l'année et ramène le jour au dernier valide, donnant le 28 février 2026 ou le 29 février 2024. L'arithmétique en jours fixes donne autre chose : plus 30 jours donne le 2 mars 2026 et plus 31 jours le 3 mars. La troncature est presque toujours le bon choix pour des dates destinées aux humains, car l'utilisateur qui demande « un mois plus tard » veut la date correspondante du mois suivant, pas un nombre fixe de jours. Si tu factures, contractes ou planifies, indique la règle retenue dans les conditions, car les clients remarquent qu'un abonnement du 31 janvier se renouvelle le 28 février.
Pourquoi soustraire un mois puis le rajouter ne redonne-t-il pas la date de départ ?
Parce que la troncature détruit de l'information et que rien ne peut la restaurer. Le 31 mars 2026 moins un mois doit atterrir en février, février n'a pas de 31, donc le résultat est tronqué au 28 février. Ce résultat ne se souvient plus qu'il venait d'un 31. Ajouter un mois au 28 février donne donc le 28 mars, et tu es trois jours avant ton départ. Le même mécanisme te coûte l'associativité : le 31 janvier plus un mois plus un mois donne le 28 mars, tandis que le 31 janvier plus deux mois en une seule étape donne le 31 mars. La conséquence pratique est une règle à appliquer partout : construis les échéanciers récurrents en ajoutant n mois à la date d'ancrage d'origine, jamais en itérant mois par mois depuis le résultat précédent. L'itération laisse une seule troncature se propager à jamais.
Un jour dure-t-il toujours 24 heures ?
Non, pas en tant que jour calendaire local. À Europe/Paris en 2026, le 29 mars dure 23 heures et le 25 octobre 25 heures, car le fuseau passe d'UTC+1 à UTC+2 puis revient. Mesurés en instants, minuit local le 29 mars vaut 2026-03-28T23:00Z et minuit local le 30 mars vaut 2026-03-29T22:00Z — 23 heures d'écart. America/New_York fait de même les 8 mars et 1er novembre 2026. Certaines transitions ne sont même pas des heures entières ; l'île Lord Howe décale de 30 minutes. Les fuseaux sans heure d'été ont des jours de 24 heures toute l'année, mais tu ne peux pas supposer que tes utilisateurs y sont. La bonne habitude est de traiter « un jour » au sens calendaire comme un incrément du champ date, résolu contre un fuseau nommé, et de réserver les 86 400 secondes au véritable travail de temps écoulé, fait en UTC.
Comment faut-il calculer l'âge de quelqu'un ?
Avec des comparaisons, jamais avec une division. Soustrais l'année de naissance de l'année courante, puis retranche un si le mois courant précède le mois de naissance, ou si les mois coïncident et que le jour courant précède le jour de naissance. C'est exact pour toute date. Les divisions échouent de façon mesurable : sur toutes les dates de naissance de 1930 à 2020 évaluées au 22 août 2026 — 32 873 dates — jours écoulés ÷ 365 se trompe dans 3,49 % des cas et ÷ 365,25 dans 0,13 %. Pire, les échecs résiduels se concentrent le jour même de l'anniversaire, le seul que les gens vérifient. Une personne née le 22 août 1932 a vécu 34 333 jours au 22 août 2026, et 34 333 ÷ 365,25 = 93,9986 : la division annonce 93 le matin de ses 94 ans. Les naissances du 29 février exigent une décision de politique distincte, car les juridictions divergent sur l'anniversaire légal en année commune : le 28 février ou le 1er mars.
Pourquoi mon formulaire accepte-t-il le 30 février sans broncher ?
Parce que la plupart des constructeurs de dates normalisent au lieu de valider. En JavaScript, new Date(2026, 1, 30) renvoie le 2 mars 2026 sans erreur : le champ jour déborde et l'excédent est reporté sur le mois suivant. La même règle transforme un indice de mois à 12 en janvier de l'année suivante et un jour à 0 en dernier jour du mois précédent, d'où l'idiome courant new Date(y, m, 0).getDate() pour la longueur d'un mois. Rien dans la valeur renvoyée ne distingue un débordement voulu d'une faute de frappe. La parade est un contrôle aller-retour : construis la date, puis vérifie que l'année, le mois et le jour relus sont bien les trois écrits. S'ils diffèrent, rejette la saisie. Le faire à la frontière coûte bien moins cher que de découvrir plus tard qu'une colonne de base de données contient une date que personne n'a jamais saisie.
Faut-il stocker les dates en UTC ou en heure locale ?
Cela dépend de ce que la valeur signifie, et répondre « toujours en UTC » crée autant de bugs que cela n'en évite. Tout ce qui enregistre quand quelque chose s'est produit — une ligne de journal, un paiement, une péremption de cache, une fenêtre de limitation de débit — est un instant, et les instants vont en UTC. Tout ce qui enregistre quand quelque chose doit se produire dans la journée de quelqu'un — un rappel à 9 h, un créneau de livraison, les horaires d'un magasin, une réunion récurrente — est une valeur de calendrier, et la stocker en instant UTC brut est un bug qui attend un changement de règle. Les gouvernements modifient les règles d'heure d'été plusieurs fois par an, et la base tz publie des versions pour les suivre ; un rendez-vous futur figé en instant dérivera de l'heure murale voulue si son fuseau est modifié. Stocke-les en date locale, heure locale et nom de fuseau IANA, et ne résous en instant qu'au moment d'agir.

Articles qui pourraient t'intéresser

Tous les guides
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.ExplicationCalculer un anniversaire de couple ou de mariageCompter en arrière et compter en avant sont deux opérations différentes, et elles divergent d'un an le jour même. Ce que ce calculateur fait du 29 février, ce que font les autres bibliothèques de dates, et d'où viennent réellement les listes de cadeaux traditionnels et modernes.TutorielCombien de semaines avant une date ? (avec exemples)Compte les semaines entre aujourd'hui et une date future. Découvre la méthode « diviser par sept », quand arrondir, et la différence entre comptage inclusif et exclusif.TutorielComment ajouter ou soustraire des jours à une dateCompte en avant pour ajouter, en arrière pour soustraire, en passant les fins de mois. Voici comment, pourquoi la longueur des mois et les années bissextiles piègent, et quand les week-ends comptent.TutorielCombien de jours jusqu'à une date ? (avec exemples)Compte les jours jusqu'à n'importe quelle date future, comprends le décompte inclusif ou exclusif, et crée un compte à rebours fiable.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 ?