Средства просмотра журналов, временные шкалы аудита и интерфейсы телеметрии часто принимают события, временные метки ISO 8601 которых были записаны с точностью до микросекунды или наносекунды сервером, базой данных или нативными часами (например, 2026-04-01T12:00:00.000123456Z). Поскольку устаревший объект Date хранит только миллисекунды, синтаксический анализ этих строк с помощью new Date() или Date.parse() автоматически усекает цифры меньше миллисекунды, в результате чего отдельные события в пределах одной миллисекунды сталкиваются и сортируются в нестабильном порядке.
Temporal.Instant представляет точную точку на временной шкале UTC с точностью до наносекунды и без привязки к часовому поясу. При анализе предварительно записанных высокоточных временных меток с помощью Temporal.Instant.from() сохраняется полное значение в наносекундах, выполняется детерминированная сортировка с помощью Temporal.Instant.compare() и вычисляются точные интервалы Temporal.Duration между событиями. Обратите внимание, что в браузерах Temporal.Now.instant() намеренно ограничено разрешением в миллисекундах или микросекундах для защиты от атак по времени Spectre. Используйте Temporal.Instant.from(), чтобы сохранить временные метки в наносекундах, созданные внешними часами, и performance.now(), чтобы измерять интервалы менее миллисекунды локально на клиенте.
Как реализовать
Чтобы анализировать, сортировать и измерять интервалы между высокоточными временными метками, выполните следующие действия:
- Обработать записанные временные метки как
Temporal.Instant. ИспользуйтеTemporal.Instant.from(isoString)для строки временной метки, предоставленной сервером, базой данных или строкой журнала. Сохранять временную метку, записанную источником, а не заменять ее наTemporal.Now.instant()в браузере. - Сортировка событий в хронологическом порядке. Используйте
Temporal.Instant.compare(a, b)в качестве компаратора, передаваемого вArray.prototype.sort()илиArray.prototype.toSorted(). Сравнение выполняется по полному значению в наносекундах, поэтому порядок событий с точностью до миллисекунды сохраняется. - Вычисление интервалов. Используйте
instant.since(previousInstant), чтобы получитьTemporal.Durationмежду последовательными событиями. Используйтеa.equals(b), чтобы обнаруживать события с одинаковыми временными метками. - Отображение интервалов с помощью
toLocaleString(). ФорматируйтеTemporal.Durationдля пользователей с помощьюduration.toLocaleString(), который делегируетIntl.DurationFormat. Не преобразуйте в единое значениеNumberнаносекунд для отображения. - Сериализация для передачи. Используйте
Temporal.Instant.prototype.toString(), чтобы получить стандартную строку ISO 8601 (всегда в формате UTC с суффиксомZ).
Пример кода: сортировка высокоточных временных меток
// 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));
Стратегическое внедрение и рекомендации
- РЕКОМЕНДУЕТСЯ использовать
Temporal.Instant.from()для обработки высокоточных временных меток, записанных по одним часам (например, на сервере, в базе данных или конвейере журналов), которые необходимо упорядочить или сравнить. В отличие отTemporal.PlainDateTime,Temporal.Instantпредставляет собой однозначную точку на универсальной временной шкале. - РЕКОМЕНДУЕТСЯ сохранять временную метку, записанную источником. В браузерах точность
Temporal.Now.instant()ограничена миллисекундами/микросекундами для защиты от атак по времени Spectre и цифровых отпечатков. Используйтеperformance.now()/performance.mark()для локальных интервалов клиента менее миллисекунды иTemporal.Instant.from()для анализа наносекундных временных меток с сервера или нативных часов. - ИСПОЛЬЗУЙТЕ
Temporal.Instant.compare()для сортировки иa.equals(b)для проверки равенства. Не проверяйтеTemporal.Instant.compare(a, b) === 0и не сравнивайте выходные данные.toString(). - НЕ выполняйте синтаксический анализ строк высокоточных временных меток с помощью
new Date()илиDate.parse()для упорядочивания, поскольку они усекаются до миллисекунд и вызывают конфликты. - НЕ СЛЕДУЕТ сокращать интервал до одного числа.
duration.total('nanoseconds')возвращаетNumber, что превышаетNumber.MAX_SAFE_INTEGERдля периодов дольше примерно 104 дней и приводит к потере точности. ВычитаниеepochNanoseconds(BigInt) дает точный результат, но отбрасывает структуру единиц. Сохраняйте интервалы какTemporal.Durationи форматируйте их с помощью.toLocaleString(). - РЕКОМЕНДУЕТСЯ преобразовывать
InstantвTemporal.ZonedDateTimeс помощьюinstant.toZonedDateTimeISO(timeZone), когда нужно показать пользователю локализованное время события.
Резервная стратегия
Резервные варианты и поддержка Temporal в браузерах
Для браузеров, которые пока не поддерживают нативный API Temporal, используйте обнаружение функций и динамически загружайте temporal-polyfill (около 15 КБ min+gz).
Обратите внимание, что импорт temporal-polyfill не приводит к автоматическому назначению объекта Temporal глобальной области видимости. Если ваш код зависит от 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();
})();