Menghitung waktu yang telah berlalu atau tersisa di antara peristiwa (seperti masa berlaku uji coba, hitung mundur langganan, atau periode SLA) dan menampilkan rentang tersebut dalam format yang dilokalkan dan dapat dibaca manusia secara historis memerlukan penghitungan manual atau library eksternal. Objek Date lama hanya melacak milidetik epoch, sehingga aritmetika bulan kalender, transisi Waktu Musim Panas (DST), dan penyeimbangan unit rentan terhadap error.
Temporal API menyambungkan Temporal.ZonedDateTime dengan Temporal.Duration dan Intl.DurationFormat untuk memberikan perbedaan waktu yang aman dari DST, penyeimbangan unit yang dapat dikonfigurasi, perbandingan durasi yang mendukung kalender, dan pemformatan yang dilokalkan.
Cara Menerapkan
- Mendapatkan endpoint
Temporal.ZonedDateTime: GunakanTemporal.Now.zonedDateTimeISO()untuk waktu saat ini di zona waktu sistem, atau teruskan string zona waktu IANA eksplisit—Temporal.Now.zonedDateTimeISO(timeZone)—saat zona waktu acara target berbeda dari default sistem pengguna. - Menghitung diferensial dengan
.since()dan.until():- Gunakan
now.since(start, options)untuk menghitung waktu yang berlalu sejak peristiwa sebelumnya. - Gunakan
now.until(end, options)untuk menghitung sisa waktu hingga peristiwa mendatang. - Teruskan
largestUnit(misalnya,'year','month','day','hour') dansmallestUnit(misalnya,'minute') untuk menyeimbangkan dan membulatkanTemporal.Durationyang dihasilkan.
- Gunakan
- Menyeimbangkan atau membandingkan objek
Temporal.Durationmandiri:- Buat durasi mandiri dengan
Temporal.Duration.from({ minutes: 90 })dan seimbangkan kembali unit denganduration.round({ largestUnit: 'hour' })(berikanrelativeTosaat menyeimbangkan di seluruh bulan kalender, tahun, atau transisi DST). - Bandingkan dua durasi dengan
Temporal.Duration.compare(d1, d2, { relativeTo })—meneruskanPlainDateatauZonedDateTimesebagairelativeTodiperlukan setiap kali salah satu durasi berisi unit kalender (years,months,weeks) atau melewati batas DST.
- Buat durasi mandiri dengan
- Format untuk manusia vs. mesin:
- Gunakan
duration.toLocaleString(locale, options)(yang didelegasikan keIntl.DurationFormat) untuk teks yang ditampilkan kepada pengguna yang dilokalkan. - Gunakan
duration.toString()hanya untuk string durasi ISO 8601 yang dapat dibaca mesin (sepertiP1M2DT4H).
- Gunakan
Contoh: Perbedaan Masa Berlaku Uji Coba dan Pemformatan yang Dilokalkan
// 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;
Contoh: Menyeimbangkan Durasi Mandiri (round dan 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"
Penerapan Strategis & Praktik Terbaik
- GUNAKAN
Temporal.ZonedDateTimeuntuk penghitungan yang melibatkan peristiwa dunia nyata yang terjadi di zona waktu tertentu (seperti perpanjangan langganan atau hitung mundur acara), dan teruskantimeZoneeksplisit keTemporal.Now.zonedDateTimeISO(timeZone)saat menghitung terhadap zona selain default sistem. - LAKUKAN penggabungan string tanggal dan waktu yang terpisah dengan metode Temporal, misalnya
Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz)atauTemporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr }), daripada menginterpolasi string ISO seperti`${dateStr}T${timeStr}`. - LAKUKAN penerusan
largestUnit(misalnya,'day','month','year') di.since(),.until(), atau.round()saat Anda ingin durasi diseimbangkan di luar jam; metode perbedaanZonedDateTimeditetapkan secara default ke'hour'. - GUNAKAN
Temporal.Duration.compare(d1, d2, { relativeTo })saat membandingkan dua durasi, dengan memberikan anchorrelativeTosetiap kali salah satu durasi melibatkan unit kalender (months,years) atau hari yang memperhitungkan zona waktu. - LAKUKAN pemformatan durasi untuk tampilan pengguna menggunakan
duration.toLocaleString()ataunew Intl.DurationFormat(locale, { style })('long','short','narrow', atau'digital'), bukan menginterpolasi${duration.years}y ${duration.months}m...secara manual. - LAKUKAN penggunaan
duration.toString()untuk string durasi ISO 8601 yang dapat dibaca mesin (misalnya,PT1H30M), jangan pernah sebagai teks yang ditampilkan kepada pengguna. - LAKUKAN render
PlainTimesebagaiHH:MM(misalnya, untuk<input type="time">) menggunakan.toPlainTime().toString({ smallestUnit: 'minute' }), bukan string.slice(0, 5). - JANGAN mengubah instance secara langsung; objek
Temporalbersifat immutable. Operasi seperti.add(),.subtract(),.round(), atau.with()menampilkan instance baru. - JANGAN kembali ke aritmetika
Date/.getTime()untuk selisihnya;Datetidak memiliki matematika kalender yang mendukung zona waktu dan hanya memiliki presisi milidetik. - LAKUKAN penggunaan properti
.signnative dariTemporal.Duration(atauTemporal.ZonedDateTime.compare()) yang dihitung untuk memeriksa apakah durasi mewakili titik waktu yang telah berlalu/berakhir (sign < 0) atau waktu mulai mendatang yang tertunda.
Strategi Penggantian
Penggantian & dukungan browser untuk Temporal
Untuk browser yang belum mendukung Temporal API native, gunakan deteksi fitur dan muat temporal-polyfill secara dinamis (~15 KB min+gz).
Perhatikan bahwa mengimpor temporal-polyfill tidak otomatis menetapkan objek Temporal ke cakupan global. Tetapkan secara eksplisit jika kode Anda mengandalkan 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();
})();