Light hero background

Temporal API:現代版本的 JavaScript Date

March 14, 2026

2026 年 3 月 11 日,Temporal 正式升上 TC39 Stage 4,成為 ES2026 標準的一部分,並已原生內建在 Chrome 144+、Firefox 139+、Edge 144+。

Temporal 是 JavaScript 的全新日期時間 API,也是三十年來 Date 物件各種設計缺陷的語言層面解答。因為 Date 的不足過去需要靠 dayjsdate-fnsmoment.js 來填補,Temporal 有望可以原生補上這個空缺。

Temporal 的型別

Temporal 下其實有幾種獨立的型別,每個都針對特定的使用情境:

型別適用情境
Temporal.PlainDate生日、紀念日——只有日期,沒有時間、沒有時區
Temporal.PlainDateTime「本地」脈絡下的行事曆事件
Temporal.ZonedDateTime會議、航班——明確的 IANA 時區,自動處理日光節約時間(DST)
Temporal.Instant某個精確的時間點,適合存資料庫或跨時區比對
Temporal.Duration兩個時間點之間的差距
Temporal.PlainTime不帶日期的時刻

所有 Temporal 物件都是不可變的(immutable),操作永遠回傳新物件,原始物件不會被改動。不確定該用哪個型別時,從 ZonedDateTime 開始。

接下來,直接用幾個具體範例來看看 Temporal 可以做到什麼:

本頁面的範例都可以自行編輯與執行,並載入了 @js-temporal/polyfill,所以不管你用哪個瀏覽器都可以直接跑。如果你的瀏覽器已原生支援 Temporal(Chrome 144+、Firefox 139+、Edge 144+),polyfill 會自動略過,直接使用原生實作。

範例一:Duration 計算

Date 算「兩個時間差多久」,你得自己把毫秒數除以 86400000 算天、取餘數再除 3600000 算小時⋯⋯每次都在手動做單位換算。Temporal 直接給你一個 Duration 物件,天、時、分各自分開,也可以用 .total('hours') 換算成單一單位:

試試看:Date 版本的 Duration 計算
JS
試試看:Temporal 版本的 Duration 計算
TemporalJS
正在載入 Temporal polyfill…

範例二:農曆支援

農曆每個月長度不固定(29 或 30 天),遇到閏年還會插入閏月。用 Date 對農曆做「加兩個月」,加的是西曆兩個月六十天,農曆日期就可能偏移。Temporal 可以透過 [u-ca=chinese] 標注農曆,「加兩個月」會在農曆規則下進行:

試試看:農曆的正確加月
TemporalJS
正在載入 Temporal polyfill…

範例三:Instant 跨時區顯示

Temporal.Instant 代表一個精確的時間點,沒有時區、沒有曆法——就是距離 Unix epoch 的奈秒數。適合存在資料庫或傳輸,然後再根據不同使用者的時區轉換顯示:

試試看:Instant 轉換成不同時區
TemporalJS
正在載入 Temporal polyfill…

範例四:ZonedDateTime 自動處理 DST(Daylight Saving Time)

許多國家有日光節約時間(Daylight Saving Time, DST),有些時刻根本不存在——Date 讓你自己負責,Temporal.ZonedDateTime 會自動跳過它。

2026 年 3 月 29 日是英國的 DST,凌晨 1 點直接跳到 2 點(01:00 → 02:00),所以 01:30 這個時刻根本不存在:

試試看:ZonedDateTime 的 DST 自動處理
TemporalJS
正在載入 Temporal polyfill…

Temporal 解決了 Date 的哪些問題?

看完上面的範例,回頭談談 Temporal 解決了什麼:

Mutation:避免改動悄悄發生

Date 的做法

Date 是可變的(mutable)。傳進函式的 Date 物件,那個函式可能在你不知情的情況下改掉原始值——沒有任何警告。

Temporal 的做法

Temporal 物件全部 immutable,所有操作都回傳新物件,原始值絕對不變。

Date vs Temporal:mutation
TemporalJS
正在載入 Temporal polyfill…

月份運算:Date 自動溢出 vs Temporal 自動 clamp

Date 的做法

Date 不驗證日期是否合法,直接把溢出的日數滾到下個月——例如 Jan 31 + 1 month 會得到 Mar 03 而不是 Feb 28,完全不報錯。

Temporal 的做法

Temporal 預設把結果 clamp 到月底,不會溢出;如果你要嚴格拒絕就用 overflow: 'reject'

Date vs Temporal:月份溢出
TemporalJS
正在載入 Temporal polyfill…

字串解析:行為未定義 vs 嚴格格式

Date 的做法

同一個格式的字串,不同瀏覽器解析出來的時區可能完全不同——例如 new Date('2026-06-25 15:15:00') 在 Chrome 用本地時區、在 Firefox 可能是 Invalid Date、在 Safari 用 UTC 解析。

Temporal 的做法

Temporal 只接受嚴格定義的格式,不確定的格式直接拋錯,完全杜絕跨環境行為不一致——格式必須包含 T 分隔符,含時區要用 +08:00[Asia/Taipei] 明確標注。

Date vs Temporal:字串解析
TemporalJS
正在載入 Temporal polyfill…

也因此我們有龐大的 datetime 函式庫生態系需求:到 2026 年,moment.js + dayjs + date-fns 合計每週下載量超過 1 億次。

