Hỗ trợ các hệ thống lịch toàn cầu bằng Temporal

Tính năng web: Temporal

Đối tượng Date JavaScript truyền thống dựa trên lịch Gregory, khiến việc xây dựng ứng dụng cho người dùng dựa vào các hệ thống lịch khác (chẳng hạn như lịch Hồi giáo (âm lịch), lịch Do Thái (âm dương lịch) hoặc lịch Trung Quốc (âm dương lịch)) trở nên khó khăn. Trước đây, nhà phát triển phải dựa vào các thư viện bên thứ ba phức tạp hoặc tính toán thủ công để hỗ trợ các hệ thống này.

API Temporal cung cấp dịch vụ hỗ trợ hàng đầu cho nhiều hệ thống lịch. Bằng cách liên kết giá trị nhận dạng lịch với các đối tượng ngày, Temporal xử lý các phép tính và định dạng phức tạp cần thiết cho nhiều bối cảnh văn hoá khác nhau theo cách tự nhiên.

Cách triển khai

Cách hỗ trợ các hệ thống lịch toàn cầu bằng Temporal:

  1. Liên kết lịch (Bắt buộc): Khi tạo hoặc chuyển đổi một đối tượng Temporal (như Temporal.PlainDate), bạn phải chỉ định hệ thống lịch mong muốn bằng withCalendar() để thực hiện các thao tác liên quan đến lịch.
  2. Sử dụng giá trị nhận dạng ổn định (Bắt buộc đối với âm dương lịch): Bạn phải sử dụng monthCode thay vì chỉ mục số month để xác định các tháng cụ thể trong nhiều năm theo âm dương lịch. Chỉ sử dụng thuộc tính này cho những lịch có tháng nhuận, chẳng hạn như lịch Do Thái và lịch Trung Quốc.
  3. Tôn trọng các bất biến của lịch (Bắt buộc): Khi lặp lại qua các tháng hoặc ngày, bạn không được giả định các giá trị cố định (như 12 tháng trong một năm hoặc 31 ngày trong một tháng). Sử dụng các thuộc tính như monthsInYear và daysInMonth để đảm bảo mã của bạn hoạt động trên tất cả các lịch.
  4. Sử dụng phép so sánh nhận biết lịch (Bắt buộc): Khi so sánh ngày trong một hệ thống lịch cụ thể, bạn phải sử dụng Temporal.PlainDate.compare() thay vì so sánh các thuộc tính năm/tháng/ngày theo cách thủ công. Thao tác này sẽ xử lý chính xác thứ tự thời gian trong các quy tắc của lịch.

Mã ví dụ: Chuyển đổi và lặp lại lịch

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

Triển khai chiến lược và các phương pháp hay nhất

  • NÊN sử dụng monthsInYear làm giới hạn trên khi lặp qua các tháng, thay vì giả định là 12.
  • NÊN sử dụng monthCode để xác định các tháng cụ thể trong lịch sử dụng tháng nhuận (ví dụ: lịch Do Thái hoặc lịch Trung Quốc), bất kể năm nào.
  • KHÔNG giả định rằng date.month === 12 là tháng cuối cùng của năm. Sử dụng date.month === date.monthsInYear.
  • KHÔNG giả định rằng inLeapYear === true có nghĩa là năm đó chỉ dài hơn một ngày. Trong âm dương lịch, năm nhuận có thể có thêm một tháng nhuận.
  • NÊN sử dụng Temporal.PlainDate.compare() khi so sánh hai ngày trong một hệ thống lịch cụ thể thay vì so sánh các thuộc tính Năm/Tháng/Ngày theo cách thủ công.
  • NÊN sử dụng toLocaleString() để định dạng ngày cho người dùng thay vì nối chuỗi theo cách thủ công.
  • NÊN xác minh rằng hệ thống lịch mục tiêu được môi trường hỗ trợ bằng cách sử dụng Intl.supportedValuesOf('calendar') trước khi tạo các đối tượng Temporal theo lịch cụ thể.
  • NÊN lưu ý rằng một số lịch (như các biến thể của lịch Hồi giáo) có thể dựa vào quan sát trực quan thay vì các phép tính cố định. API Temporal tuân theo cách triển khai Intl của môi trường, thường sử dụng các giá trị gần đúng được tính toán. Đối với các phép tính ngày quan trọng về văn hoá hoặc tôn giáo, hãy xác minh với các chuyên gia về miền hoặc sử dụng các thư viện chuyên biệt.
  • NÊN tính đến tên niên hiệu khi sử dụng lịch có niên hiệu (ví dụ: lịch Nhật Bản, Phật lịch), bằng cách sử dụng toLocaleString().

Chiến lược dự phòng

Phương án dự phòng và hỗ trợ trình duyệt cho Temporal

Đối với các trình duyệt chưa hỗ trợ API Temporal gốc, hãy sử dụng tính năng phát hiện tính năng và tải động temporal-polyfill (~15 KB min+gz).

Xin lưu ý rằng việc nhập temporal-polyfill không tự động chỉ định đối tượng Temporal cho phạm vi toàn cục. Hãy chỉ định rõ ràng nếu mã của bạn dựa vào 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();
})();