Tradycyjny obiekt Date w JavaScripcie jest oparty na proleptycznym kalendarzu gregoriańskim, co utrudnia tworzenie aplikacji dla użytkowników, którzy korzystają z innych systemów kalendarza, takich jak kalendarz islamski (księżycowy), hebrajski (księżycowo-słoneczny) czy chiński (księżycowo-słoneczny). Wcześniej deweloperzy musieli korzystać ze skomplikowanych bibliotek innych firm lub wykonywać obliczenia ręcznie, aby obsługiwać te systemy.
Interfejs API Temporal zapewnia najwyższej klasy obsługę wielu systemów kalendarzy. Dzięki powiązaniu identyfikatora kalendarza z obiektami daty Temporal natywnie obsługuje złożone obliczenia i formatowanie wymagane w różnych kontekstach kulturowych.
Jak to zrobić?
Aby obsługiwać globalne systemy kalendarzy za pomocą Temporal:
- Powiązanie kalendarza (obowiązkowe): podczas tworzenia lub konwertowania obiektu Temporal (np.
Temporal.PlainDate) musisz określić odpowiedni system kalendarza za pomocąwithCalendar(), aby wykonywać operacje zależne od kalendarza. - Używaj stałych identyfikatorów (obowiązkowe w przypadku kalendarzy księżycowo-słonecznych): do identyfikowania konkretnych miesięcy w różnych latach w kalendarzach księżycowo-słonecznych musisz używać identyfikatora
monthCode, a nie indeksu liczbowegomonth. Używaj tej opcji tylko w przypadku kalendarzy, które zawierają miesiące przestępne, takich jak kalendarz żydowski i chiński. - Przestrzeganie niezmiennych kalendarza (obowiązkowe): podczas iteracji po miesiącach lub dniach nie wolno zakładać stałych wartości (np. 12 miesięcy w roku lub 31 dni w miesiącu). Używaj właściwości takich jak
monthsInYearidaysInMonth, aby mieć pewność, że kod działa we wszystkich kalendarzach. - Używaj porównań uwzględniających kalendarz (obowiązkowe): podczas porównywania dat w określonym systemie kalendarza musisz używać
Temporal.PlainDate.compare()zamiast ręcznego porównywania właściwości roku, miesiąca i dnia. Dzięki temu reguły kalendarza będą prawidłowo obsługiwać kolejność chronologiczną.
Przykładowy kod: konwertowanie i iterowanie kalendarzy
// 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'
});
Strategiczne wdrażanie i sprawdzone metody
- DO use
monthsInYearas the upper bound when looping through months, rather than assuming 12. - UŻYWAJ
monthCodedo identyfikowania konkretnych miesięcy w kalendarzach, które używają miesięcy przestępnych (np. hebrajskim lub chińskim), niezależnie od roku. - NIE zakładaj, że
date.month === 12to ostatni miesiąc roku. Użyjdate.month === date.monthsInYear. - NIE zakładaj, że
inLeapYear === trueoznacza, że rok jest dłuższy tylko o 1 dzień. W kalendarzach księżycowo-słonecznych może to być cały miesiąc przestępny. - UŻYWAJ
Temporal.PlainDate.compare()podczas porównywania 2 dat w określonym systemie kalendarza zamiast ręcznego porównywania właściwości Rok/Miesiąc/Dzień. - UŻYWAJ
toLocaleString()do formatowania dat dla użytkowników zamiast ręcznej konkatenacji ciągów znaków. - SPRAWDŹ, czy docelowy system kalendarza jest obsługiwany przez środowisko, używając
Intl.supportedValuesOf('calendar')przed utworzeniem obiektów Temporal związanych z kalendarzem. - PAMIĘTAJ, że niektóre kalendarze (np. warianty kalendarza islamskiego) mogą opierać się na obserwacji wizualnej, a nie na stałych obliczeniach. Interfejs API
Temporaljest zgodny z implementacjąIntlśrodowiska, która zwykle używa obliczonych przybliżeń. W przypadku obliczeń dotyczących ważnych dat kulturowych lub religijnych skonsultuj się z ekspertami w danej dziedzinie lub skorzystaj ze specjalistycznych bibliotek. - DO account for era names when using calendars that use eras (e.g., Japanese, Buddhist), using
toLocaleString().
Strategia zastępcza
Zastępcze rozwiązania i obsługa Temporal w przeglądarkach
W przypadku przeglądarek, które nie obsługują jeszcze natywnego interfejsu API Temporal, użyj wykrywania funkcji i dynamicznie wczytaj temporal-polyfill (~15 KB min+gz).
Pamiętaj, że zaimportowanie temporal-polyfill nie powoduje automatycznego przypisania obiektu Temporal do zakresu globalnego. Przypisz go bezpośrednio, jeśli Twój kod opiera się na 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();
})();