Mengurutkan Stempel Waktu Presisi Tinggi dengan Temporal.Instant

Fitur web: Temporal

Penampil log, linimasa audit, dan UI telemetri sering kali menyerap peristiwa yang stempel waktu ISO 8601-nya dicatat dengan presisi mikrodetik atau nanodetik oleh server, database, atau jam native (misalnya, 2026-04-01T12:00:00.000123456Z). Karena objek Date lama hanya menyimpan milidetik, penguraian string tersebut dengan new Date() atau Date.parse() akan otomatis memotong digit sub-milidetik, sehingga menyebabkan peristiwa yang berbeda dalam milidetik yang sama bertabrakan dan diurutkan dalam urutan yang tidak stabil.

Temporal.Instant mewakili titik yang tepat pada linimasa UTC dengan presisi nanodetik dan tanpa zona waktu yang disertakan. Saat mengurai stempel waktu presisi tinggi yang telah direkam sebelumnya dengan Temporal.Instant.from(), stempel waktu tersebut mempertahankan nilai nanodetik penuh, mengurutkan secara deterministik dengan Temporal.Instant.compare(), dan menghitung interval Temporal.Duration yang tepat di antara peristiwa. Perhatikan bahwa di browser, Temporal.Now.instant() sengaja dibatasi pada resolusi milidetik atau mikrodetik untuk mitigasi serangan pengaturan waktu Spectre—gunakan Temporal.Instant.from() untuk mempertahankan stempel waktu nanodetik yang dihasilkan oleh jam eksternal, dan gunakan performance.now() saat mengukur interval sub-milidetik secara lokal di klien.

Cara Menerapkan

Untuk mengurai, mengurutkan, dan mengukur interval antara stempel waktu presisi tinggi:

  1. Mengurai stempel waktu yang dicatat sebagai Temporal.Instant: Gunakan Temporal.Instant.from(isoString) pada string stempel waktu yang disediakan oleh server, database, atau baris log. Pertahankan stempel waktu yang dicatat sumber; jangan ganti dengan Temporal.Now.instant() di browser.
  2. Mengurutkan peristiwa secara kronologis: Gunakan Temporal.Instant.compare(a, b) sebagai pembanding yang diteruskan ke Array.prototype.sort() atau Array.prototype.toSorted(). Metode ini membandingkan nilai nanodetik penuh sehingga pengurutan sub-milidetik dipertahankan.
  3. Menghitung interval: Gunakan instant.since(previousInstant) untuk mendapatkan Temporal.Duration di antara peristiwa yang berurutan. Gunakan a.equals(b) untuk mendeteksi peristiwa dengan stempel waktu yang sama.
  4. Menampilkan interval dengan toLocaleString(): Format Temporal.Duration untuk pengguna dengan duration.toLocaleString(), yang mendelegasikan ke Intl.DurationFormat. Jangan konversi ke satu Number nanodetik untuk ditampilkan.
  5. Serialisasi untuk transmisi: Gunakan Temporal.Instant.prototype.toString() untuk memunculkan string ISO 8601 standar (selalu dalam UTC dengan akhiran Z).

Contoh Kode: Mengurutkan Stempel Waktu Presisi Tinggi

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

Penerapan Strategis & Praktik Terbaik

  • LAKUKAN penggunaan Temporal.Instant.from() untuk mengurai stempel waktu presisi tinggi yang dicatat terhadap satu jam (seperti server, database, atau pipeline log) yang perlu diurutkan atau dibedakan. Tidak seperti Temporal.PlainDateTime, Temporal.Instant mewakili titik yang jelas pada linimasa universal.
  • LAKUKAN penyimpanan stempel waktu yang dicatat oleh sumber. Di browser, Temporal.Now.instant() dibatasi pada presisi milidetik/mikrodetik untuk mitigasi serangan pengaturan waktu dan sidik jari Spectre; gunakan performance.now() / performance.mark() untuk interval klien sub-milidetik lokal, dan Temporal.Instant.from() untuk mengurai stempel waktu nanodetik dari server atau jam native.
  • LAKUKAN penggunaan Temporal.Instant.compare() untuk pengurutan dan a.equals(b) untuk pemeriksaan kesetaraan; jangan periksa Temporal.Instant.compare(a, b) === 0 atau bandingkan output .toString().
  • JANGAN mengurai string stempel waktu presisi tinggi dengan new Date() atau Date.parse() untuk pengurutan, karena string tersebut akan dipotong menjadi milidetik dan menghasilkan tabrakan.
  • JANGAN menciutkan interval menjadi satu angka untuk ditampilkan. duration.total('nanoseconds') menampilkan Number, yang melebihi Number.MAX_SAFE_INTEGER untuk rentang yang lebih lama dari ~104 hari dan kehilangan presisi secara otomatis. Pengurangan epochNanoseconds (BigInt) bersifat tepat, tetapi mengabaikan struktur unit; pertahankan interval sebagai Temporal.Duration dan format dengan .toLocaleString().
  • LAKUKAN konversi Instant ke Temporal.ZonedDateTime dengan instant.toZonedDateTimeISO(timeZone) saat Anda perlu menampilkan waktu jam dinding yang dilokalkan dari suatu peristiwa kepada pengguna.

Strategi Penggantian

Penggantian & dukungan browser untuk Temporal

Untuk browser yang belum mendukung Temporal API native, gunakan deteksi fitur dan muat temporal-polyfill secara dinamis (~15 KB min+gz).

Perhatikan bahwa mengimpor temporal-polyfill tidak otomatis menetapkan objek Temporal ke cakupan global. Tetapkan secara eksplisit jika kode Anda mengandalkan 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();
})();