📋 目錄
這是 TC39 新語法深度解析系列的第一篇。JavaScript 的
Date物件是社群公認最糟糕的 API 之一:月份是 0-indexed、timezone 行為混亂、不可變、API 不一致。Temporal API 是 TC39 對這個問題的正式答案。
Date 物件的七宗罪
在進入 Temporal 之前,先快速確認 Date 為什麼爛:
// 1. 月份從 0 開始:6 月是 month = 5
new Date(2026, 5, 19) // 創建了 7月!
// 2. 所有時間都是 UTC 的本地時間——沒有時區意識
// 3. Date 是可變的:date.setMonth(date.getMonth() + 1) 修改了原物件
// 4. 沒有不變性(immutable)支援
// 5. 格式化 API(toLocaleString)不一致且慢
// 6. Date.parse() 的字串解析行為在不同瀏覽器不一致
// 7. 沒有「純日期」(只有時間)概念
Temporal API 對每一個問題都有對應的解決方案。
Temporal 的核心物件
Temporal 不是一個物件,而是一組各自專門用途的物件:
| 物件 | 用途 |
|---|---|
Temporal.PlainDate | 日曆日期(2026-03-19),不含時間和時區 |
Temporal.PlainTime | 牆上時鐘時間(13:45:00),不含日期 |
Temporal.PlainDateTime | 日期+時間,不含時區 |
Temporal.ZonedDateTime | 完整的日期+時間+時區(最常用) |
Temporal.Instant | 絕對時間點(類似 Unix timestamp,時區無關) |
Temporal.Duration | 時間段(5年、3個月、2天) |
PlainDate:純日曆日期
// PlainDate:沒有時間,沒有時區
const today = Temporal.PlainDate.from({ year: 2026, month: 3, day: 19 });
// 或
const today = Temporal.PlainDate.from('2026-03-19');
// 加一天
const tomorrow = today.add({ days: 1 }); // 2026-03-20
// 兩個日期相差幾天
const diff = today.until(tomorrow, { unit: 'days' }); // 1
注意:add() 和 until() 都返回新的物件,不會修改原物件——這就是不可變性。
ZonedDateTime:最常用的完整時間
// 現在的時間(帶時區)
const now = Temporal.Now.zonedDateTimeISO();
console.log(now.toLocaleString()); // "2026/3/19 下午11:00:00"
// 從字串建立
const meeting = Temporal.ZonedDateTime.from({
year: 2026,
month: 3,
day: 20,
hour: 14,
minute: 30,
timeZone: 'Asia/Taipei',
});
console.log(meeting.toLocaleString()); // "2026/3/20 下午2:30:00"
// 時間運算
const end = meeting.add({ hours: 2 });
console.log(end.toLocaleString()); // "2026/3/20 下午4:30:00"
Instant:絕對時間點
// Instant 是時區無關的精確時間點
const instant = Temporal.Instant.fromEpochSeconds(1742438400);
// 轉換到特定時區
const taipeiTime = instant.toZonedDateTimeISO('Asia/Taipei');
console.log(taipeiTime.toLocaleString()); // "2026/3/20 上午12:00:00"
當你需要存儲或傳輸時間(如 API 來回),Instant 或 epoch seconds 是最好的選擇——它們不依賴時區,兩端系統不需要協商時區轉換。
時區處理的實際範例
場景:儲存使用者預約,並在不同時區顯示
// 使用者在台北預約了東京時間 14:00 的會議
const appointment = Temporal.ZonedDateTime.from({
year: 2026,
month: 4,
day: 5,
hour: 14,
minute: 0,
timeZone: 'Asia/Tokyo',
});
// 儲存時:轉成 UTC instant(儲存這個)
const saved = appointment.toInstant();
console.log(saved.epochSeconds); // 1743838800
// 在東京顯示:直接用原本時間
console.log(appointment.toLocaleString()); // "2026/4/5 下午2:00:00"
// 在台北顯示:自動轉換(台北比東京快 1 小時)
const taipeiView = appointment.withTimeZone('Asia/Taipei');
console.log(taipeiView.toLocaleString()); // "2026/4/5 下午3:00:00"
withTimeZone() 是 Temporal 最實用的功能之一——同一個 ZonedDateTime 可以用不同的時區視角顯示,不需要重新計算。
Duration:時間段處理
const duration = Temporal.Duration.from({ days: 5, hours: 3, minutes: 30 });
// Duration 的數學運算
const longer = duration.add({ hours: 1 }); // 5天4小時30分鐘
const shorter = duration.subtract({ days: 2 }); // 3天3小時30分鐘
// Duration 與日期的運算
const start = Temporal.PlainDate.from('2026-03-19');
const end = start.add(duration);
console.log(end.toString()); // 2026-03-24
與 date-fns / dayjs 的比較
Temporal 不是要完全取代 date-fns——date-fns 的格式化、相對時間(“3 天前”)、日曆系統(伊斯兰历、农历)支援仍然更完整。Temporal 的優勢在於型別安全和原生語法:
// 過去:date-fns 處理
import { addDays, differenceInDays, format } from 'date-fns';
const nextWeek = addDays(new Date(), 7);
const daysLeft = differenceInDays(deadline, today);
// Temporal:更直覺
const nextWeek = today.add({ days: 7 });
const daysLeft = today.until(deadline, { unit: 'days' });
目前瀏覽器支援與 Polyfill
# 安裝 Polyfill
npm install @tc39/temporal
// 使用前檢查
if (typeof Temporal !== 'undefined') {
const now = Temporal.Now.plainDateTimeISO();
console.log(now.toLocaleString());
} else {
console.log('使用 date-fns fallback');
}
截至 2026 年,所有主流瀏覽器的最新版本都已原生支援 Temporal API 核心物件(Chrome 122+、Firefox 128+、Safari 17.4+),但保險起見仍建議在 package.json 中加上 @tc39/temporal polyfill。
總結:什麼時候該用 Temporal?
用 Temporal:
- 需要準確的時區處理
- 需要不可變的日期物件
- 需要進行日期數學運算(加、減、差距)
- 需要在多個時區之間切換顯示
繼續用 date-fns / dayjs:
- 只需要格式化輸出(相對時間 “3 天前”)
- 需要支援非 Gregorian 日曆系統
- 還有舊代碼還沒遷移到 ES modules
這是 TC39 新語法深度解析系列的第一篇。下一篇:Explicit Resource Management — 用 using 關鍵字讓資源自動釋放。
延伸閱讀
- TypeScript 6.0 RC 升級指南 — 內建 Temporal API 類型支援
本文基於 TC39 Temporal API 提案(Stage 3)整理。