Ordinamento dei timestamp ad alta precisione con Temporal.Instant

Funzionalità web: Temporal

I visualizzatori log, le cronologie di controllo e le UI di telemetria spesso importano eventi i cui timestamp ISO 8601 sono stati registrati con precisione in microsecondi o nanosecondi da un server, un database o un orologio nativo (ad esempio, 2026-04-01T12:00:00.000123456Z). Poiché l'oggetto Date legacy memorizza solo i millisecondi, l'analisi di queste stringhe con new Date() o Date.parse() tronca silenziosamente le cifre inferiori al millisecondo, causando la collisione e l'ordinamento instabile di eventi distinti nello stesso millisecondo.

Temporal.Instant rappresenta un punto esatto nella cronologia UTC con precisione al nanosecondo e senza fuso orario associato. Quando analizza i timestamp ad alta precisione preregistrati con Temporal.Instant.from(), conserva il valore completo dei nanosecondi, ordina in modo deterministico con Temporal.Instant.compare() e calcola gli intervalli Temporal.Duration esatti tra gli eventi. Tieni presente che nei browser Temporal.Now.instant() è deliberatamente limitato alla risoluzione in millisecondi o microsecondi per la mitigazione degli attacchi di temporizzazione Spectre. Utilizza Temporal.Instant.from() per conservare i timestamp in nanosecondi prodotti da orologi esterni e utilizza performance.now() quando misuri intervalli inferiori al millisecondo localmente nel client.

Modalità di implementazione

Per analizzare, ordinare e misurare gli intervalli tra timestamp ad alta precisione:

  1. Analizza i timestamp registrati come Temporal.Instant: utilizza Temporal.Instant.from(isoString) sulla stringa del timestamp fornita dal server, dal database o dalla riga di log. Mantieni il timestamp registrato dall'origine; non sostituirlo con Temporal.Now.instant() nel browser.
  2. Ordina gli eventi in ordine cronologico: utilizza Temporal.Instant.compare(a, b) come comparatore passato a Array.prototype.sort() o Array.prototype.toSorted(). Confronta il valore completo dei nanosecondi in modo da preservare l'ordine dei sottomillisecondi.
  3. Calcola gli intervalli: utilizza instant.since(previousInstant) per ottenere un Temporal.Duration tra eventi consecutivi. Utilizza a.equals(b) per rilevare gli eventi con timestamp identici.
  4. Visualizza gli intervalli con toLocaleString(): formatta Temporal.Duration per gli utenti con duration.toLocaleString(), che delega a Intl.DurationFormat. Non convertire in un singolo Number di nanosecondi per la visualizzazione.
  5. Serializza per la trasmissione: utilizza Temporal.Instant.prototype.toString() per generare una stringa ISO 8601 standard (sempre in UTC con un suffisso Z).

Esempio di codice: ordinamento dei timestamp ad alta precisione

// 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));

Implementazione strategica e best practice

  • SÌ: utilizza Temporal.Instant.from() per analizzare i timestamp ad alta precisione registrati su un singolo orologio (ad esempio un server, un database o una pipeline di log) che devono essere ordinati o differenziati. A differenza di Temporal.PlainDateTime, Temporal.Instant rappresenta un punto univoco nella cronologia universale.
  • SÌ: mantieni il timestamp registrato dall'origine. Nei browser, Temporal.Now.instant() è limitato alla precisione in millisecondi/microsecondi per la mitigazione degli attacchi di temporizzazione Spectre e del fingerprinting; utilizza performance.now()/performance.mark() per gli intervalli client locali inferiori al millisecondo e Temporal.Instant.from() per l'analisi dei timestamp in nanosecondi dai clock del server o nativi.
  • USA Temporal.Instant.compare() per l'ordinamento e a.equals(b) per i controlli di uguaglianza; non controllare Temporal.Instant.compare(a, b) === 0 o confrontare l'output di .toString().
  • NON analizzare stringhe di timestamp ad alta precisione con new Date() o Date.parse() per l'ordinamento, perché vengono troncate in millisecondi e generano collisioni.
  • NON comprimere un intervallo in un singolo numero per la visualizzazione. duration.total('nanoseconds') restituisce un Number, che supera Number.MAX_SAFE_INTEGER per intervalli superiori a circa 104 giorni e perde silenziosamente precisione. La sottrazione di epochNanoseconds (un BigInt) è esatta, ma ignora la struttura dell'unità; mantieni gli intervalli come Temporal.Duration e formattali con .toLocaleString().
  • SÌ: converti un Instant in Temporal.ZonedDateTime con instant.toZonedDateTimeISO(timeZone) quando devi mostrare a un utente l'ora locale di un evento.

Strategia di riserva

Fallback e supporto del browser per Temporal

Per i browser che non supportano ancora l'API Temporal nativa, utilizza il rilevamento delle funzionalità e carica dinamicamente temporal-polyfill (~15 kB min+gz).

Tieni presente che l'importazione di temporal-polyfill non assegna automaticamente l'oggetto Temporal all'ambito globale. Assegnalo esplicitamente se il tuo codice si basa su 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();
})();