L'oggetto JavaScript Date tradizionale si basa su un calendario gregoriano prolettico, il che rende difficile creare applicazioni per gli utenti che si affidano ad altri sistemi di calendario, come i calendari islamico (lunare), ebraico (lunisolare) o cinese (lunisolare). In precedenza, gli sviluppatori dovevano fare affidamento su librerie di terze parti complesse o calcoli manuali per supportare questi sistemi.
L'API Temporal fornisce un supporto di prim'ordine per più sistemi di calendario. Associando un identificatore di calendario agli oggetti data, Temporal gestisce in modo nativo l'aritmetica e la formattazione complesse richieste per i diversi contesti culturali.
Modalità di implementazione
Per supportare i sistemi di calendario globali utilizzando Temporal:
- Associa un calendario (obbligatorio): quando crei o converti un oggetto Temporal (come
Temporal.PlainDate), devi specificare il sistema di calendario desiderato utilizzandowithCalendar()per eseguire operazioni sensibili al calendario. - Utilizza identificatori stabili (obbligatorio per i calendari lunisolari): devi utilizzare
monthCodeanziché l'indice numericomonthper identificare mesi specifici nel corso degli anni nei calendari lunisolari. Utilizza questa opzione solo per i calendari che utilizzano i mesi bisestili, come i calendari ebraico e cinese. - Rispetta le invarianti del calendario (obbligatorio): quando esegui l'iterazione di mesi o giorni, non devi presupporre valori fissi (come 12 mesi in un anno o 31 giorni in un mese). Utilizza proprietà come
monthsInYearedaysInMonthper assicurarti che il codice funzioni in tutti i calendari. - Utilizza i confronti basati sul calendario (obbligatorio): quando confronti le date all'interno di un sistema di calendario specifico, devi utilizzare
Temporal.PlainDate.compare()anziché confrontare manualmente le proprietà anno/mese/giorno. In questo modo, l'ordine cronologico viene gestito correttamente all'interno delle regole del calendario.
Codice di esempio: conversione e iterazione dei calendari
// 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'
});
Implementazione strategica e best practice
- DO use
monthsInYearas the upper bound when looping through months, rather than assuming 12. - DO use
monthCodefor identifying specific months in calendars that use leap months (e.g., Hebrew or Chinese), regardless of the year. - NON dare per scontato che
date.month === 12sia l'ultimo mese dell'anno. Utilizzadate.month === date.monthsInYear. - NON dare per scontato che
inLeapYear === trueimplichi che l'anno sia più lungo di un solo giorno. Nei calendari lunisolari, potrebbe essere aggiunto un intero mese bisestile. - SÌ: utilizza
Temporal.PlainDate.compare()quando confronti due date in un sistema di calendario specifico anziché confrontare manualmente le proprietà Year/Month/Day. - SÌ: utilizza
toLocaleString()per formattare le date per gli utenti anziché la concatenazione manuale delle stringhe. - SÌ: verifica che il sistema di calendario di destinazione sia supportato dall'ambiente utilizzando
Intl.supportedValuesOf('calendar')prima di creare oggetti Temporal specifici del calendario. - SÌ: tieni presente che alcuni calendari (come le varianti del calendario islamico) potrebbero basarsi sull'osservazione visiva anziché su calcoli fissi. L'API
Temporalsegue l'implementazione diIntldell'ambiente, che di solito utilizza approssimazioni calcolate. Per calcoli di date culturali o religiose critiche, verifica con esperti del dominio o utilizza librerie specializzate. - SÌ: tieni conto dei nomi delle ere quando utilizzi calendari che le utilizzano (ad es. giapponese, buddista), utilizzando
toLocaleString().
Strategia di riserva
Fallback e supporto del browser per Temporal
Per i browser che non supportano ancora l'API Temporal nativa, utilizza il rilevamento delle funzionalità e carica dinamicamente temporal-polyfill (~15 kB min+gz).
Tieni presente che l'importazione di temporal-polyfill non assegna automaticamente l'oggetto Temporal all'ambito globale. Assegnalo esplicitamente se il tuo codice si basa su 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();
})();