การรองรับระบบปฏิทินทั่วโลกด้วย Temporal

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

ออบเจ็กต์ Date ของ JavaScript แบบดั้งเดิมจะอิงตามปฏิทินเกรกอเรียนแบบย้อนหลัง ทำให้การสร้างแอปพลิเคชันสำหรับผู้ใช้ที่ใช้ระบบปฏิทินอื่นๆ เช่น ปฏิทินอิสลาม (จันทรคติ) ปฏิทินฮีบรู (สุริยจันทรคติ) หรือปฏิทินจีน (สุริยจันทรคติ) เป็นเรื่องที่ท้าทาย ก่อนหน้านี้ นักพัฒนาแอปต้องอาศัยไลบรารีของบุคคลที่สามที่ซับซ้อนหรือการคำนวณด้วยตนเองเพื่อรองรับระบบเหล่านี้

Temporal API รองรับระบบปฏิทินหลายระบบอย่างเต็มรูปแบบ การเชื่อมโยงตัวระบุปฏิทินกับออบเจ็กต์วันที่ทำให้ Temporal จัดการการคำนวณและการจัดรูปแบบที่ซับซ้อนซึ่งจำเป็นสำหรับบริบททางวัฒนธรรมที่แตกต่างกันได้โดยตรง

วิธีการนำไปใช้งาน

วิธีรองรับระบบปฏิทินทั่วโลกโดยใช้ Temporal

  1. เชื่อมโยงปฏิทิน (บังคับ): เมื่อสร้างหรือแปลงออบเจ็กต์ Temporal (เช่น Temporal.PlainDate) คุณต้องระบุระบบปฏิทินที่ต้องการโดยใช้ withCalendar() เพื่อดำเนินการที่ขึ้นอยู่กับปฏิทิน
  2. ใช้ตัวระบุที่เสถียร (จำเป็นสำหรับปฏิทินสุริยจันทรคติ): คุณต้องใช้ monthCode แทนดัชนี month ที่เป็นตัวเลขเพื่อระบุเดือนที่เฉพาะเจาะจงในแต่ละปีในปฏิทินสุริยจันทรคติ ใช้ตัวเลือกนี้กับปฏิทินที่ใช้อธิกมาสเท่านั้น เช่น ปฏิทินฮีบรูและปฏิทินจีน
  3. เคารพค่าคงที่ของปฏิทิน (บังคับ): เมื่อวนซ้ำผ่านเดือนหรือวัน คุณต้องไม่ถือว่าค่าคงที่ (เช่น 12 เดือนในปีหรือ 31 วันในเดือน) ใช้พร็อพเพอร์ตี้อย่าง monthsInYear และ daysInMonth เพื่อให้โค้ดทำงานได้ในปฏิทินทั้งหมด
  4. ใช้การเปรียบเทียบที่คำนึงถึงปฏิทิน (บังคับ): เมื่อเปรียบเทียบวันที่ภายในระบบปฏิทินที่เฉพาะเจาะจง คุณต้องใช้ Temporal.PlainDate.compare() แทนการเปรียบเทียบพร็อพเพอร์ตี้ปี/เดือน/วันด้วยตนเอง ซึ่งจะจัดการการเรียงลำดับตามเวลาภายในกฎของปฏิทินได้อย่างถูกต้อง

โค้ดตัวอย่าง: การแปลงและการทำซ้ำปฏิทิน

// 1. Helper to check calendar support
function isCalendarSupported(calendarId) {
  try {
    return Intl.supportedValuesOf('calendar').includes(calendarId);
  } catch {
    // Fallback for environments where supportedValuesOf is not available
    return false;
  }
}

// 2. Get current date in default ISO 8601 calendar
const isoDate = Temporal.Now.plainDateISO();

// 3. Convert to Hebrew calendar if supported
const calendarId = 'hebrew';
const targetDate = isCalendarSupported(calendarId) 
  ? isoDate.withCalendar(calendarId)
  : isoDate; // Fallback to ISO if not supported

