Temporal

可用性有限

此特性不是基線特性,因為它在一些最廣泛使用的瀏覽器中不起作用。

實驗性: 這是一項實驗性技術
在生產中使用此技術之前,請仔細檢查瀏覽器相容性表格

Temporal 物件可以在各種場景下進行日期和時間管理,包括內建時區和日曆表示、掛鐘時間轉換、算術運算、格式化等等。它旨在完全替代 Date 物件。

描述

與大多數全域性物件不同,Temporal 不是建構函式。你不能將其與 new 運算子一起使用,也不能將 Temporal 物件作為函式呼叫。Temporal 的所有屬性和方法都是靜態的(就像 Math 物件一樣)。

Temporal 擁有一個複雜而強大的 API。它通過幾個類公開了 200 多個實用方法,因此它可能看起來非常複雜。我們將對這些 API 之間的關係提供一個高階概述。

背景和概念

自 JavaScript 誕生以來,它就一直有 Date 物件來處理日期和時間。然而,Date API 基於 Java 中設計不佳的 java.util.Date 類,該類在 2010 年代初被替換;但由於 JavaScript 致力於向後相容,Date 仍然存在於語言中。

整個介紹的重要前提是:日期處理是複雜的Date 的大部分問題可以透過新增更多方法來解決,但一個根本的設計缺陷依然存在:它在同一個物件上公開了如此多的方法,以至於開發人員經常對使用哪個方法感到困惑,從而導致意想不到的陷阱。一個設計良好的 API 不僅需要做更多的事情,而且在每個抽象層次上都應該做更少的事情,因為防止誤用與實現用例同樣重要。

Date 物件同時扮演兩個角色

  • 作為時間戳:自某個固定時間點(稱為紀元)以來經過的毫秒或納秒數。
  • 作為元件的組合:年、月、日、小時、分鐘、秒、毫秒和納秒。年、月、日識別符號只有在參考日曆系統時才有意義。當與時區關聯時,整個組合對映到歷史上的一個唯一時刻。Date 物件提供了讀取和修改這些元件的方法。

時區是大量日期相關 bug 的根本原因。當透過“元件組合”模型與 Date 互動時,時間只能是兩個時區之一:UTC 和本地(裝置),並且無法指定任意時區。此外還缺少“無時區”的概念:這被稱為日曆日期(對於日期)或掛鐘時間(對於時間),它表示你“從日曆或時鐘上讀取的時間”。例如,如果你設定每日起床鬧鐘,你希望將其設定為“上午 8:00”,無論是否夏令時,無論你是否旅行到不同的時區等等。

Date 缺乏的第二個功能是日曆系統。大多數人可能熟悉公曆,它有兩個時代,公元前和公元;有 12 個月;每個月有不同的天數;大約每 4 年有一個閏年;等等。然而,當你使用其他日曆系統時,例如希伯來曆、農曆、日本歷等,其中一些概念可能不適用。使用 Date,你只能使用公曆模型。

Date 還有許多其他不受歡迎的遺留問題,例如所有設定器都是變異的(這通常會導致不必要的副作用),日期時間字串格式無法以一致的方式解析等。最終,最好的解決方案是從頭開始構建一個新的 API,這就是 Temporal

API 概述

Temporal 是一個名稱空間,就像 Intl。它包含幾個類和名稱空間,每個都旨在處理日期和時間管理的特定方面。這些類可以這樣分組:

此外,還有一個實用名稱空間 Temporal.Now,它提供了以各種格式獲取當前時間的方法。

共享類介面

