Sortowanie sygnatur czasowych o wysokiej precyzji za pomocą Temporal.Instant

Funkcje internetowe: Temporal

Przeglądarki logów, osie czasu kontroli i interfejsy telemetryczne często przetwarzają zdarzenia, których sygnatury czasowe ISO 8601 zostały zarejestrowane z dokładnością do mikrosekund lub nanosekund przez serwer, bazę danych lub zegar natywny (np. 2026-04-01T12:00:00.000123456Z). Ponieważ starszy obiekt Date przechowuje tylko milisekundy, analizowanie tych ciągów znaków za pomocą new Date() lub Date.parse() powoduje ciche obcinanie cyfr poniżej milisekundy, co powoduje kolizję różnych zdarzeń w tej samej milisekundzie i sortowanie ich w niestabilnej kolejności.

Temporal.Instant reprezentuje dokładny punkt na osi czasu UTC z dokładnością do nanosekundy i bez dołączonej strefy czasowej. Podczas analizowania wstępnie zarejestrowanych sygnatur czasowych o wysokiej precyzji za pomocą Temporal.Instant.from() zachowuje pełną wartość nanosekund, sortuje deterministycznie za pomocą Temporal.Instant.compare() i oblicza dokładne interwały Temporal.Duration między zdarzeniami. Pamiętaj, że w przeglądarkach Temporal.Now.instant() jest celowo ograniczany do rozdzielczości milisekundowej lub mikrosekundowej, aby zapobiec atakom Spectre na czas. Używaj Temporal.Instant.from(), aby zachować sygnatury czasowe w nanosekundach generowane przez zegary zewnętrzne, i używaj performance.now(), gdy mierzysz interwały poniżej milisekundy lokalnie w kliencie.

Jak to zrobić?

Aby analizować, sortować i mierzyć interwały między sygnaturami czasowymi o wysokiej precyzji:

  1. Analizuj zarejestrowane sygnatury czasowe jako Temporal.Instant: użyj Temporal.Instant.from(isoString) w ciągu sygnatury czasowej dostarczonym przez serwer, bazę danych lub wiersz dziennika. Zachowaj sygnaturę czasową zarejestrowaną przez źródło. Nie zastępuj jej wartością Temporal.Now.instant() w przeglądarce.
  2. Sortowanie zdarzeń chronologicznie: użyj Temporal.Instant.compare(a, b) jako komparatora przekazywanego do Array.prototype.sort() lub Array.prototype.toSorted(). Porównuje pełną wartość nanosekund, dzięki czemu zachowywana jest kolejność poniżej milisekundy.
  3. Obliczanie interwałów: użyj funkcji instant.since(previousInstant), aby uzyskać Temporal.Duration między kolejnymi zdarzeniami. Użyj a.equals(b), aby wykrywać zdarzenia z identycznymi sygnaturami czasowymi.
  4. Wyświetlanie interwałów za pomocą toLocaleString(): sformatuj Temporal.Duration dla użytkowników za pomocą duration.toLocaleString(), który przekazuje dane do Intl.DurationFormat. Nie konwertuj na pojedynczą wartość Number w nanosekundach do wyświetlenia.
  5. Serializacja na potrzeby transmisji: użyj Temporal.Instant.prototype.toString(), aby wygenerować standardowy ciąg znaków ISO 8601 (zawsze w czasie UTC z przyrostkiem Z).

Przykładowy kod: sortowanie sygnatur czasowych o wysokiej precyzji

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

Strategiczne wdrażanie i sprawdzone metody

  • UŻYWAJ typu Temporal.Instant.from() do analizowania sygnatur czasowych o wysokiej precyzji, które są rejestrowane przez jeden zegar (np. serwer, bazę danych lub potok logów) i które muszą być uporządkowane lub rozróżnione. W przeciwieństwie do Temporal.PlainDateTime Temporal.Instant reprezentuje jednoznaczny punkt na uniwersalnej osi czasu.
  • ZACHOWAJ sygnaturę czasową zarejestrowaną przez źródło. W przeglądarkach wartość Temporal.Now.instant() jest ograniczona do milisekund lub mikrosekund, aby zapobiegać atakom czasowym Spectre i pobieraniu odcisków cyfrowych. Używaj performance.now() / performance.mark() w przypadku lokalnych interwałów klienta poniżej milisekundy i Temporal.Instant.from() do analizowania sygnatur czasowych w nanosekundach z serwera lub zegarów natywnych.
  • UŻYWAJ Temporal.Instant.compare() do sortowania i a.equals(b) do sprawdzania równości. Nie sprawdzaj Temporal.Instant.compare(a, b) === 0 ani nie porównuj danych wyjściowych .toString().
  • NIE analizuj ciągów znaków sygnatur czasowych o wysokiej precyzji za pomocą new Date() ani Date.parse() w celu porządkowania, ponieważ są one obcinane do milisekund i powodują kolizje.
  • NIE zwijaj przedziału do pojedynczej liczby na potrzeby wyświetlania. duration.total('nanoseconds') zwraca wartość Number, która przekracza Number.MAX_SAFE_INTEGER w przypadku okresów dłuższych niż ok. 104 dni i bez powiadomienia traci precyzję. Odejmowanie epochNanoseconds (BigInt) jest dokładne, ale odrzuca strukturę jednostki. Zachowaj interwały jako Temporal.Duration i sformatuj je za pomocą .toLocaleString().
  • ZALECENIE: przekonwertuj Instant na Temporal.ZonedDateTime za pomocą instant.toZonedDateTimeISO(timeZone), gdy chcesz wyświetlić użytkownikowi zlokalizowany czas zegarowy wydarzenia.

Strategia zastępcza

Zastępcze rozwiązania i obsługa Temporal w przeglądarkach

W przypadku przeglądarek, które nie obsługują jeszcze natywnego interfejsu API Temporal, użyj wykrywania funkcji i dynamicznie wczytaj temporal-polyfill (~15 KB min+gz).

Pamiętaj, że zaimportowanie temporal-polyfill nie powoduje automatycznego przypisania obiektu Temporal do zakresu globalnego. Przypisz go bezpośrednio, jeśli Twój kod opiera się na 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();
})();