需要注意的地方

1. 比較不能用 >, <,要用 compare()

Temporal 物件不是 primitive,直接用 >, < 比較會拋錯。需要使用 compare() ,會回傳 -101,可以直接丟給 Array.sort(dates, Temporal.PlainDate.compare)

試試看:compare() 與排序
TemporalJS
正在載入 Temporal polyfill…

2. dayOfWeek 是 ISO 8601,不是 Date.getDay()

Date.getDay()0 表示週日、6 表示週六。Temporal 遵循 ISO 8601:1 是週一,7 是週日。把 Date 的舊程式碼移植過來時要特別注意這個差異。

試試看:dayOfWeek vs Date.getDay()
TemporalJS
正在載入 Temporal polyfill…

3. Duration 不會自動 balance

Temporal.Duration 不會自動把 100 秒換算成 1 分 40 秒——它保留你給的單位原封不動(PT100S,不是 PT1M40S)。需要換算時,用 round({ largestUnit: 'hour' }) 明確指定。

試試看:Duration balance
TemporalJS
正在載入 Temporal polyfill…

4. total() 對於月份、年份需要 relativeTo

因為一個月有 28–31 天,一年有 365/366 天,所以「2 個月等於幾天」沒有固定答案,需要告訴 Temporal 從哪個日期起算:

試試看:total() 與 relativeTo
TemporalJS
正在載入 Temporal polyfill…

5. since()until() 的方向

兩者都能做「相差多少時間」的計算,但方向相反,搞混了結果會變負數。

記憶方式:a.until(b) = 從 a 到 b 還有多遠;a.since(b) = 距離 b 過了多久。

試試看:since() vs until()
TemporalJS
正在載入 Temporal polyfill…

6. PlainDateTimeZonedDateTime 時,夏令時間隙需要處理

把一個「牆時鐘時間」轉成特定時區時,如果那個時刻剛好落在 DST 跳過的空隙裡,Temporal 會要你明確選擇怎麼處理:reject 直接拋錯、earlier 跳到間隙前、later 跳到間隙後、compatible(預設)等同 later

如果你用的是 ZonedDateTime 直接做加法,就不會有這個問題——Temporal 會自動跳過。

試試看:PlainDateTime 的 disambiguation 選項
TemporalJS
正在載入 Temporal polyfill…

7. Instant 沒有 yearmonthday

Temporal.Instant 只表示一個精確的時間點(奈秒 timestamp),沒有曆法概念,所以沒有 yearmonthday——必須先 .toZonedDateTimeISO(tz) 轉成 ZonedDateTime 再存取。

試試看:Instant 沒有日期屬性
TemporalJS
正在載入 Temporal polyfill…

8. DST 當天只有 23 小時

夏令時間「撥前」的那天,從當天午夜到隔天午夜實際上只有 23 小時。如果你用 ZonedDateTime.until() 計算這段時間,會得到 PT23H 而不是 PT24H

這是正確的行為——ZonedDateTime 忠實反映現實。如果你需要「24 小時後」,用 .add({ hours: 24 });如果需要「隔天同一時間」,用 .add({ days: 1 })(兩個結果不同!)。

試試看:DST 當天的 23 小時
TemporalJS
正在載入 Temporal polyfill…

9. PlainMonthDay:年度重複事件的正確型別

生日、節日這種「每年固定日期」的事件,應該用 Temporal.PlainMonthDay,然後每年用 toPlainDate({ year }) 生成實際日期,之後可以直接查任一年的星期幾,不需要手動換算。

試試看:PlainMonthDay 年度事件
TemporalJS
正在載入 Temporal polyfill…

10. 與 Date 互轉

如果還在混用舊的 Date API:Date → TemporalTemporal.Instant.fromEpochMilliseconds(legacy.getTime())(ES2026 原生可直接用 legacy.toTemporalInstant());Temporal → Datenew Date(instant.epochMilliseconds)

注意:轉換時精度從奈秒降為毫秒,其他 Temporal 型別(如曆法資訊、時區名稱)也不會被保留。

試試看:Date 與 Temporal 互轉
TemporalJS
正在載入 Temporal polyfill…

11. 瀏覽器支援

Temporal 在 2026 年 3 月正式成為 ES2026 標準,主流瀏覽器已原生支援:

  • Chrome 144+(2026 年 1 月起)
  • Firefox 139+(2025 年 5 月起)
  • Edge 144+(2026 年 1 月起)
  • Safari(Tech Preview 部分支援)

如果需要支援舊版瀏覽器,可以用 @js-temporal/polyfill

npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';

總結

三十年來,JavaScript 前端開發者要處理任何稍微複雜的日期邏輯,幾乎都要靠第三方套件——這件事終於有了轉變。Temporal 帶來了 immutable 物件、明確的型別系統(InstantPlainDate 是完全不同的概念)、嚴格的格式解析、完整的 IANA 時區支援、多曆法運算。這些功能以前分散在不同套件裡,現在都內建了。

對前端開發最直接的影響是:日期相關的處理會輕鬆很多

Chrome 144+、Firefox 139+、Edge 144+ 已原生支援,Safari 還在路上。現在可以用 polyfill 先跑起來,等瀏覽器全跟上之後把 polyfill 移掉就好。


參考資料