Традиционный объект JavaScript Date основан на пролептическом григорианском календаре, что затрудняет создание приложений для пользователей, которые полагаются на другие календарные системы, такие как исламский (лунный), еврейский (лунно-солнечный) или китайский (лунно-солнечный) календари. Раньше разработчикам приходилось использовать сложные сторонние библиотеки или выполнять расчеты вручную.
Temporal API обеспечивает первоклассную поддержку нескольких календарных систем. Связывая идентификатор календаря с объектами дат, Temporal обрабатывает сложные арифметические операции и форматирование, необходимые для различных культурных контекстов.
Как реализовать
Чтобы поддерживать глобальные календарные системы с помощью Temporal:
- Связывание с календарем (обязательно). При создании или преобразовании объекта Temporal (например,
Temporal.PlainDate) необходимо указать нужную систему календаря с помощьюwithCalendar(), чтобы выполнять операции, зависящие от календаря. - Используйте стабильные идентификаторы (обязательно для лунно-солнечных календарей). Чтобы идентифицировать определенные месяцы в разных годах в лунно-солнечных календарях, используйте
monthCode, а не числовой индексmonth. Используйте это значение только для календарей, в которых есть високосные месяцы, например для еврейского и китайского. - Учитывайте инварианты календаря (обязательно). При переборе месяцев или дней не используйте фиксированные значения (например, 12 месяцев в году или 31 день в месяце). Используйте такие свойства, как
monthsInYearиdaysInMonth, чтобы код работал во всех календарях. - Используйте сравнения с учетом календаря (обязательно). При сравнении дат в определенной системе календаря необходимо использовать
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означает, что год длиннее всего на один день. В лунно-солнечных календарях может добавляться целый високосный месяц. - РЕКОМЕНДУЕТСЯ использовать
Temporal.PlainDate.compare()для сравнения двух дат в определенной системе календаря вместо сравнения свойств Year/Month/Day вручную. - РЕКОМЕНДУЕТСЯ использовать
toLocaleString()для форматирования дат вместо объединения строк вручную. - РЕКОМЕНДУЕТСЯ проверять, поддерживается ли целевая календарная система средой, с помощью
Intl.supportedValuesOf('calendar'), прежде чем создавать объекты Temporal, относящиеся к календарю. - УЧИТЫВАЙТЕ, что некоторые календари (например, варианты исламского календаря) могут опираться на визуальные наблюдения, а не на фиксированные расчеты. API
Temporalследует реализацииIntlсреды, которая обычно использует расчетные приближения. Для критически важных расчетов дат, связанных с культурой или религией, проконсультируйтесь с экспертами или используйте специализированные библиотеки. - УЧИТЫВАЙТЕ названия эр при использовании календарей, в которых они есть (например, японского или буддийского), с помощью
toLocaleString().
Резервная стратегия
Резервные варианты и поддержка 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();
})();