if (targetDate.calendar.id !== calendarId) {
  console.warn(`Calendar ${calendarId} not supported; falling back to ISO 8601`);
}

// 4. Log properties specific to the calendar
console.log(`Calendar: ${targetDate.calendar.id}`);
console.log(`Year: ${targetDate.year}`);
console.log(`Month Code: ${targetDate.monthCode}`); // Stable across leap years

// 5. Safely iterate through months in the current year
for (let m = 1; m <= targetDate.monthsInYear; m++) {
  console.log(`Month ${m} has ${targetDate.with({ month: m }).daysInMonth} days.`);
}

// 6. Compare dates within the same calendar
const today = Temporal.Now.plainDateISO().withCalendar(calendarId);
const comparison = Temporal.PlainDate.compare(targetDate, today);
const relative = comparison < 0 ? 'Past' : comparison > 0 ? 'Future' : 'Today';
console.log(`Timeline: ${relative}`);

// 7. Format for display using toLocaleString (including era for era-based calendars like 'japanese')
const localizedDisplay = targetDate.toLocaleString('en-u-ca-hebrew', {
  day: 'numeric',
  month: 'long',
  year: 'numeric',
  era: 'long'
});

การนำไปใช้เชิงกลยุทธ์และแนวทางปฏิบัติแนะนำ

  • ควรใช้ monthsInYear เป็นขอบเขตบนเมื่อวนซ้ำเดือน แทนที่จะถือว่าเป็น 12
  • ควรใช้ monthCode เพื่อระบุเดือนที่เฉพาะเจาะจงในปฏิทินที่ใช้อธิกมาส (เช่น ปฏิทินฮีบรูหรือปฏิทินจีน) โดยไม่คำนึงถึงปี
  • อย่าคิดว่า date.month === 12 เป็นเดือนสุดท้ายของปี ใช้ date.month === date.monthsInYear
  • อย่าคิดว่า inLeapYear === true หมายถึงปีนั้นมีจำนวนวันมากกว่าปกติเพียง 1 วัน ในปฏิทินสุริยจันทรคติ อาจมีการเพิ่มเดือนอธิกมาสเต็มเดือน
  • ควรใช้ Temporal.PlainDate.compare() เมื่อเปรียบเทียบวันที่ 2 วันในระบบปฏิทินที่เฉพาะเจาะจง แทนการเปรียบเทียบพร็อพเพอร์ตี้ปี/เดือน/วันด้วยตนเอง
  • ควรใช้ toLocaleString() เพื่อจัดรูปแบบวันที่สำหรับผู้ใช้แทนการเชื่อมโยงสตริงด้วยตนเอง
  • ควรตรวจสอบว่าสภาพแวดล้อมที่ใช้ Intl.supportedValuesOf('calendar') รองรับระบบปฏิทินเป้าหมายก่อนสร้างออบเจ็กต์ Temporal ที่เจาะจงปฏิทิน
  • ควรทราบว่าปฏิทินบางประเภท (เช่น ปฏิทินอิสลามรูปแบบต่างๆ) อาจอาศัยการสังเกตด้วยสายตามากกว่าการคำนวณที่แน่นอน Temporal API จะเป็นไปตามการใช้งาน Intl ของสภาพแวดล้อม ซึ่งมักจะใช้ค่าประมาณที่คำนวณได้ สำหรับการคำนวณวันที่ทางวัฒนธรรมหรือศาสนาที่สำคัญ ให้ตรวจสอบกับผู้เชี่ยวชาญเฉพาะด้านหรือใช้ไลบรารีเฉพาะทาง
  • ควรพิจารณาชื่อยุคเมื่อใช้ปฏิทินที่ใช้ยุค (เช่น ญี่ปุ่น พุทธศักราช) โดยใช้ toLocaleString()

กลยุทธ์สำรอง

เบราว์เซอร์ที่รองรับและทางเลือกสำรองสำหรับ 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();
})();