Fuseaux horaires en JavaScript sans surprises : UTC, noms IANA et heure d'été

Une personne à Paris programme un appel vidéo à 9 h. Son collègue à New York voit 3 h au lieu des 4 h attendues. Un rappel qui fonctionnait le mois dernier se déclenche une heure trop tôt. Un journal serveur contient un nombre qui ressemble à un horodatage Unix, mais affiche une date de 1970.

Ces problèmes viennent d’une confusion entre heures locales, instants UTC ou unités d’horodatage. Une date et une heure ne désignent pas forcément un instant sans ambiguïté. Une application web doit distinguer un moment sur la ligne du temps de l’heure locale affichée à une personne. Voyons comment le faire en JavaScript, ce que les changements d’heure compliquent et les cas à tester avant de publier une fonction de planification.

Pour visualiser les exemples, utilisez notre convertisseur horaire universel gratuit. Il permet de comparer des fuseaux nommés, d’examiner les heures répétées ou sautées, de lire des horodatages et de partager une conversion. Son article de lancement présente ses principaux usages.

Un instant et une heure locale sont deux choses différentes

Un instant est un point précis dans le temps. Par exemple, 2026-11-01T05:30:00Z représente un instant UTC au format ISO 8601. Il ne dépend pas du lieu où se trouve la personne qui le consulte.

Une heure locale est celle qu’indique une horloge dans un lieu donné, comme “jeudi à 9 h à Paris”. Il faut une date du calendrier et un fuseau horaire pour déterminer l’instant correspondant. L’identifiant IANA Europe/Paris est bien plus utile qu’un décalage supposé de UTC+1, car Paris change de décalage au cours de l’année.

Choisissez les données adaptées au besoin :

Besoin du produitDonnées conseillées
“Ce webinaire commence à un instant précis.”Horodatage UTC, éventuellement accompagné d’un fuseau IANA pour l’affichage
“Afficher la date à laquelle cette commande a été passée.”Horodatage UTC, affiché dans le fuseau de la personne qui le consulte
“Ce magasin ouvre à 9 h chaque lundi à Paris.”Heure locale, jour ou règle de récurrence et Europe/Paris
“Programmer le premier du mois prochain à 8 h à New York.”Date et heure locales, fuseau IANA et règle explicite pour les ambiguïtés des changements d’heure

Les deux derniers cas ne se résolvent pas correctement en ajoutant un nombre fixe de millisecondes à un horodatage UTC. L’intervalle entre deux jours consécutifs à 9 h locales peut durer 23 ou 25 heures autour d’un changement d’heure.

Stocker les événements fixes sous forme d’instants UTC

Pour un rendez-vous dont l’instant précis est déjà déterminé, transmettez un horodatage ISO UTC dans votre API :

{
  "eventId": "demo-42",
  "startsAt": "2026-11-01T05:30:00Z",
  "eventTimeZone": "America/New_York"
}

L’horodatage sert aux rappels, au tri et aux comparaisons. Le fuseau conserve le contexte dans lequel l’événement a été organisé, ou l’heure locale pertinente pour son organisateur. Il ne remplace pas l’horodatage.

En JavaScript, analysez l’horodatage UTC et affichez-le dans un fuseau explicitement nommé. Les exemples conservent la locale en-US et leurs commentaires en anglais pour rendre leurs résultats comparables :

const startsAt = new Date("2026-11-01T05:30:00Z");

const formatter = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  year: "numeric",
  month: "short",
  day: "numeric",
  hour: "2-digit",
  minute: "2-digit",
  timeZoneName: "short"
});

console.log(formatter.format(startsAt));
// Nov 1, 2026, 01:30 AM EDT (format varies by runtime/locale)

Intl.DateTimeFormat utilise les règles de fuseau disponibles dans l’environnement JavaScript. Les libellés, la ponctuation et les abréviations peuvent varier selon la locale et l’environnement. Pour une valeur exploitable par du code, conservez l’horodatage ISO d’origine au lieu d’analyser à nouveau la chaîne d’affichage localisée.

Pour afficher l’événement dans le fuseau système de l’utilisateur, omettez timeZone, ou transmettez un fuseau IANA choisi par l’utilisateur. Une préférence explicite convient mieux aux personnes qui voyagent souvent ou travaillent dans plusieurs lieux.

Pourquoi le changement d’heure crée deux occurrences de 1 h 30

New York a reculé ses horloges le 1er novembre 2026. Ce jour-là, 1 h 30 du matin se produit deux fois. Avec le formatter défini précédemment, comparez ces deux instants UTC :

const first = new Date("2026-11-01T05:30:00Z");
const second = new Date("2026-11-01T06:30:00Z");

for (const instant of [first, second]) {
  console.log(formatter.format(instant));
}
// 1:30 a.m. EDT
// 1:30 a.m. EST

Les deux horloges affichent 1 h 30, mais les instants sont séparés d’une heure. Si l’interface accepte “le 1er novembre à 1 h 30 à New York”, elle doit prévoir une règle : demander à l’utilisateur s’il s’agit de la première ou de la seconde occurrence, ou appliquer un choix par défaut clairement documenté.

