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:
- Analizuj zarejestrowane sygnatury czasowe jako
Temporal.Instant: użyjTemporal.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. - Sortowanie zdarzeń chronologicznie: użyj
Temporal.Instant.compare(a, b)jako komparatora przekazywanego doArray.prototype.sort()lubArray.prototype.toSorted(). Porównuje pełną wartość nanosekund, dzięki czemu zachowywana jest kolejność poniżej milisekundy. - Obliczanie interwałów: użyj funkcji
instant.since(previousInstant), aby uzyskaćTemporal.Durationmiędzy kolejnymi zdarzeniami. Użyja.equals(b), aby wykrywać zdarzenia z identycznymi sygnaturami czasowymi. - Wyświetlanie interwałów za pomocą
toLocaleString(): sformatujTemporal.Durationdla użytkowników za pomocąduration.toLocaleString(), który przekazuje dane doIntl.DurationFormat. Nie konwertuj na pojedynczą wartośćNumberw nanosekundach do wyświetlenia. - Serializacja na potrzeby transmisji: użyj
Temporal.Instant.prototype.toString(), aby wygenerować standardowy ciąg znaków ISO 8601 (zawsze w czasie UTC z przyrostkiemZ).
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 doTemporal.PlainDateTimeTemporal.Instantreprezentuje 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żywajperformance.now()/performance.mark()w przypadku lokalnych interwałów klienta poniżej milisekundy iTemporal.Instant.from()do analizowania sygnatur czasowych w nanosekundach z serwera lub zegarów natywnych. - UŻYWAJ
Temporal.Instant.compare()do sortowania ia.equals(b)do sprawdzania równości. Nie sprawdzajTemporal.Instant.compare(a, b) === 0ani nie porównuj danych wyjściowych.toString(). - NIE analizuj ciągów znaków sygnatur czasowych o wysokiej precyzji za pomocą
new Date()aniDate.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 przekraczaNumber.MAX_SAFE_INTEGERw przypadku okresów dłuższych niż ok. 104 dni i bez powiadomienia traci precyzję. OdejmowanieepochNanoseconds(BigInt) jest dokładne, ale odrzuca strukturę jednostki. Zachowaj interwały jakoTemporal.Durationi sformatuj je za pomocą.toLocaleString(). - ZALECENIE: przekonwertuj
InstantnaTemporal.ZonedDateTimeza 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();
})();