L'API Temporal

Aujourd'hui je vous propose de découvrir l'objet Temporal qui permet de de remplacer l'objet Date avec une gestion plus rigoureuse des fuseaux horaires, des objets dédiés à chaque cas d'usage (date seule, heure seule, date avec timezone…), des opérations arithmétiques claires et une immutabilité par défaut.

Sommaire

  • 00:00 Introduction
  • 00:27 Les problèmes de l'objet Date
  • 02:00 Présentation de Temporal
  • 04:16 Création d'un objet Temporal
  • 07:50 Manipuler une date
  • 11:37 Gestion des durées
  • 15:50 Gestion des input
  • 18:50 Conclusion

Les problèmes de l'objet Date

Pour comprendre l'intérêt de cette nouvelle API, il faut d'abord rappeler pourquoi l'objet Date existant en JavaScript pose problème.

Pas de notion de fuseau horaire. En interne, JavaScript représente une date comme le nombre de millisecondes écoulées depuis le premier Janvier 1970. L'inconvénient, c'est que le fuseau horaire est entièrement géré par le navigateur. On ne peut pas créer une date en précisant explicitement dans quel fuseau horaire elle se situe.

Une API peu intuitive. Il y a ensuite tout un tas de subtilités qui piègent régulièrement :

  • Les mois sont indexés à 0. Pour créer une date au 1er janvier 2025, il faut écrire new Date(2025, 0, 1). Mettre 1 donne… février.
  • Les dates invalides sont silencieusement corrigées. new Date(2025, 1, 30) ne lève aucune erreur et retourne le 2 mars.
  • getYear() ne retourne pas l'année mais le nombre d'années écoulées depuis 1900. Il faut utiliser getFullYear().

On pourrait continuer la liste, mais l'idée est là : l'objet Date est truffé de pièges.

Présentation de Temporal

C'est dans ce contexte que l'API Temporal arrive. À l'heure actuelle, elle n'est supporée que par Chrome et Firefox mais il est possible d'utiliser un polyfill comme js-temporal-polyfill ou fullcalendar/temporal-polyfill pour les rendre disponibles partout.

La première chose qui peut surprendre en découvrant cette API, c'est la quantité d'objets disponibles. Mais chaque objet correspond à un cas d'usage précis :