L’inverse se produit lorsque les horloges avancent. Le 8 mars 2026, 2 h 30 du matin n’a pas existé à New York. Un formulaire de planification doit rejeter cette saisie locale ou expliquer l’ajustement prévu, plutôt que de choisir silencieusement une autre heure.

Le convertisseur horaire universel permet d’examiner rapidement ces cas précis avant d’écrire les tests.

Ne pas interpréter une date et heure sans fuseau comme de l’UTC

Ce code est risqué :

const appointment = new Date("2026-11-01T01:30:00");

Comme l’explique la référence MDN de Date, sans Z ni décalage explicite, cette chaîne contenant une date et une heure est interprétée dans le fuseau local de l’environnement d’exécution. Elle ne désigne pas America/New_York, et l’heure ambiguë peut être résolue selon des règles locales que les utilisateurs de votre application n’ont jamais choisies.

Les chaînes contenant seulement une date suivent une autre règle : new Date("2026-11-01") désigne minuit UTC. N’en déduisez pas le comportement d’une chaîne qui contient aussi une heure.

Une autre erreur tentante consiste à ajouter Z :

const wrong = new Date("2026-11-01T01:30:00Z");

Ajouter Z signifie que la saisie était déjà en UTC. Cela ne convertit pas une heure locale de New York en UTC. Le résultat désignerait un tout autre instant.

Pour un formulaire de planification en heure locale, conservez séparément les champs du calendrier et le fuseau IANA. Résolvez-les avec une bibliothèque maintenue qui gère les fuseaux, ou une implémentation de Temporal adaptée, en choisissant délibérément le traitement des heures ambiguës et inexistantes. Vérifiez la prise en charge de Temporal dans vos navigateurs et versions de Node.js cibles, sans supposer sa disponibilité partout.

Comparer deux villes à la date prévue

Supposons qu’un collègue new-yorkais propose 9 h. L’heure correspondante à Paris n’est pas toujours la même :

  • Le 15 janvier 2026, 9 h à New York correspond à 15 h à Paris.
  • Le 15 mars 2026, 9 h à New York correspond à 14 h à Paris.

Les deux pays ne changent pas d’heure le même week-end. Un décalage de six heures inscrit en dur rend incorrect en mars un horaire qui semblait juste en hiver.

Dans les échanges d’équipe, transmettez l’instant UTC et le nom du fuseau prévu, plutôt qu’une phrase ambiguë comme “la mise en production est à 9 h”.

Distinguer les secondes Unix des millisecondes JavaScript

La méthode JavaScript Date.getTime() utilise des millisecondes depuis l’époque Unix. De nombreuses API documentent leurs champs d’horodatage en secondes. Mélanger ces unités produit souvent des dates proches de janvier 1970 ou très éloignées dans le futur.

const instant = new Date("2026-11-01T05:30:00Z");
const unixMilliseconds = instant.getTime();
const unixSeconds = Math.floor(unixMilliseconds / 1000);

const fromSeconds = new Date(unixSeconds * 1000);
console.log(fromSeconds.toISOString());
// 2026-11-01T05:30:00.000Z

Dans la documentation d’une API, préférez des noms comme createdAtIso, expiresAtUnixSeconds ou timestampMs à un champ time ambigu. Indiquez explicitement les unités du schéma. Une requête reproductible préparée avec le générateur de commandes cURL peut aider à comparer la réponse réelle d’un point d’accès à sa documentation.

Les fuseaux servent aux dates, pas aux mesures de performances

Utilisez Date pour représenter les dates civiles et les horodatages. Ne calculez pas la durée d’une opération courte en supposant que deux heures affichées constituent des mesures monotones fiables.

Pour mesurer le temps d’exécution du code, utilisez performance.now() plutôt que de formater des dates ou de soustraire des chaînes d’heures locales. Notre article sur le brouillon High Resolution Time du W3C explique les horloges monotones, timeOrigin et les enjeux de confidentialité liés aux mesures temporelles.

Tester les dates qui mettent vos hypothèses en défaut

Une fonction liée aux fuseaux mérite plus qu’un test avec la date du jour. Ajoutez les cas suivants :

  1. Une date d’hiver et une date d’été ordinaires dans deux fuseaux nommés.
  2. Une semaine où les régions changent d’heure à des dates différentes.
  3. Une heure locale sautée lors du passage à l’heure d’été.
  4. Les deux occurrences d’une heure ambiguë à l’automne.
  5. Un événement qui franchit minuit ou change de jour pour un participant.
  6. Les secondes Unix et les millisecondes, y compris les saisies mal formées.
  7. Des bases de fuseaux, formats régionaux et configurations serveur différents.
  8. Une récurrence qui traverse un changement de décalage UTC.

Utilisez des instants attendus explicites dans les tests. Évitez de vérifier une chaîne localisée unique lorsque la ponctuation et les abréviations ne font pas partie du contrat de votre produit.

L'offre Premium arrive bientôt

Un abonnement Premium facultatif permettant de supprimer les publicités est prévu. Les abonnements ne sont pas encore disponibles.

Vous pouvez continuer à utiliser nos outils gratuits sans compte.