TC39JavaScript新語法Temporal API日期處理

TC39 新語法深度解析(一):JavaScript Temporal API — 告別 Date 物件的時間處理噩夢

整理 TC39 Temporal API(Stage 3)的核心物件:PlainDate、PlainDateTime、ZonedDateTime、Instant。包含 Date 的缺陷說明與實際遷移範例。

· 4 分鐘閱讀

這是 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 關鍵字讓資源自動釋放。


延伸閱讀


本文基於 TC39 Temporal API 提案(Stage 3)整理。