Type Usage
Temporal.PlainDate Une date sans heure ni fuseau (ex : une date de naissance)
Temporal.PlainTime Une heure sans date ni fuseau
Temporal.PlainDateTime Une date + heure, sans fuseau
Temporal.ZonedDateTime Une date + heure avec fuseau horaire
Temporal.Instant Un instant précis sur la timeline (équivalent d'un timestamp)
Temporal.Duration Une durée

L'idée, c'est de choisir l'objet adapté à la situation. On modélise un réveil ? PlainTime. L'anniversaire d'un utilisateur ? PlainMonthDay. Un événement planifié dans un fuseau horaire précis ? ZonedDateTime.

Il est également bon de noter que Temporal supporte des calendriers autres que le calendrier grégorien.

Créer un objet Temporal

Il y a trois façons de construire un objet Temporal.

Via Temporal.Now, qui permet d'obtenir l'instant présent dans différents formats :

const now = Temporal.Now.plainDateTimeISO(); // PlainDateTime en heure locale
const today = Temporal.Now.plainDateISO(); // PlainDate du jour

Via le constructeur directement, avec les valeurs numériques. Contrairement à l'objet Date, les mois sont indexés à 1 :

const date = new Temporal.PlainDate(2025, 1, 1); // 1er janvier 2025
const datetime = new Temporal.PlainDateTime(2025, 1, 1, 12, 0, 0); // à midi

Via from(), à partir d'une chaîne ISO :

const date = Temporal.PlainDate.from("2025-01-01");
const datetime = Temporal.PlainDateTime.from("2025-01-01T12:00:00");

Pour une ZonedDateTime, il faut préciser le fuseau horaire dans la chaîne :

const zdt = Temporal.ZonedDateTime.from(
  "2025-01-01T12:00:00+01:00[Europe/Paris]",
);
console.log(zdt.toString());
// 2025-01-01T12:00:00+01:00[Europe/Paris]

Manipuler une date

Les objets Temporal sont immuables. Toute modification retourne un nouvel objet, ce qui évite les mauvaises surprises.

Pour modifier une propriété, on utilise with() :

const date = Temporal.PlainDateTime.from("2025-01-01T12:00:00");
const date2030 = date.with({ year: 2030 });
console.log(date2030.toString()); // 2030-01-01T12:00:00

Pour ajouter ou soustraire du temps, on a add() et subtract() :

const date = Temporal.PlainDate.from("2025-01-01");
const plus4 = date.add({ days: 4 }); // 2025-01-05
const plus40 = date.add({ days: 40 }); // 2025-02-10 (change de mois)
const moins40 = date.subtract({ days: 40 });

Pour les objets avec une notion d'heure, startOfDay() permet de revenir à minuit :

const atMidnight = datetime.startOfDay(); // 2025-01-01T00:00:00

Pour changer de fuseau horaire sur une ZonedDateTime, on utilise withTimeZone() :

const paris = Temporal.ZonedDateTime.from(
  "2025-01-01T12:00:00+01:00[Europe/Paris]",
);
const utc = paris.withTimeZone("UTC");
console.log(utc.toString()); // 2025-01-01T11:00:00+00:00[UTC]

Comparer deux dates se fait via la méthode statique compare() disponible sur chaque objet. Elle retourne -1, 0 ou 1 :

Temporal.ZonedDateTime.compare(date1, date2);

Attention, l'opérateur === et même la méthode equals() comparent les objets par référence, pas par valeur.

Gestion des durées

L'objet Duration représente une durée. On peut en obtenir une en soustrayant deux dates avec until() (ou son inverse since()) :

const start = Temporal.PlainDate.from("2025-01-01");
const end = Temporal.PlainDate.from("2025-07-21");
const duration = start.until(end);

Par défaut, la durée est exprimée dans la plus petite unité pertinente (ici les jours). Pour obtenir une représentation en mois, il faut préciser largestUnit :

const duration = start.until(end, { largestUnit: "months" });
// { months: 6, days: 20, hours: 1, ... }

On peut ensuite arrondir la durée avec round(). Attention cependant : les conversions impliquant des mois nécessitent un relativeTo, car un mois n'a pas toujours le même nombre de jours :

const rounded = duration.round({ largestUnit: "days", relativeTo: start });

Pour les unités inférieures au jour (heures, minutes…), la conversion est directe et ne pose aucun problème.

Enfin, on peut formater une durée à l'aide de Intl.DurationFormat :

const formatter = new Intl.DurationFormat("fr", {
  style: "short",
});
console.log(formatter.format(duration)); // "6 mois, 20 j."

La combinaison de largestUnit et smallestUnit donne un contrôle assez fin sur la façon d'afficher la durée.

Gestion des inputs HTML

Temporal s'intègre naturellement avec les inputs de type date, datetime-local et time. Ces champs retournent leur valeur sous forme de chaîne ISO, que l'on peut passer directement à from() :

// <input type="time" id="time-input">
const input = document.querySelector("#time-input");
input.addEventListener("change", () => {
  const time = Temporal.PlainTime.from(input.value);
  const timePlusThree = time.add({ hours: 3 });
  console.log(timePlusThree.toString()); // ex: "15:24:00"
});

Le même principe s'applique pour les autres types :

// type="date" → Temporal.PlainDate.from(input.value)
// type="datetime-local" → Temporal.PlainDateTime.from(input.value)

Si on souhaite travailler avec un fuseau horaire à partir d'un datetime-local, on peut convertir la PlainDateTime obtenue en ZonedDateTime :

const plain = Temporal.PlainDateTime.from(input.value);
const zoned = plain.toZonedDateTime("Europe/Paris");
console.log(zoned.toString());

Conclusion

La première fois qu'on tombe sur la documentation de Temporal, la multiplicité des objets peut sembler intimidante. Mais en pratique, chaque objet répond à un besoin précis, et cette granularité est justement ce qui rend l'API solide.

  • Immutabilité par défaut : plus de surprises liées à une date modifiée par effet de bord.
  • Gestion explicite des fuseaux horaires.
  • Mois indexés à 1, dates invalides rejetées : une API cohérente et prévisible.
  • Des objets adaptés à chaque cas d'usage (PlainDate, PlainTime, ZonedDateTime…).

Pour les nouveaux projets, difficile de trouver une bonne raison de continuer à utiliser l'objet Date en dehors du support qui n'est pas encore optimal. Pour suivre l'état du support :

  • Safari
  • Bun
  • NodeJS supporte via un drapeau node --harmony-temporal app.js
Publié
Technologies utilisées
Auteur :
Grafikart
Partager