Temporal 名稱空間中有許多類,但它們共享許多相似的方法。下表列出了每個類的所有方法(除了轉換方法

Instant ZonedDateTime PlainDateTime PlainDate PlainTime PlainYearMonth PlainMonthDay
構造 Instant()
Instant.from()
Instant.fromEpochMilliseconds()
Instant.fromEpochNanoseconds()
ZonedDateTime()
ZonedDateTime.from()
PlainDateTime()
PlainDateTime.from()
PlainDate()
PlainDate.from()
PlainTime()
PlainTime.from()
PlainYearMonth()
PlainYearMonth.from()
PlainMonthDay()
PlainMonthDay.from()
更新器 N/A with()
withCalendar()
withTimeZone()
withPlainTime()
with()
withCalendar()
withPlainTime()
with()
withCalendar()
with() with() with()
算術 add()
subtract()
since()
until()
add()
subtract()
since()
until()
add()
subtract()
since()
until()
add()
subtract()
since()
until()
add()
subtract()
since()
until()
add()
subtract()
since()
until()
N/A
舍入 round() round() round() N/A round() N/A N/A
比較 equals()
Instant.compare()
equals()
ZonedDateTime.compare()
equals()
PlainDateTime.compare()
equals()
PlainDate.compare()
equals()
PlainTime.compare()
equals()
PlainYearMonth.compare()
equals()
序列化 toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()
toJSON()
toLocaleString()
toString()
valueOf()

下表總結了每個類可用的屬性,讓你大致瞭解每個類可以表示的資訊。

Instant ZonedDateTime PlainDateTime PlainDate PlainTime PlainYearMonth PlainMonthDay
日曆 N/A calendarId calendarId calendarId N/A calendarId calendarId
年份相關 N/A era
eraYear

inLeapYear
monthsInYear
daysInYear
era
eraYear

inLeapYear
monthsInYear
daysInYear
era
eraYear

inLeapYear
monthsInYear
daysInYear
N/A era
eraYear

inLeapYear
monthsInYear
daysInYear
N/A
月份相關 N/A 月份
monthCode
daysInMonth
月份
monthCode
daysInMonth
月份
monthCode
daysInMonth
N/A 月份
monthCode
daysInMonth
monthCode
周相關 N/A weekOfYear
yearOfWeek
daysInWeek
weekOfYear
yearOfWeek
daysInWeek
weekOfYear
yearOfWeek
daysInWeek
N/A N/A N/A
日期相關 N/A
dayOfWeek
dayOfYear

dayOfWeek
dayOfYear

dayOfWeek
dayOfYear
N/A N/A
時間元件 N/A 小時
minute

millisecond
microsecond
nanosecond
小時
minute

millisecond
microsecond
nanosecond
N/A 小時
minute

millisecond
microsecond
nanosecond
N/A N/A
時區 N/A timeZoneId
offset
offsetNanoseconds
hoursInDay
getTimeZoneTransition()
startOfDay()
N/A N/A N/A N/A N/A
紀元時間 epochMilliseconds
epochNanoseconds
epochMilliseconds
epochNanoseconds
N/A N/A N/A N/A N/A

類之間轉換

下表總結了每個類中存在的所有轉換方法。

如何從...
Instant ZonedDateTime PlainDateTime PlainDate PlainTime PlainYearMonth PlainMonthDay
到...Instant/toInstant()首先轉換為 ZonedDateTime
ZonedDateTimetoZonedDateTimeISO()/toZonedDateTime()toZonedDateTime()PlainDate#toZonedDateTime()(作為引數傳遞)首先轉換為 PlainDate
PlainDateTime首先轉換為 ZonedDateTimetoPlainDateTime()/toPlainDateTime()PlainDate#toPlainDateTime()(作為引數傳遞)
PlainDatetoPlainDate()toPlainDate()/資訊無重疊toPlainDate()toPlainDate()
PlainTimetoPlainTime()toPlainTime()資訊無重疊/資訊無重疊
PlainYearMonth首先轉換為 PlainDatetoPlainYearMonth()資訊無重疊/首先轉換為 PlainDate
PlainMonthDaytoPlainMonthDay()首先轉換為 PlainDate/

透過這些表格,你應該對如何使用 Temporal API 有了基本的瞭解。

日曆

日曆是一種組織日期的方式,通常分為周、月、年和時代。世界上大多數地方使用公曆,但也有許多其他日曆在使用,特別是在宗教和文化背景下。預設情況下,所有感知日曆的 Temporal 物件都使用 ISO 8601 日曆系統,該系統基於公曆並定義了額外的周編號規則。Intl.supportedValuesOf() 列出了瀏覽器可能支援的大多數日曆。在這裡,我們將簡要概述日曆系統的構成方式,以幫助你理解不同日曆之間可能存在的差異。

地球上有三個顯著的週期性事件:它繞太陽公轉(一次公轉 365.242 天)、月球繞地球公轉(從新月到新月 29.53 天)以及它繞其軸自轉(從日出到日出 24 小時)。每種文化對“一天”的衡量標準都是相同的,即 24 小時。偶爾的變化,例如夏令時,不屬於日曆的一部分,而是時區資訊的一部分。

  • 有些日曆主要將一年定義為平均 365.242 天,透過將年份定義為 365 天,並大約每 4 年增加一個額外的一天,即閏日。然後,一年可以進一步劃分為稱為月份的部分。這些日曆被稱為陽曆。公曆和伊朗太陽曆都是陽曆。
  • 有些日曆主要將一個月定義為平均 29.5 天,透過將月份在 29 天和 30 天之間交替定義。然後,12 個月可以組合成一個 354 天的年份。這些日曆被稱為陰曆。伊斯蘭曆就是一種陰曆。由於陰曆年份是人為的,與季節週期無關,因此陰曆通常較少見。
  • 有些日曆也主要根據月球週期定義月份,類似於陰曆。然後,為了彌補與陽曆 11 天的差異,大約每 3 年增加一個額外的月份,即閏月。這些日曆被稱為陰陽曆。希伯來曆和農曆都是陰陽曆。

Temporal 中,在一個日曆系統下的每個日期都由三個元件唯一標識:yearmonthday。雖然 year 通常是正整數,但它也可以是零或負數,並且隨著時間的推移單調遞增。年份 1(或 0,如果存在)被稱為日曆紀元,對於每個日曆都是任意的。month 是一個正整數,每次遞增 1,從 1 開始,到 date.monthsInYear 結束,然後隨著年份的推進重置回 1day 也是一個正整數,但它可能不從 1 開始,或者每次不遞增 1,因為政治變化可能導致日期跳過或重複。但總的來說,day 單調遞增並隨著月份的推進而重置。

除了 year,對於使用時代的日曆,年份也可以透過 eraeraYear 的組合唯一標識。例如,公曆使用“CE”(公元)和“BCE”(公元前)時代,年份 -1{ era: "bce", eraYear: 2 } 相同(請注意,年份 0 始終存在於所有日曆中;對於公曆,由於天文年編號,它對應於公元前 1 年)。era 是一個小寫字串,eraYear 是一個任意整數,可以是零或負數,甚至可能隨著時間減少(通常用於最古老的時代)。

注意:始終成對使用 eraeraYear;不要在沒有使用另一個屬性的情況下使用其中一個。此外,為避免衝突,在指定年份時不要將 yearera/eraYear 組合使用。選擇一種年份表示並始終如一地使用它。

請注意以下關於年份的錯誤假設

  • 不要假設 eraeraYear 總是存在;它們可能是 undefined
  • 不要假設 era 是使用者友好的字串;請使用 toLocaleString() 來格式化你的日期。
  • 不要假設來自不同日曆的兩個 year 值是可比較的;請使用 compare() 靜態方法。
  • 不要假設年份有 365/366 天和 12 個月;請使用 daysInYearmonthsInYear
  • 不要假設閏年(inLeapYeartrue)多一天;它們可能多一個月。

除了 month,一年中的月份也可以透過 monthCode 唯一標識。monthCode 通常對映到月份的名稱,但 month 不對映。例如,在陰陽曆的情況下,兩個具有相同 monthCode 的月份,其中一個屬於閏年而另一個不屬於,如果它們在閏月之後,則由於插入了一個額外的月份,它們將具有不同的 month 值。

注意:為避免衝突,在指定月份時不要將 monthmonthCode 組合使用。選擇一種月份表示並始終如一地使用它。如果你需要一年中月份的順序(例如,迴圈遍歷月份時),month 更實用,而如果你需要月份的名稱(例如,儲存生日時),monthCode 更實用。

請注意以下關於月份的錯誤假設

  • 不要假設 monthCodemonth 總是對應。
  • 不要假設月份的天數;請使用 daysInMonth
  • 不要假設 monthCode 是使用者友好的字串;請使用 toLocaleString() 來格式化你的日期。
  • 通常,不要將月份名稱快取到陣列或物件中。儘管 monthCode 通常在一個日曆中對映到月份的名稱,但我們建議始終使用以下方法計算月份名稱,例如 date.toLocaleString("en-US", { calendar: date.calendarId, month: "long" })

除了 day(這是基於月份的索引),一年中的某一天也可以透過 dayOfYear 唯一標識。dayOfYear 是一個正整數,每次遞增 1,從 1 開始,到 date.daysInYear 結束。

“周”的概念與任何天文事件無關,而是一種文化建構。雖然最常見的長度是 7 天,但周也可以有 4、5、6、8 或更多天,甚至完全沒有固定的天數。要獲取日期一週中的具體天數,請使用日期的 daysInWeekTemporal 透過 weekOfYearyearOfWeek 的組合來標識周。weekOfYear 是一個正整數,每次遞增 1,從 1 開始,然後隨著年份的推進重置回 1yearOfWeek 通常與 year 相同,但在每年的開始或結束時可能不同,因為一週可能跨越兩年,並且 yearOfWeek 根據日曆規則選擇其中一年。

注意:始終成對使用 weekOfYearyearOfWeek;不要使用 weekOfYearyear

請注意以下關於周的錯誤假設

  • 不要假設 weekOfYearyearOfWeek 總是存在;它們可能是 undefined
  • 不要假設周總是 7 天長;請使用 daysInWeek
  • 請注意,當前的 Temporal API 不支援年-週日期,因此你無法使用這些屬性構造日期或將日期序列化為年-周表示。它們僅是資訊性屬性。

RFC 9557 格式

所有 Temporal 類都可以使用 RFC 9557 中指定的格式進行序列化和反序列化,該格式基於 ISO 8601 / RFC 3339。該格式的完整形式如下(空格僅用於可讀性,不應出現在實際字串中)

YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone_id] [u-ca=calendar_id]

