Obliczanie czasu, który upłynął lub pozostał między zdarzeniami (takimi jak wygaśnięcie okresu próbnego, odliczanie do zakończenia subskrypcji lub okna gwarancji jakości usług), i prezentowanie tych przedziałów w zlokalizowanym, zrozumiałym dla człowieka formacie wymagało dotychczas ręcznych obliczeń lub zewnętrznych bibliotek. Starszy obiekt Date śledzi tylko milisekundy od początku epoki, co sprawia, że obliczenia dotyczące miesięcy kalendarzowych, przejścia na czas letni i równoważenie jednostek są podatne na błędy.
Interfejs API Temporal łączy Temporal.ZonedDateTime z Temporal.Duration i Intl.DurationFormat, aby zapewnić różnice czasowe uwzględniające czas letni, konfigurowalne równoważenie jednostek, porównania czasu trwania uwzględniające kalendarz i zlokalizowane formatowanie.
Jak to zrobić?
- Uzyskiwanie punktów końcowych
Temporal.ZonedDateTime: użyjTemporal.Now.zonedDateTimeISO()dla bieżącego czasu w strefie czasowej systemu lub przekaż jawny ciąg znaków strefy czasowej IANA (Temporal.Now.zonedDateTimeISO(timeZone)), gdy strefa czasowa docelowego wydarzenia różni się od ustawienia domyślnego systemu użytkownika. - Obliczanie różnic za pomocą
.since()i.until():- Użyj funkcji
now.since(start, options), aby obliczyć czas, który upłynął od zdarzenia w przeszłości. - Użyj
now.until(end, options), aby obliczyć czas pozostały do przyszłego wydarzenia. - Przekaż
largestUnit(np.'year','month','day','hour') ismallestUnit(np.'minute'), aby zrównoważyć i zaokrąglić wynikowyTemporal.Duration.
- Użyj funkcji
- Zrównoważ lub porównaj samodzielne obiekty
Temporal.Duration:- Utwórz samodzielne okresy za pomocą
Temporal.Duration.from({ minutes: 90 })i ponownie zbilansuj jednostki za pomocąduration.round({ largestUnit: 'hour' })(podającrelativeTopodczas bilansowania w miesiącach kalendarzowych, latach lub przejściach na czas letni). - Porównaj 2 okresy za pomocą
Temporal.Duration.compare(d1, d2, { relativeTo })– przekazaniePlainDatelubZonedDateTimejakorelativeTojest wymagane, gdy którykolwiek z okresów zawiera jednostki kalendarzowe (years,months,weeks) lub przekracza granice czasu letniego.
- Utwórz samodzielne okresy za pomocą
- Formatowanie dla ludzi i maszyn:
- Używaj
duration.toLocaleString(locale, options)(który przekazuje uprawnienia doIntl.DurationFormat) w przypadku zlokalizowanego tekstu dla użytkownika. - Używaj
duration.toString()tylko w przypadku ciągów znaków czasu trwania w formacie ISO 8601 czytelnym dla komputerów (np.P1M2DT4H).
- Używaj
Przykład: różnica w dacie zakończenia okresu próbnego i formatowanie zlokalizowane
// 1. Get the current time in an explicit target IANA time zone (or omit the
// argument to use the system's current time zone).
const tz = 'America/New_York';
const now = Temporal.Now.zonedDateTimeISO(tz);
// 2. Combine separate date and time strings directly.
// Do not build an ISO string with template literals; chain Temporal methods instead.
const startDateStr = '2025-01-01';
const startTimeStr = '12:00:00';
const endDateStr = '2025-01-31';
const endTimeStr = '12:00:00';
const start = Temporal.PlainDate.from(startDateStr).toPlainDateTime(startTimeStr).toZonedDateTime(tz);
const end = Temporal.PlainDate.from(endDateStr).toPlainDateTime(endTimeStr).toZonedDateTime(tz);
// 3. Calculate differences using .since() and .until()
// ZonedDateTime differences default to largestUnit: 'hour' (days are not a fixed
// length across DST, so Temporal won't balance into days unless asked). Pass
// largestUnit: 'day' or a calendar unit ('week', 'month', 'year') when you want a
// human-scale breakdown instead of e.g. PT720H. smallestUnit rounds away
// seconds and sub-second noise so the formatted string stays readable.
const timeActive = now.since(start, { largestUnit: 'year', smallestUnit: 'minute' });
const timeRemaining = now.until(end, { largestUnit: 'year', smallestUnit: 'minute' });
// 4. Format durations for display with .toLocaleString() (Intl.DurationFormat)
// instead of manually concatenating unit numbers and suffixes. 'en-US' is used
// here for deterministic output; pass undefined to use the user's locale.
console.log(`Active: ${timeActive.toLocaleString('en-US', { style: 'narrow' })}`);
console.log(`Remaining: ${timeRemaining.toLocaleString('en-US', { style: 'long' })}`);
// Use .toString() for ISO 8601 serialization (e.g., API payloads or <time datetime="...">)
console.log(`ISO 8601: ${timeRemaining.toString()}`);
// 5. Check status via .sign, and compare durations with Temporal.Duration.compare()
const isExpired = timeRemaining.sign < 0;
// Pass relativeTo when comparing durations that may include calendar months/years or DST shifts
const isPastHalfway = Temporal.Duration.compare(timeActive, timeRemaining, { relativeTo: now }) > 0;
Przykład: równoważenie czasu trwania samodzielnych reklam (round i Intl.DurationFormat)
// Convert a raw unit count (e.g., 90 minutes) into a balanced breakdown (1 hour, 30 minutes)
const rawDuration = Temporal.Duration.from({ minutes: 90 });
const balanced = rawDuration.round({ largestUnit: 'hour' });
// Format via duration.toLocaleString() or directly with Intl.DurationFormat
const formatter = new Intl.DurationFormat('en-US', { style: 'long' });
console.log(formatter.format(balanced)); // "1 hour, 30 minutes"
Strategiczne wdrażanie i sprawdzone metody
- UŻYWAJ
Temporal.ZonedDateTimedo obliczeń związanych z wydarzeniami w świecie rzeczywistym, które występują w określonych strefach czasowych (np. odnowienia subskrypcji lub odliczanie do wydarzenia), i przekazuj jawny parametrtimeZonedoTemporal.Now.zonedDateTimeISO(timeZone)podczas obliczeń dotyczących strefy innej niż ustawienie domyślne systemu. - ZALECENIE: łącz osobne ciągi znaków daty i godziny za pomocą metod Temporal, np.
Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz)lubTemporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr }), zamiast interpolować ciąg znaków ISO, np.`${dateStr}T${timeStr}`. - PRZEKAZUJ
largestUnit(np.'day','month','year') w.since(),.until()lub.round(), gdy chcesz zrównoważyć czas trwania poza godzinami; metody różnicoweZonedDateTimedomyślnie ustawiają'hour'. - UŻYWAJ funkcji
Temporal.Duration.compare(d1, d2, { relativeTo })podczas porównywania 2 czasów trwania, podając punkt odniesieniarelativeTo, gdy którykolwiek z czasów trwania obejmuje jednostki kalendarzowe (months,years) lub dni uwzględniające strefę czasową. - DO format durations for user display using
duration.toLocaleString()ornew Intl.DurationFormat(locale, { style })('long','short','narrow', or'digital') instead of manually interpolating${duration.years}y ${duration.months}m.... - UŻYWAJ
duration.toString()w przypadku ciągów znaków czasu trwania w formacie ISO 8601 czytelnym dla komputerów (np.PT1H30M), nigdy w tekście dla użytkownika. - TAK – renderuj
PlainTimejakoHH:MM(np. w przypadku<input type="time">) za pomocą.toPlainTime().toString({ smallestUnit: 'minute' }), a nie ciągu znaków.slice(0, 5). - NIE modyfikuj instancji bezpośrednio. Obiekty
Temporalsą niezmienne. Operacje takie jak.add(),.subtract(),.round()lub.with()zwracają nową instancję. - NIE używaj arytmetyki
Date/.getTime()do obliczania różnicy.Datenie ma arytmetyki kalendarza uwzględniającej strefę czasową i zapewnia dokładność tylko do milisekund. - UŻYWAJ właściwości natywnej
.signobliczonegoTemporal.Duration(lubTemporal.ZonedDateTime.compare()), aby sprawdzić, czy czas trwania reprezentuje przeszły/wygasły punkt czasowy (sign < 0) czy przyszłe oczekujące rozpoczęcie.
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();
})();