การจัดเรียงการประทับเวลาที่มีความแม่นยำสูงด้วย Temporal.Instant

ฟีเจอร์บนเว็บ: Temporal

ผู้ดูบันทึก ไทม์ไลน์การตรวจสอบ และ UI การตรวจวัดและส่งข้อมูลทางไกลมักจะนำเข้าเหตุการณ์ที่มีการประทับเวลา 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.Instant แสดงถึงจุดที่ชัดเจนในไทม์ไลน์สากล ซึ่งแตกต่างจาก Temporal.PlainDateTime
  • ควรเก็บการประทับเวลาที่บันทึกโดยแหล่งที่มาไว้ ในเบราว์เซอร์ 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

สำหรับเบราว์เซอร์ที่ยังไม่รองรับ Temporal API แบบเนทีฟ ให้ใช้การตรวจหาฟีเจอร์และโหลด temporal-polyfill แบบไดนามิก (~15 KB 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();
})();