Le calcul du temps écoulé ou restant entre des événements (comme l'expiration d'un essai, le compte à rebours d'un abonnement ou les fenêtres de SLA) et la présentation de ces périodes dans un format localisé et lisible par l'humain nécessitaient auparavant des calculs manuels ou des bibliothèques externes. L'ancien objet Date ne suit que les millisecondes d'epoch, ce qui rend les calculs de mois calendaires, les transitions d'heure d'été et l'équilibrage des unités sujets aux erreurs.
L'API Temporal associe Temporal.ZonedDateTime à Temporal.Duration et Intl.DurationFormat pour fournir des différences de temps sécurisées pour l'heure d'été, un équilibrage d'unité configurable, des comparaisons de durée tenant compte du calendrier et une mise en forme localisée.
Implémentation
- Obtenir les points de terminaison
Temporal.ZonedDateTime: utilisezTemporal.Now.zonedDateTimeISO()pour l'heure actuelle dans le fuseau horaire du système, ou transmettez une chaîne de fuseau horaire IANA explicite (Temporal.Now.zonedDateTimeISO(timeZone)) lorsque le fuseau horaire de l'événement cible diffère du paramètre système par défaut de l'utilisateur. - Calculer les différentiels avec
.since()et.until():- Utilisez
now.since(start, options)pour calculer le temps écoulé depuis un événement passé. - Utilisez
now.until(end, options)pour calculer le temps restant avant un événement futur. - Transmettez
largestUnit(par exemple,'year','month','day','hour') etsmallestUnit(par exemple,'minute') pour équilibrer et arrondir leTemporal.Durationobtenu.
- Utilisez
- Équilibrer ou comparer des objets
Temporal.Durationautonomes :- Créez des durées autonomes avec
Temporal.Duration.from({ minutes: 90 })et rééquilibrez les unités avecduration.round({ largestUnit: 'hour' })(en fournissantrelativeTolors de l'équilibrage entre les mois calendaires, les années ou les transitions d'heure d'été). - Comparez deux durées avec
Temporal.Duration.compare(d1, d2, { relativeTo }). Vous devez transmettre unPlainDateou unZonedDateTimeen tant querelativeTochaque fois que l'une des durées contient des unités de calendrier (years,months,weeks) ou franchit les limites de l'heure d'été.
- Créez des durées autonomes avec
- Format pour les humains et les machines :
- Utilisez
duration.toLocaleString(locale, options)(qui délègue àIntl.DurationFormat) pour le texte localisé visible par l'utilisateur. - Utilisez
duration.toString()uniquement pour les chaînes de durée ISO 8601 lisibles par machine (commeP1M2DT4H).
- Utilisez
Exemple : Différence d'expiration de l'essai et mise en forme localisée
// 1. Get the current time in an explicit target IANA time zone (or omit the
// argument to use the system's current time zone).
const tz = 'America/New_York';
const now = Temporal.Now.zonedDateTimeISO(tz);
// 2. Combine separate date and time strings directly.
// Do not build an ISO string with template literals; chain Temporal methods instead.
const startDateStr = '2025-01-01';
const startTimeStr = '12:00:00';
const endDateStr = '2025-01-31';
const endTimeStr = '12:00:00';
const start = Temporal.PlainDate.from(startDateStr).toPlainDateTime(startTimeStr).toZonedDateTime(tz);
const end = Temporal.PlainDate.from(endDateStr).toPlainDateTime(endTimeStr).toZonedDateTime(tz);
// 3. Calculate differences using .since() and .until()
// ZonedDateTime differences default to largestUnit: 'hour' (days are not a fixed
// length across DST, so Temporal won't balance into days unless asked). Pass
// largestUnit: 'day' or a calendar unit ('week', 'month', 'year') when you want a
// human-scale breakdown instead of e.g. PT720H. smallestUnit rounds away
// seconds and sub-second noise so the formatted string stays readable.
const timeActive = now.since(start, { largestUnit: 'year', smallestUnit: 'minute' });
const timeRemaining = now.until(end, { largestUnit: 'year', smallestUnit: 'minute' });
// 4. Format durations for display with .toLocaleString() (Intl.DurationFormat)
// instead of manually concatenating unit numbers and suffixes. 'en-US' is used
// here for deterministic output; pass undefined to use the user's locale.
console.log(`Active: ${timeActive.toLocaleString('en-US', { style: 'narrow' })}`);
console.log(`Remaining: ${timeRemaining.toLocaleString('en-US', { style: 'long' })}`);
// Use .toString() for ISO 8601 serialization (e.g., API payloads or <time datetime="...">)
console.log(`ISO 8601: ${timeRemaining.toString()}`);
// 5. Check status via .sign, and compare durations with Temporal.Duration.compare()
const isExpired = timeRemaining.sign < 0;
// Pass relativeTo when comparing durations that may include calendar months/years or DST shifts
const isPastHalfway = Temporal.Duration.compare(timeActive, timeRemaining, { relativeTo: now }) > 0;
Exemple : Équilibrer les durées autonomes (round et Intl.DurationFormat)
// Convert a raw unit count (e.g., 90 minutes) into a balanced breakdown (1 hour, 30 minutes)
const rawDuration = Temporal.Duration.from({ minutes: 90 });
const balanced = rawDuration.round({ largestUnit: 'hour' });
// Format via duration.toLocaleString() or directly with Intl.DurationFormat
const formatter = new Intl.DurationFormat('en-US', { style: 'long' });
console.log(formatter.format(balanced)); // "1 hour, 30 minutes"
Implémentation stratégique et bonnes pratiques
- À FAIRE : Utilisez
Temporal.ZonedDateTimepour les calculs impliquant des événements réels qui se produisent dans des fuseaux horaires spécifiques (comme les renouvellements d'abonnement ou les comptes à rebours d'événements), et transmettez untimeZoneexplicite àTemporal.Now.zonedDateTimeISO(timeZone)lors du calcul par rapport à un fuseau horaire autre que le paramètre système par défaut. - À FAIRE : Combinez des chaînes de date et d'heure distinctes avec des méthodes temporelles, par exemple
Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz)ouTemporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr }), au lieu d'interpoler une chaîne ISO comme`${dateStr}T${timeStr}`. - PASSEZ
largestUnit(par exemple,'day','month','year') dans.since(),.until()ou.round()lorsque vous souhaitez équilibrer les durées au-delà des heures. Les méthodes de différenceZonedDateTimesont définies par défaut sur'hour'. - À FAIRE : Utilisez
Temporal.Duration.compare(d1, d2, { relativeTo })lorsque vous comparez deux durées, en fournissant un ancragerelativeTochaque fois que l'une des durées implique des unités de calendrier (months,years) ou des jours tenant compte du fuseau horaire. - À FAIRE : Formatez les durées pour l'affichage utilisateur à l'aide de
duration.toLocaleString()ounew Intl.DurationFormat(locale, { style })('long','short','narrow'ou'digital') au lieu d'interpoler manuellement${duration.years}y ${duration.months}m.... - À FAIRE : Utilisez
duration.toString()pour les chaînes de durée ISO 8601 lisibles par machine (par exemple,PT1H30M), jamais pour le texte visible par l'utilisateur. - À FAIRE : affichez un
PlainTimesous la formeHH:MM(par exemple, pour<input type="time">) à l'aide de.toPlainTime().toString({ smallestUnit: 'minute' })plutôt que de la chaîne.slice(0, 5). - NE MODIFIEZ PAS les instances directement. Les objets
Temporalsont immuables. Les opérations telles que.add(),.subtract(),.round()ou.with()renvoient une nouvelle instance. - N'utilisez PAS l'arithmétique
Date/.getTime()pour la différence elle-même.Daten'a pas de calculs de calendrier tenant compte du fuseau horaire et n'offre qu'une précision en millisecondes. - UTILISEZ la propriété native
.signde l'élémentTemporal.Duration(ouTemporal.ZonedDateTime.compare()) calculé pour vérifier si une durée représente un point temporel passé/expiré (sign < 0) ou un démarrage en attente futur.
Stratégie de remplacement
Solutions de remplacement et compatibilité des navigateurs avec Temporal
Pour les navigateurs qui ne sont pas encore compatibles avec l'API Temporal native, utilisez la détection de fonctionnalités et chargez dynamiquement temporal-polyfill (~15 Ko min+gz).
Notez que l'importation de temporal-polyfill n'attribue pas automatiquement l'objet Temporal au champ d'application global. Attribuez-la explicitement si votre code repose sur globalThis.Temporal.
// Check if Temporal is supported natively
(async () => {
if (typeof Temporal === 'undefined') {
// Load the polyfill conditionally
const module = await import('https://esm.sh/temporal-polyfill');
globalThis.Temporal = module.Temporal;
// Extend Date.prototype if needed
Date.prototype.toTemporalInstant = module.toTemporalInstant;
}
initializeApp();
})();