7 bugs de timestamp Unix que tout développeur a déjà livrés
Voici les erreurs de timestamp qui provoquent des incidents en production : incohérences d'unité silencieuses, hypothèses de fuseau, le ×1000 oublié, le stockage en chaîne, le parsing ambigu et les erreurs aux limites de l'heure d'été. Chaque bug inclut le symptôme, la cause racine et un correctif pratique.
Bug 1 : oubli du × 1000 en JavaScript
new Date(1700000000) produit une date proche de janvier 1970, pas de novembre 2023. Le constructeur Date de JavaScript attend des millisecondes, mais la plupart des API serveur et des bases renvoient des secondes Unix. Ce bug apparaît souvent d'abord dans les tableaux de bord d'admin car le payload backend est du JSON valide et l'UI affiche simplement la mauvaise année.
- Wrong: new Date(response.created_at) — if created_at is Unix seconds
- Right: new Date(response.created_at * 1000)
- Detection: if your date shows a year near 1970, the timestamp is in seconds not milliseconds
- Safe wrapper: const toDate = ts => new Date(ts < 1e11 ? ts * 1000 : ts)
Bug 2 : unités mélangées à une frontière système
JavaScript renvoie des millisecondes. Python, Go et la plupart des API serveur renvoient des secondes. Quand JavaScript envoie un timestamp à un backend Python sans conversion, le backend interprète une valeur en millisecondes comme des secondes, plaçant l'événement en l'an 55792. Le sens inverse est tout aussi grave : l'UI reçoit des secondes, les passe directement à new Date() et affiche janvier 1970.
- JavaScript sends: Date.now() → 1700000000000 (ms)
- Python receives: datetime.fromtimestamp(1700000000000) → year 55792
- Fix: divide by 1000 before sending to a non-JavaScript system
- Better fix: use ISO 8601 strings at API boundaries — they are unambiguous
Bug 3 : se fier au fuseau local du serveur
Le code qui utilise datetime.fromtimestamp() de Python sans préciser UTC produit des résultats différents selon le fuseau configuré du serveur. Cela marche en développement et casse en production lors d'un déploiement dans une autre région. La même catégorie de bug apparaît en Node quand le code formate des dates sans option timeZone explicite puis fige le résultat dans les tests.
- Wrong: datetime.fromtimestamp(ts) — uses server's local timezone
- Right: datetime.fromtimestamp(ts, tz=datetime.timezone.utc)
- Wrong in JS: date.toLocaleDateString() — uses the Node process timezone
- Right in JS: date.toISOString() or Intl.DateTimeFormat with an explicit timeZone option
Bug 4 : stocker les timestamps en chaînes
Stocker un timestamp en chaîne formatée dans une colonne VARCHAR semble inoffensif jusqu'à ce qu'il faille trier par temps, interroger une plage ou faire de l'arithmétique. La comparaison de chaînes de dates ne marche qu'avec des champs ISO 8601 stricts à zéros de remplissage. Des chaînes lisibles comme Nov 15, 2023 ou 1/2/24 sont des formats d'affichage, pas de stockage.
- Wrong: INSERT INTO events (created) VALUES ('Nov 15, 2023') — not sortable
- Wrong: INSERT INTO events (created) VALUES ('2023-11-15') — loses time component
- Right: use a native DATETIME/TIMESTAMPTZ column or BIGINT for Unix milliseconds
- If a string is unavoidable: use ISO 8601 with full precision: '2023-11-15T06:13:20Z'
Bug 5 : parser des chaînes de date ambiguës
new Date('2024-01-01') et new Date('2024/01/01') semblent équivalents mais se comportent différemment. Le format ISO 8601 à tirets est parsé comme minuit UTC ; le format à barres dépend de l'implémentation et la plupart des navigateurs le parsent comme minuit local. Un sélecteur de date, un payload d'API et une ligne de base peuvent afficher la même date de calendrier tout en représentant des instants différents.
- new Date('2024-01-01') → January 1, 2024 00:00:00 UTC (correct, explicit)
- new Date('2024/01/01') → January 1, 2024 00:00:00 local time (varies by runtime)
- new Date('January 1, 2024') → local time, may be rejected by strict parsers
- Safe rule: always use ISO 8601 with explicit timezone: new Date('2024-01-01T00:00:00Z')
Bug 6 : ajouter 24 heures pour dire demain
Ajouter 86 400 000 millisecondes semble une façon propre d'obtenir demain, mais les jours de calendrier locaux ne durent pas toujours 24 heures. Lors des transitions d'heure d'été, un jour local peut faire 23 ou 25 heures. Si la logique produit signifie le prochain jour de calendrier local, utilisez de l'arithmétique de calendrier dans le fuseau cible, pas de l'arithmétique de durée en millisecondes UTC.
- Mauvais pour les jours de calendrier : next = new Date(date.getTime() + 86400000)
- Mieux en code Date local : copiez le Date puis appelez setDate(d.getDate() + 1)
- Pour le code serveur : utilisez une bibliothèque gérant les fuseaux ou l'arithmétique de dates de la base
- Pour les planifications récurrentes : stockez local_date, local_time et timezone_id, puis calculez chaque occurrence
- Ajoutez des tests pour les dates de début et de fin d'heure d'été dans le fuseau cible
Bug 7 : filtres de fin de journée inclusifs
Une requête analytics courante utilise created_at >= start et created_at <= endOfDay. Cela paraît raisonnable, mais crée des bugs de précision quand la base stocke des millisecondes, microsecondes ou nanosecondes. Le modèle plus sûr est une plage semi-ouverte : supérieur ou égal au début, et strictement inférieur au début de la période suivante.
- Risky: WHERE created_at <= '2026-05-19 23:59:59'
- Safer: WHERE created_at >= '2026-05-19' AND created_at < '2026-05-20'
- Half-open ranges work for seconds, milliseconds, microseconds, and nanoseconds
- The same pattern works for months: created_at >= monthStart AND created_at < nextMonthStart
Checklist de prévention en production
La plupart des incidents de timestamp sont évitables par le nommage, les tests et des frontières explicites. Le but n'est pas de rendre chaque ligne de code de date astucieuse ; c'est de rendre l'unité, le fuseau et la sémantique de plage d'un ennui évident.
- Nommez les champs numériques avec leur unité : createdAtMs, expiresAtSeconds, imported_at_ms
- Utilisez UTC pour le stockage et des fuseaux IANA explicites pour l'affichage
- Préférez des chaînes ISO 8601 avec Z ou offsets aux frontières d'API inspectées par des humains
- Utilisez des plages semi-ouvertes pour les filtres de date
- Testez autour du 1er janvier, des jours bissextiles, des transitions d'heure d'été et de la limite de l'an 2038
FAQ sur les bugs de timestamp Unix
- Quel est le bug de timestamp Unix le plus courant ?
- Passer des secondes là où des millisecondes sont attendues, ou l'inverse. En JavaScript, new Date(unixSeconds) tombe en 1970 car le constructeur attend des millisecondes ; multipliez d'abord la valeur à 10 chiffres par 1000.
- Comment prévenir les bugs secondes-vs-millisecondes ?
- Nommez les champs avec leur unité (createdAtMs, expiresAtSeconds), convertissez aux frontières système et préférez des chaînes ISO 8601 dans les contrats d'API pour que la valeur s'auto-décrive.
- Pourquoi ajouter 86 400 000 ms casse « demain » ?
- Les jours de calendrier locaux durent 23 ou 25 heures lors des transitions d'heure d'été, donc ajouter 24 heures fixes peut tomber sur la mauvaise date. Utilisez l'arithmétique de calendrier dans le fuseau cible.