不同的類對每個元件的存在有不同的要求,因此你將在每個類的文件中找到一個名為“RFC 9557 格式”的部分,其中指定了該類識別的格式。

這與 Date 使用的日期時間字串格式非常相似,後者也基於 ISO 8601。主要的增加是能夠指定微秒和納秒元件,以及能夠指定時區和日曆系統。

可表示日期

所有表示特定日曆日期的 Temporal 物件都對可表示日期的範圍施加了類似的限制,即從 Unix 紀元開始 ±108 天(包含),或從 -271821-04-20T00:00:00+275760-09-13T00:00:00 的瞬時範圍。這與有效日期的範圍相同。更具體地說:

  • Temporal.InstantTemporal.ZonedDateTime 直接將其 epochNanoseconds 值限制在此範圍內。
  • Temporal.PlainDateTime 在 UTC 時區解釋日期時間,並要求它從 Unix 紀元開始 ±(108 + 1) 天(不包含),因此其有效範圍是 -271821-04-19T00:00:00+275760-09-14T00:00:00,不包含。這允許任何 ZonedDateTime 轉換為 PlainDateTime,無論其偏移量如何。
  • Temporal.PlainDate 對該日期的中午 (12:00:00) 應用與 PlainDateTime 相同的檢查,因此其有效範圍是 -271821-04-19+275760-09-13。這允許任何 PlainDateTime 轉換為 PlainDate,無論其時間如何,反之亦然。
  • Temporal.PlainYearMonth 的有效範圍是 -271821-04+275760-09。這允許任何 PlainDate 轉換為 PlainYearMonth,無論其日期如何(除非非 ISO 月份的第一天落在 ISO 月份 -271821-03 中)。

