Associer les types temporels aux tâches liées à des dates réelles : sept exemples illustrés
Travaillez sur sept tâches courantes liées aux dates et aux heures à l’aide de JavaScript Temporal, allant des dates simples et du dépassement des mois aux fuseaux horaires, à l’arithmétique des heures d’été et à la validation.
Sources: JavaScript Temporal: 7 Date and Time Problems Explained Simply
Les exigences liées aux dates semblent généralement banales : prolonger une abonnement d’un mois, afficher une réunion dans le fuseau horaire d’un collègue, compter les jours entre deux dates. Les complications apparaissent plus tard, lorsque le mois de février s’avère trop court, qu’un changement d’heure d’été fait que « demain » est à 23 heures de distance, ou qu’il faut enregistrer une date d’anniversaire même si elle ne correspond à aucun fuseau horaire. L’API Temporal de JavaScript résout ce problème en attribuant à chaque type de valeur son propre type, de sorte que le type lui-même indique ce que signifie la valeur. Les sept exemples fonctionnels ci-dessous montrent quel type convient à quelle tâche, où les valeurs par défaut peuvent surprendre, et quelles décisions Temporal laisse délibérément à votre application.
Mettre en place un environnement pour exécuter les exemples
Le support natif est encore en cours de déploiement. Au moment de la rédaction, MDN indique que Temporal est disponible de manière limitée ; vérifiez donc le support dans les navigateurs et la version de Node.js que vous utilisez avant de compter sur l’implémentation intégrée. La page de référence Temporal sur MDN suit l’état actuel de compatibilité.
Pour expérimenter en l’absence de support natif, installez le polyfill Temporal dans un dossier de projet vide. Vous avez besoin de Node.js et de npm :
mkdir temporal-demo
cd temporal-demo
npm init -y
npm install @js-temporal/polyfill@0.5.1
Créez un fichier nommé demo.mjs et commencez-le par cette importation :
import { Temporal } from "@js-temporal/polyfill";
Ajoutez l’un des exemples ci-dessous après l’importation et exécutez le fichier :
node demo.mjs
Tous les exemples supposent la même importation. Le polyfill exporte une liaison locale Temporal et ne crée pas de variable globale, ce qui oblige chaque fichier qui l’utilise à effectuer cette importation. La version est fixée à 0,5.1 car c’est celle pour laquelle les exemples ont été écrits ; vérifiez les versions du polyfill avant de l’utiliser dans un projet réel.
1. Stocker une date de calendrier sans heure ni fuseau horaire
Une date d’anniversaire ou une échéance de contrat ne possède souvent pas d’heure significative. Si la exigence est simplement « 15 octobre », l’ajout de minuit et d’un fuseau horaire invente des informations, et ce sont précisément ces informations supplémentaires qui font que la date est décalée d’une journée lorsqu’elle est affichée ailleurs. Temporal.PlainDate ne représente que la date :
const deadline = Temporal.PlainDate.from("2026-10-15");
Ses champs sont de simples nombres et il est sérialisé en chaîne ISO :
console.log(deadline.year); // 2026
console.log(deadline.month); // 10
console.log(deadline.day); // 15
console.log(deadline.toString()); // 2026-10-15
Ce champ ne contient ni heure, ni décalage UTC, ni fuseau horaire. Il utilise par défaut le calendrier ISO, et contrairement à l’objet Date ancien, les mois sont numérotés de 1 à 12 plutôt que de zéro.
N’oubliez pas cette limitation : un PlainDate ne définit aucun instant en temps absolu. Si l’échéance signifie réellement « 17h à Delhi », vous avez également besoin d’une heure et d’un fuseau horaire, c’est là que ZonedDateTime entre en jeu.
2. Obtenir de nouvelles dates sans modifier l’originale
Les dates mutables partagées sont une source classique d’erreurs : une partie du code modifie une valeur que l’autre partie lit encore. Les objets temporels sont immuables, et les opérations arithmétiques génèrent toujours un nouvel objet :
const originalDate = Temporal.PlainDate.from("2026-10-15");
const extendedDate = originalDate.add({ days: 7 });
En enregistrant les deux valeurs, on constate que l’originale reste inchangée :
console.log(originalDate.toString()); // 2026-10-15
console.log(extendedDate.toString()); // 2026-10-22
Cela signifie que vous pouvez conserver la date limite initiale et celle prolongée côte à côte sans avoir besoin de cloner quoi que ce soit au préalable.
L’erreur fréquente qui en découle est d’appeler originalDate.add({ days: 7 }) sur une ligne à part et d’espérer que originalDate change. Ce n’est pas le cas. Il faut assigner ou retourner le résultat.
3. Déterminer ce que signifie « un mois plus tard »
Ajouter un mois au 31 janvier ne possède pas de réponse unique et correcte, car le 31 février n’existe pas. Votre application a besoin d’une règle. La valeur par défaut de Temporal consiste à restreindre le résultat à la date valide la plus proche. Commencez par la fin du mois de janvier :
const januaryEnd = Temporal.PlainDate.from("2026-01-31");
Ajouter un mois force le jour à la fin du mois de février :
const nextMonth = januaryEnd.add({ months: 1 });console.log(nextMonth.toString()); // 2026-02-28
Lorsque le déplacement silencieux de la date est inacceptable, demandez à Temporal de lancer une exception en passant overflow: "reject". Prenons la date de facturation comme exemple :
const billingDate = Temporal.PlainDate.from("2026-01-31");
Le rejet se manifeste sous forme d’une RangeError, que le fragment de code capture avant de relancer toute autre exception :
try {
billingDate.add({ months: 1 }, { overflow: "reject" });
} catch (error) {
if (!(error instanceof RangeError)) throw error;
console.log("That day does not exist in the next month.");
}
Aucune de ces options ne correspond à votre politique de facturation ; chacune n’est qu’un mécanisme pour la mettre en œuvre. Il existe également un effet cumulatif à prendre en compte. Une fois que le 31 janvier a été ramené au 28 février, l’ajout d’un autre mois donne le 28 mars et non le 31 mars, de sorte qu’un calendrier construit en ajoutant systématiquement un mois à la valeur précédente s’éloigne progressivement de la date cible. Si la règle est « le dernier jour de chaque mois », modélisez cette règle explicitement, par exemple en calculant toujours à partir de la date de référence initiale ou en utilisant la durée du mois, plutôt que d’enchaîner des additions.
4. Afficher un instant dans plusieurs fuseaux horaires
Une date et heure d’événement identifie un moment précis. La date et l’heure locales affichées sur l’écran de quelqu’un dépendent de son emplacement. Temporal.Instant représente ce moment lui-même :
const meeting = Temporal.Instant.from("2026-10-15T09:00:00Z");
En le convertissant en fuseaux horaires nommés, on obtient l’heure locale sur place, et en le convertissant à nouveau, on constate que les deux désignent toujours le même instant :
const delhi = meeting.toZonedDateTimeISO("Asia/Kolkata");
const london = meeting.toZonedDateTimeISO("Europe/London");console.log(delhi.toString());
// 2026-10-15T14:30:00+05:30[Asia/Kolkata]console.log(london.toString());
// 2026-10-15T10:00:00+01:00[Europe/London]console.log(delhi.toInstant().equals(london.toInstant()));
// true
Les représentations locales diffèrent, mais l’instant sous-jacent reste identique. (Les lignes imbriquées dans cet extrait devraient chacune constituer une instruction distincte.)
Préférez des fuseaux horaires nommés tels que Europe/London plutôt que des décalages fixes. Un fuseau horaire nommé intègre les règles régissant son décalage au fil de l’année ; un +01:00 codé manuellement ne décrit que Londres en heure d’été.
C’est ici que commencent de nombreux bugs : un décalage n’est pas une zone horaire. Un décalage indique la différence par rapport à UTC à un moment donné, tandis qu’une zone nommée fournit les règles qui déterminent cette différence à tout moment.
Les chaînes de caractères ci-dessus sont destinées aux machines, y compris l’annotation de zone entre crochets. Pour tout ce qui est affiché aux utilisateurs, formatez la valeur selon leur locale, par exemple avec toLocaleString() ou Intl.DateTimeFormat, plutôt que d’afficher la chaîne brute.
5. Séparer « demain » et « 24 heures plus tard »
Pendant la majeure partie de l’année, ces expressions donnent le même résultat. Cependant, lors des changements d’heure d’été, ce n’est plus le cas. Prenons midi à New York le 7 mars 2026, la veille du passage à l’heure d’été :
const beforeClockChange = Temporal.ZonedDateTime.from(
"2026-03-07T12:00:00-05:00[America/New_York]"
);
Ajouter une journée calendaire et ajouter 24 heures produisent des heures locales différentes :
const tomorrow = beforeClockChange.add({ days: 1 });
const after24Hours = beforeClockChange.add({ hours: 24 });console.log(tomorrow.toString());
// 2026-03-08T12:00:00-04:00[America/New_York]console.log(after24Hours.toString());
// 2026-03-08T13:00:00-04:00[America/New_York]
Les calculs calendaires maintiennent l’heure indiquée par une montre murale à midi. Les calculs basés sur le temps écoulé comptent exactement 24 heures, et comme l’heure avance d’une heure pendant la nuit, on obtient 13h.
Chacune de ces méthodes convient à des besoins différents. Un rendez-vous quotidien signifie généralement « à la même heure locale demain », il faut donc ajouter des jours. Un jeton ou une réservation qui expire exactement après 24 heures doit faire l’objet d’un ajout d’heures. Temporal peut calculer les deux cas ; il vous faut déterminer quel sens votre fonctionnalité nécessite, et il est utile de noter cette décision.
6. Rejeter les dates invalides et les heures locales inexistantes
Lors de la validation des entrées de formulaire, une date impossible doit provoquer une erreur, et non se transformer silencieusement en une autre date. Pour les entrées d’objet, demandez un traitement strict avec overflow: "reject":
try {
Temporal.PlainDate.from(
{ year: 2026, month: 2, day: 30 },
{ overflow: "reject" }
);
} catch (error) {
if (!(error instanceof RangeError)) throw error;
console.log("Please choose a valid calendar date.");
}
Sans cette option, l’objet { year: 2026, month: 2, day: 30 } serait limité au 28 février. Les chaînes ISO se comportent différemment : la parsing de "2026-02-30" est rejetée immédiatement au lieu d’être corrigée. Ne supposez pas que la parsing de chaînes et la création d’objets présentent le même comportement en cas de dépassement.
Une date valide peut encore être associée à une heure locale qui n’existe pas. Lorsque New York avance ses horloges le 8 mars 2026, l’heure 2h30 du matin est complètement ignorée. L’option disambiguation contrôle ce qui se passe avec de telles entrées :
try {
Temporal.ZonedDateTime.from(
"2026-03-08T02:30[America/New_York]",
{ disambiguation: "reject" }
);
} catch (error) {
if (!(error instanceof RangeError)) throw error;
console.log("Choose a time that exists and is unambiguous.");
}
Avec disambiguation: "reject", Temporal génère une erreur tant pour les heures manquantes que pour celles qui se répètent, c’est-à-dire l’heure qui apparaît deux fois lors du décalage des horloges. Les autres paramètres choisissent automatiquement une interprétation. Pour un formulaire de réservation, demander à l’utilisateur de sélectionner une heure valide est généralement plus clair que de modifier son rendez-vous sans lui en informer.
7. Comparer, mesurer et trier les dates de manière explicite
Pour des comparaisons chronologiques, utilisez les méthodes propres au type. Commencez par deux dates :
const startDate = Temporal.PlainDate.from("2026-10-15");
const endDate = Temporal.PlainDate.from("2026-10-20");
Temporal.PlainDate.compare() renvoie -1, 0 ou 1 selon que le premier argument est antérieur, égal ou postérieur. until() renvoie une Temporal.Duration, qui s’affiche au format de durée ISO 8601 ; ainsi, cinq jours s’expriment sous la forme P5D:
console.log(Temporal.PlainDate.compare(startDate, endDate));
// -1const difference = startDate.until(endDate, {
largestUnit: "days",
});console.log(difference.days); // 5
console.log(difference.toString()); // P5D
Pour PlainDate, la différence est mesurée en jours calendaires. Il ne calcule pas les heures écoulées dans un fuseau horaire donné, et il ne décide pas non plus si votre produit compte à la fois le jour de début et celui de fin ; c’est à vous de définir cette règle liée à l’erreur de +1.
Puisque compare a la signature attendue par Array.prototype.sort, il peut être passé directement. Pour une liste de dates non triée :
const dates = [
"2026-11-03",
"2026-10-15",
"2026-10-20",
].map(value => Temporal.PlainDate.from(value));
le tri devient une seule ligne de code :
dates.sort(Temporal.PlainDate.compare);console.log(dates.map(date => date.toString()).join(", "));
// 2026-10-15, 2026-10-20, 2026-11-03
Évitez les habitudes héritées de Date. Soustraire des objets Temporal ou les comparer avec < et > provoque des erreurs pour ces types, ce qui est intentionnel : cela empêche les comparaisons accidentelles de mauvais type. De plus, === vérifie l’identité des objets, donc deux dates égales créées séparément ne sont pas === ; utilisez .equals() pour vérifier l’égalité des valeurs. Pour découvrir d’autres manières subtiles par lesquelles de vieilles habitudes réapparaissent, consultez notre guide sur les pièges de l’API Temporal qui réintroduisent des erreurs liées aux dates.
Choisissez le type avant d’écrire le calcul
La plupart des erreurs liées aux dates sont en réalité des erreurs de modélisation, donc déterminez d’abord ce que signifie une valeur :
- Une date sans heure ni fuseau horaire :
PlainDate - Une heure sans date ni fuseau horaire :
PlainTime
PlainDateTimeInstantZonedDateTimeDurationCe choix mérite d’être discuté lors de la conception d’un contrat API ou d’une colonne de base de données. Un champ simplement nommé date peut prêter à confusion quant au fait qu’il contienne une date calendrier, une timestamp UTC ou une heure locale.
La migration n’a pas besoin d’être faite d’un seul coup. Choisissez une fonctionnalité aux exigences claires, définez son format d’entrée, son type temporel, sa politique de fuseau horaire et la sortie attendue, puis ajoutez des cas de test pour les fins de mois et les changements d’heure le cas échéant. Le code relatif aux dates de travail peut rester inchangé tant qu’il n’y a pas de raison de le modifier.
Liste de contrôle avant production
- Vérifiez le support temporel dans chaque environnement vers lequel vous déploiez votre application, et si vous utilisez un polyfill, prenez en compte le coût de son téléchargement et de sa mise en forme dans votre budget de bundle.
- Convenez d’un format de sérialisation avec votre backend. Une date simple, un instant en UTC et une chaîne contenant un identifiant de zone entre crochets représentent trois formats différents.
- Rédigez clairement les règles métier : peut-on ajuster la date pour « un mois plus tard » ? Faut-il rejeter les horaires d’appointment ambigus ? Une durée désigne-t-elle des jours calendaires ou des heures écoulées ?
Conclusion
La principale contribution de Temporal n’est pas l’existence d’une interface API plus agréable, mais plutôt l’établissement de distinctions que Date avait flouées : dates contre instants, offsets contre zones horaires, arithmétique calendulaire contre temps écoulé. Il fournit des outils précis pour chaque cas, y compris des modes stricts qui signalent clairement une erreur au lieu de deviner. Ce qu’il ne peut pas faire, c’est choisir vos politiques. Déterminez ces règles en connaissance de cause, encodez-les dans les types et options que vous transmettez, et couvrez les cas limites par des tests.