Сортировка высокоточных временных меток с помощью Temporal.Instant

Веб-функции: Temporal

Средства просмотра журналов, временные шкалы аудита и интерфейсы телеметрии часто принимают события, временные метки 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(), чтобы измерять интервалы менее миллисекунды локально на клиенте.

Как реализовать

Чтобы анализировать, сортировать и измерять интервалы между высокоточными временными метками, выполните следующие действия:

  1. Обработать записанные временные метки как Temporal.Instant. Используйте Temporal.Instant.from(isoString) для строки временной метки, предоставленной сервером, базой данных или строкой журнала. Сохранять временную метку, записанную источником, а не заменять ее на Temporal.Now.instant() в браузере.
  2. Сортировка событий в хронологическом порядке. Используйте Temporal.Instant.compare(a, b) в качестве компаратора, передаваемого в Array.prototype.sort() или Array.prototype.toSorted(). Сравнение выполняется по полному значению в наносекундах, поэтому порядок событий с точностью до миллисекунды сохраняется.
  3. Вычисление интервалов. Используйте instant.since(previousInstant), чтобы получить Temporal.Duration между последовательными событиями. Используйте a.equals(b), чтобы обнаруживать события с одинаковыми временными метками.
  4. Отображение интервалов с помощью toLocaleString(). Форматируйте Temporal.Duration для пользователей с помощью duration.toLocaleString(), который делегирует Intl.DurationFormat. Не преобразуйте в единое значение Number наносекунд для отображения.
  5. Сериализация для передачи. Используйте 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();
})();