Temporal 物件將拒絕構造表示超出此限制的日期/時間的例項。這包括:

  • 使用建構函式或 from() 靜態方法。
  • 使用 with() 方法更新日曆欄位。
  • 使用 add()subtract()round() 或任何其他方法派生新例項。

靜態屬性

Temporal.Duration 實驗性

表示兩個時間點之間的差值,可用於日期/時間算術。它基本上表示為年、月、周、日、小時、分鐘、秒、毫秒、微秒和納秒值的組合。

Temporal.Instant 實驗性

表示一個唯一的納秒精度時間點。它基本上表示為自 Unix 紀元(1970 年 1 月 1 日 UTC 午夜開始)以來的納秒數,不帶任何時區或日曆系統。

Temporal.Now 實驗性

提供了以各種格式獲取當前時間的方法。

Temporal.PlainDate 實驗性

表示日曆日期(不帶時間或時區的日期);例如,日曆上發生的事件,無論發生在哪個時區,都持續一整天。它基本上表示為 ISO 8601 日曆日期,具有年、月、日欄位和關聯的日曆系統。

Temporal.PlainDateTime 實驗性

表示不帶時區的日期(日曆日期)和時間(掛鐘時間)。它基本上表示為日期(帶關聯日曆系統)和時間的組合。

Temporal.PlainMonthDay 實驗性

表示日曆日期的月份和日期,不帶年份或時區;例如,日曆上每年重複發生並持續一整天的事件。它基本上表示為 ISO 8601 日曆日期,具有年、月、日欄位和關聯的日曆系統。年份用於消除非 ISO 日曆系統中月份-日期的歧義。

Temporal.PlainTime 實驗性

表示不帶日期或時區的時間;例如,每天在同一時間發生的重複事件。它基本上表示為小時、分鐘、秒、毫秒、微秒和納秒值的組合。

Temporal.PlainYearMonth 實驗性

表示日曆日期的年份和月份,不帶日期或時區;例如,日曆上發生並持續一整個月的事件。它基本上表示為 ISO 8601 日曆日期,具有年、月、日欄位和關聯的日曆系統。日期用於消除非 ISO 日曆系統中年份-月份的歧義。

Temporal.ZonedDateTime 實驗性

表示帶有時區的日期和時間。它基本上表示為瞬時、時區和日曆系統的組合。

Temporal[Symbol.toStringTag]

[Symbol.toStringTag] 屬性的初始值是字串 "Temporal"。此屬性用於 Object.prototype.toString()

規範

規範
Temporal
# sec-temporal-objects

瀏覽器相容性

另見