Trier des codes temporels de haute précision avec Temporal.Instant

Fonctionnalités Web : Temporal

Les visionneuses de journaux, les chronologies d'audit et les interfaces utilisateur de télémétrie ingèrent souvent des événements dont les codes temporels ISO 8601 ont été enregistrés avec une précision de l'ordre de la microseconde ou de la nanoseconde par un serveur, une base de données ou une horloge native (par exemple, 2026-04-01T12:00:00.000123456Z). Étant donné que l'ancien objet Date ne stocke que les millisecondes, l'analyse de ces chaînes avec new Date() ou Date.parse() tronque silencieusement les chiffres inférieurs à la milliseconde, ce qui entraîne la collision d'événements distincts au cours de la même milliseconde et leur tri dans un ordre instable.

Temporal.Instant représente un point exact sur la chronologie UTC avec une précision à la nanoseconde et sans fuseau horaire associé. Lors de l'analyse de codes temporels haute précision préenregistrés avec Temporal.Instant.from(), il conserve la valeur complète en nanosecondes, trie de manière déterministe avec Temporal.Instant.compare() et calcule les intervalles Temporal.Duration exacts entre les événements. Notez que dans les navigateurs, Temporal.Now.instant() est délibérément limité à une résolution en millisecondes ou en microsecondes pour atténuer les attaques temporelles Spectre. Utilisez Temporal.Instant.from() pour conserver les codes temporels en nanosecondes produits par des horloges externes, et utilisez performance.now() pour mesurer localement des intervalles inférieurs à la milliseconde dans le client.

Implémentation

Pour analyser, trier et mesurer les intervalles entre des codes temporels de haute précision :

  1. Analyser les codes temporels enregistrés en tant que Temporal.Instant : utilisez Temporal.Instant.from(isoString) sur la chaîne de code temporel fournie par le serveur, la base de données ou la ligne de journal. Conservez le code temporel enregistré par la source. Ne le remplacez pas par Temporal.Now.instant() dans le navigateur.
  2. Trier les événements par ordre chronologique : utilisez Temporal.Instant.compare(a, b) comme comparateur transmis à Array.prototype.sort() ou Array.prototype.toSorted(). Il compare la valeur complète en nanosecondes afin de préserver l'ordre des sous-millisecondes.
  3. Calculer des intervalles : utilisez instant.since(previousInstant) pour obtenir un Temporal.Duration entre des événements consécutifs. Utilisez a.equals(b) pour détecter les événements ayant des codes temporels identiques.
  4. Afficher les intervalles avec toLocaleString() : formatez l'Temporal.Duration pour les utilisateurs avec duration.toLocaleString(), qui délègue à Intl.DurationFormat. Ne convertissez pas en un seul Number de nanosecondes pour l'affichage.
  5. Sérialiser pour la transmission : utilisez Temporal.Instant.prototype.toString() pour émettre une chaîne ISO 8601 standard (toujours au format UTC avec un suffixe Z).

Exemple de code : Trier des codes temporels de haute précision

// Events received with nanosecond-precision ISO 8601 timestamps.
const rawEvents = [
  { nodeId: 'api-2', eventType: 'db.query', ts: '2026-04-01T12:00:00.000123456Z' },
  { nodeId: 'api-1', eventType: 'request.start', ts: '2026-04-01T12:00:00.000120000Z' },
  { nodeId: 'api-2', eventType: 'request.end', ts: '2026-04-01T12:00:00.000987654Z' },
];

// 1. Parse recorded timestamps as Temporal.Instant (never `new Date(ts)`,
// which drops everything below the millisecond).
const events = rawEvents.map(evt => ({
  ...evt,
  timestamp: Temporal.Instant.from(evt.ts),
}));

// 2. Sort chronologically with the static compare() method
function sequenceEvents(items) {
  // Instant.compare returns -1, 0, or 1, which is what sort() expects.
  return [...items].sort((a, b) => Temporal.Instant.compare(a.timestamp, b.timestamp));
}

// 3. Calculate and display the interval between consecutive events
function analyzeTrace(sortedEvents) {
  for (let i = 1; i < sortedEvents.length; i++) {
    const prev = sortedEvents[i - 1];
    const curr = sortedEvents[i];

    // since() returns a Temporal.Duration that retains nanosecond precision.
    const gap = curr.timestamp.since(prev.timestamp);

    // 4. toLocaleString() formats the duration for people (e.g., "3μs 456ns").
    // Avoid gap.total('nanoseconds'): spans over ~104 days exceed Number.MAX_SAFE_INTEGER.
    // 'en-US' is used here for deterministic output; pass undefined to use the user's locale.
    console.log(`${prev.eventType} → ${curr.eventType}: ${gap.toLocaleString('en-US', { style: 'narrow' })}`);
  }
}

analyzeTrace(sequenceEvents(events));

Implémentation stratégique et bonnes pratiques

  • À FAIRE : Utilisez Temporal.Instant.from() pour analyser les codes temporels de haute précision enregistrés sur une seule horloge (comme un serveur, une base de données ou un pipeline de journaux) qui doivent être ordonnés ou différenciés. Contrairement à Temporal.PlainDateTime, Temporal.Instant représente un point sans ambiguïté sur la chronologie universelle.
  • À FAIRE : Conservez le code temporel enregistré par la source. Dans les navigateurs, Temporal.Now.instant() est limité à une précision de l'ordre de la milliseconde/microseconde pour atténuer les attaques par canal auxiliaire Spectre et l'empreinte numérique. Utilisez performance.now() / performance.mark() pour les intervalles client locaux inférieurs à la milliseconde, et Temporal.Instant.from() pour analyser les codes temporels en nanosecondes à partir du serveur ou des horloges natives.
  • À FAIRE : Utilisez Temporal.Instant.compare() pour le tri et a.equals(b) pour les vérifications d'égalité. Ne vérifiez pas Temporal.Instant.compare(a, b) === 0 et ne comparez pas la sortie .toString().
  • NE PAS analyser les chaînes de code temporel de haute précision avec new Date() ou Date.parse() pour le tri, car elles sont tronquées en millisecondes et produisent des collisions.
  • NE RÉDUISEZ PAS un intervalle à un seul nombre pour l'affichage. duration.total('nanoseconds') renvoie un Number, qui dépasse Number.MAX_SAFE_INTEGER pour les périodes de plus de 104 jours environ et perd silencieusement en précision. La soustraction de epochNanoseconds (un BigInt) est exacte, mais supprime la structure des unités. Conservez les intervalles sous la forme Temporal.Duration et formatez-les avec .toLocaleString().
  • À FAIRE : Convertissez un Instant en Temporal.ZonedDateTime avec instant.toZonedDateTimeISO(timeZone) lorsque vous devez afficher l'heure locale d'un événement pour un utilisateur.

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();
})();