Le bug commence souvent par une phrase qui paraît inoffensive
La plupart des bugs de date et d’heure ne commencent pas avec de l’astronomie compliquée. Ils commencent avec une phrase produit qui semble évidente: “envoyer le rappel demain matin”, “faire expirer cet essai après 30 jours”, “afficher les commandes d’aujourd’hui”, “lancer ce traitement à 2 h 30”, “stocker le timestamp”. Chaque phrase cache une règle. Demain matin où? Trente jours calendaires ou 30 fois 24 heures? Aujourd’hui dans la ville du client, dans l’entrepôt ou sur le serveur de base de données? Que se passe-t-il quand 2 h 30 n’existe pas?
Le logiciel sait compter. Les calendriers ne sont pas seulement du comptage. Ils empilent des conventions humaines: fuseaux horaires, changements d’heure, jours ouvrés, formats locaux, années bissextiles, semaines ISO, types de base de données et heures limites légales. Les bugs apparaissent quand le code traite une couche comme si elle était une autre.
Ce guide est un manuel de terrain pour les erreurs qui cassent régulièrement des systèmes en production. Il s’adresse aux développeurs, product managers, analystes et à toute personne qui a déjà regardé un tableau de bord en se demandant pourquoi les chiffres d’hier avaient changé après le petit déjeuner.
1. Datetimes naïfs: le timestamp sans passeport
Un datetime naïf est une date et une heure sans fuseau horaire. 2026-03-08 02:15 paraît précis, mais ne dit pas où cela s’est produit. À New York, lors du passage à l’heure d’été au printemps 2026, l’heure de 02:00 à 02:59 n’existe pas. Dans une autre ville, elle peut être parfaitement valide. En UTC, elle signifie encore autre chose.
L’échec courant consiste à stocker une heure locale comme si c’était un instant. Une réunion créée pour “9 h Europe/Madrid” doit rester à 9 h à Madrid quand l’Espagne change d’heure. Un événement de paiement déjà arrivé doit être stocké comme un instant. Ce sont deux modèles différents.
| Cas d’usage | Stocker | Pourquoi |
|---|---|---|
| Journal d’audit, clic, paiement, événement API | Instant, généralement UTC | L’événement a déjà eu lieu à un moment précis. |
| Réunion future dans une ville | Date locale, heure locale, fuseau IANA | L’heure locale doit survivre aux changements de règles DST. |
| Anniversaire, jour férié, jour de facturation | Date seule | Il peut ne pas y avoir d’heure utile. |
| Horaires d’ouverture | Heure locale plus lieu ou règle | La règle se répète en temps civil local. |
Si vous devez comparer des moments exacts, convertissez en instants. Si vous devez garder un rendez-vous humain là où il a été prévu, gardez aussi le lieu. Un décalage comme UTC+01:00 ne suffit pas pour planifier dans le futur, car le décalage peut changer.
2. Confusion avec UTC: bon stockage, mauvaise expérience utilisateur
“Tout stocker en UTC” est une règle utile pour les événements déjà arrivés. Ce n’est pas une conception produit complète. Les utilisateurs ne pensent pas en UTC quand ils réservent un dentiste, publient un webinaire ou choisissent une date d’arrivée à l’hôtel. Ils pensent en temps civil local.
Le bug apparaît quand un système stocke UTC correctement mais oublie le fuseau voulu par l’utilisateur. Une invitation créée à Los Angeles pour 9 h peut être représentée en interne comme 17 h UTC. C’est normal. Mais si l’application reconstruit ensuite l’événement avec le fuseau du serveur, ou ne stocke qu’un décalage fixe, la réunion peut bouger après un changement d’heure.
Les bons systèmes séparent trois questions: quel est l’instant, quelle heure locale l’utilisateur voulait-il, et quelles règles de fuseau horaire expliquent cette heure locale? Notre convertisseur de fuseaux horaires est utile précisément parce que la date, le lieu et le décalage comptent ensemble.
3. Trous et replis DST: des heures qui disparaissent ou arrivent deux fois
L’heure d’été crée deux pièges classiques. Au printemps, une heure locale peut être sautée. À l’automne, une heure locale peut arriver deux fois. Si votre planificateur accepte “2026-03-08 02:30 America/New_York”, que doit-il faire? Rejeter l’entrée? La déplacer à 03:00? L’interpréter dans un autre fuseau? Il n’existe pas de réponse universelle. Le produit a besoin d’une règle.
Les heures ambiguës d’automne sont encore plus subtiles. “2026-11-01 01:30 America/New_York” peut désigner la première 01:30 ou la seconde 01:30. Les deux libellés locaux sont réels. Journaux, transports, systèmes hospitaliers et planificateurs de tâches doivent savoir lequel était voulu.
Une bonne suite de tests contient de vraies dates de transition pour les fuseaux qui comptent pour vos utilisateurs. Ne testez pas seulement le 15 janvier. Testez un jour de passage au printemps, un jour de retour à l’automne et un pays dont les règles DST diffèrent des vôtres.
4. Dates seules transformées accidentellement en minuit
Une date n’est pas toujours un datetime avec minuit ajouté. Une réservation d’hôtel pour le 3 juin, un anniversaire, une date limite fiscale et un jour férié sont des concepts de date. Quand le code les transforme en 2026-06-03T00:00:00Z, il invente une heure et un fuseau.
C’est ainsi que des anniversaires se décalent au jour précédent pour les utilisateurs à l’ouest d’UTC. C’est aussi ainsi que “le rapport d’aujourd’hui” peut afficher les données d’hier pour une équipe et celles de demain pour une autre. La correction est simple: utilisez des types date seule pour les concepts date seule, et ne créez un datetime que lorsque la règle métier a réellement besoin d’une heure.
Pour les échéances, posez une question plus précise: l’élément expire-t-il au début de la date, à la fin de la date, à la fermeture locale de l’entreprise ou à un instant exact? “Expire le 3 juin” est une copie produit, pas une règle complète.
5. Ajouter des jours comme des secondes
Ajouter 86 400 secondes n’est pas la même chose qu’ajouter un jour calendaire en heure locale. La plupart des jours durent 24 heures, mais les jours de changement d’heure peuvent durer 23 ou 25 heures. Si un rappel quotidien doit apparaître à 8 h heure locale, ajoutez un jour calendrier dans le fuseau de l’utilisateur. Si un cache doit expirer après exactement 24 heures, ajoutez une durée.
| Formulation | Signifie généralement | Modèle plus sûr |
|---|---|---|
| Même heure demain | Jour calendaire local suivant à la même heure murale | Date/heure dans le fuseau utilisateur |
| Expire dans 24 heures | Durée écoulée exacte | Instant plus durée |
| Échéance dans 3 jours ouvrés | Règle locale de jours de travail | Calendrier ouvré |
| Tous les lundis à 9 h | Horaire local récurrent | Heure locale plus fuseau IANA |
C’est pourquoi un calculateur de jours ouvrés n’est pas seulement un calculateur d’écart entre dates avec les week-ends retirés. Jours fériés, week-ends locaux et fermetures observées peuvent changer le résultat.
6. Unités des timestamps Unix: secondes, millisecondes, microsecondes, nanosecondes
Le temps Unix est élégant: compter le temps depuis 1970-01-01T00:00:00Z. Le problème est que les écosystèmes logiciels ne sont pas d’accord sur l’unité. Beaucoup d’API utilisent les secondes. JavaScript utilise les millisecondes. Certaines bases de données et plateformes d’observabilité utilisent microsecondes ou nanosecondes. 1717425600 et 1717425600000 représentent le même moment avec des unités différentes; utilisez la mauvaise et votre date saute de plusieurs siècles.
Le contrôle le plus rapide est le nombre de chiffres. Les secondes Unix actuelles ont 10 chiffres. Les millisecondes en ont 13. Les microsecondes, 16. Les nanosecondes, 19. Un bon importateur doit identifier l’unité explicitement ou rejeter les entrées ambiguës au lieu de deviner en silence. Notre convertisseur de timestamp Unix montre la même idée sous forme d’outil.
7. Trier des dates comme chaînes dans le mauvais format
Les dates de style ISO se trient bien comme texte: 2026-06-03 vient avant 2026-12-01. Beaucoup de formats locaux ne le font pas. Une liste de chaînes MM/DD/YYYY ou DD/MM/YYYY peut sembler correcte dans un petit échantillon et échouer dès que les mois ou les pays changent.
Utilisez ISO 8601 ou de vrais types date pour l’échange et le stockage. Utilisez les formats localisés seulement à la limite d’affichage. Si un CSV est destiné à des humains, indiquez le format attendu dans l’en-tête ou la documentation d’export. S’il est destiné à des machines, préférez un standard non ambigu comme RFC 3339 pour les timestamps.
8. Bugs de numéro de semaine autour du Nouvel An
Les numéros de semaine ne sont pas universels. Les semaines ISO commencent le lundi et font de la semaine 1 celle qui contient le premier jeudi. Certains systèmes de style américain commencent le dimanche et comptent comme semaine 1 celle qui contient le 1er janvier. Autour du Nouvel An, ces règles peuvent différer d’une semaine entière.
Si un rapport indique “revenu semaine 1”, qu’un export paie indique “2026-W01” et qu’un tableur utilise sa fonction par défaut, confirmez la règle avant de rapprocher les chiffres. Pour les équipes internationales, l’étiquette la plus sûre est le système de semaine plus la plage de dates, par exemple: “ISO 2026-W01, 29 déc. 2025-4 janv. 2026”.
La même logique existe dans notre outil de numéro de semaine, qui affiche la plage de dates au lieu de traiter le numéro comme évident.
9. Fuites du fuseau horaire du serveur
L’un des bugs de production les plus frustrants est le code qui passe tous les tests en local et échoue après déploiement parce que le serveur tourne dans un autre fuseau horaire. Le portable du développeur, le runner CI, l’image Docker, la base de données, le worker de file et l’entrepôt analytique peuvent tous avoir des valeurs par défaut différentes.
Définissez des fuseaux horaires explicites dans les tests. Exécutez au moins un job de test avec un fuseau non local. Évitez de dépendre des valeurs par défaut du processus pour parser ou formater. Si une fonction reçoit une chaîne de date, faites du format attendu et du fuseau une partie du contrat.
10. “Fin de journée” sans lieu
Fin de journée paraît clair jusqu’à ce que les clients vivent dans plusieurs endroits. Une promotion qui se termine “vendredi à minuit” peut signifier minuit local du client, minuit au siège de l’entreprise, minuit UTC ou l’heure limite du processeur de paiement. Chaque règle crée une expérience différente.
Une bonne copie produit nomme le lieu ou utilise un compte à rebours lié à l’échéance réelle. Un bon backend stocke l’échéance comme un instant précis après application de la règle métier. Une bonne documentation support dit quelle règle a été utilisée.
Checklist pratique avant de livrer du code date/heure
- Cette valeur est-elle un instant, une date/heure locale, une date seule, une durée ou une règle de récurrence?
- Si elle est locale, quel fuseau IANA possède la règle?
- Si c’est une date seule, avons-nous évité d’inventer minuit UTC?
- Les trous et replis DST ont-ils un comportement explicite?
- Les unités de timestamp sont-elles nommées, validées et testées?
- Les rapports définissent-ils le système de semaine et la plage de dates?
- Les tests tournent-ils hors du fuseau du développeur?
- Le texte visible en dit-il assez pour qu’une personne agisse correctement?
La règle plus profonde
Les bugs de date et d’heure viennent rarement d’un manque de code malin. Ils viennent d’un nom manquant. “Demain” a besoin d’un lieu. “Trente jours” doit dire calendrier ou durée. “Semaine 1” a besoin d’un système. “Timestamp” a besoin d’une unité. “Fin de journée” a besoin d’une juridiction ou d’un fuseau horaire.
Quand le nom est explicite, le code devient souvent plus simple. La colonne de base de données reçoit le bon type. Le contrat API surprend moins. L’interface peut expliquer le résultat. Et la prochaine personne qui lit le code n’a pas besoin de deviner ce que “maintenant plus un jour” devait signifier.