描述
與大多數全域性物件不同,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.Duration - 表示時間點
- 表示歷史上的一個唯一時刻
- 作為時間戳:
Temporal.Instant - 作為日期-時間元件組合與時區配對:
Temporal.ZonedDateTime
- 作為時間戳:
- 表示不感知時區的日期/時間(所有都以“Plain”為字首)
- 日期(年、月、日)+ 時間(小時、分鐘、秒、毫秒、微秒、納秒):
Temporal.PlainDateTime(注意:ZonedDateTime等同於PlainDateTime加上時區)- 日期(年、月、日):
Temporal.PlainDate - 時間(小時、分鐘、秒、毫秒、微秒、納秒):
Temporal.PlainTime
- 日期(年、月、日):
- 日期(年、月、日)+ 時間(小時、分鐘、秒、毫秒、微秒、納秒):
- 表示歷史上的一個唯一時刻
此外,還有一個實用名稱空間 Temporal.Now,它提供了以各種格式獲取當前時間的方法。
共享類介面
Temporal 名稱空間中有許多類,但它們共享許多相似的方法。下表列出了每個類的所有方法(除了轉換方法)
下表總結了每個類可用的屬性,讓你大致瞭解每個類可以表示的資訊。
Instant |
ZonedDateTime |
PlainDateTime |
PlainDate |
PlainTime |
PlainYearMonth |
PlainMonthDay |
|
|---|---|---|---|---|---|---|---|
| 日曆 | N/A | calendarId |
calendarId |
calendarId |
N/A | calendarId |
calendarId |
| 年份相關 | N/A | eraeraYear年inLeapYearmonthsInYeardaysInYear |
eraeraYear年inLeapYearmonthsInYeardaysInYear |
eraeraYear年inLeapYearmonthsInYeardaysInYear |
N/A | eraeraYear年inLeapYearmonthsInYeardaysInYear |
N/A |
| 月份相關 | N/A | 月份monthCodedaysInMonth |
月份monthCodedaysInMonth |
月份monthCodedaysInMonth |
N/A | 月份monthCodedaysInMonth |
monthCode |
| 周相關 | N/A | weekOfYearyearOfWeekdaysInWeek |
weekOfYearyearOfWeekdaysInWeek |
weekOfYearyearOfWeekdaysInWeek |
N/A | N/A | N/A |
| 日期相關 | N/A | 日dayOfWeekdayOfYear |
日dayOfWeekdayOfYear |
日dayOfWeekdayOfYear |
N/A | N/A | 日 |
| 時間元件 | N/A | 小時minute秒millisecondmicrosecondnanosecond |
小時minute秒millisecondmicrosecondnanosecond |
N/A | 小時minute秒millisecondmicrosecondnanosecond |
N/A | N/A |
| 時區 | N/A | timeZoneIdoffsetoffsetNanosecondshoursInDaygetTimeZoneTransition()startOfDay() |
N/A | N/A | N/A | N/A | N/A |
| 紀元時間 | epochMillisecondsepochNanoseconds |
epochMillisecondsepochNanoseconds |
N/A | N/A | N/A | N/A | N/A |
類之間轉換
下表總結了每個類中存在的所有轉換方法。
| 如何從... | ||||||||
Instant |
ZonedDateTime |
PlainDateTime |
PlainDate |
PlainTime |
PlainYearMonth |
PlainMonthDay |
||
|---|---|---|---|---|---|---|---|---|
| 到... | Instant | / | toInstant() | 首先轉換為 ZonedDateTime | ||||
ZonedDateTime | toZonedDateTimeISO() | / | toZonedDateTime() | toZonedDateTime() | PlainDate#toZonedDateTime()(作為引數傳遞) | 首先轉換為 PlainDate | ||
PlainDateTime | 首先轉換為 ZonedDateTime | toPlainDateTime() | / | toPlainDateTime() | PlainDate#toPlainDateTime()(作為引數傳遞) | |||
PlainDate | toPlainDate() | toPlainDate() | / | 資訊無重疊 | toPlainDate() | toPlainDate() | ||
PlainTime | toPlainTime() | toPlainTime() | 資訊無重疊 | / | 資訊無重疊 | |||
PlainYearMonth | 首先轉換為 PlainDate | toPlainYearMonth() | 資訊無重疊 | / | 首先轉換為 PlainDate | |||
PlainMonthDay | toPlainMonthDay() | 首先轉換為 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 中,在一個日曆系統下的每個日期都由三個元件唯一標識:year、month 和 day。雖然 year 通常是正整數,但它也可以是零或負數,並且隨著時間的推移單調遞增。年份 1(或 0,如果存在)被稱為日曆紀元,對於每個日曆都是任意的。month 是一個正整數,每次遞增 1,從 1 開始,到 date.monthsInYear 結束,然後隨著年份的推進重置回 1。day 也是一個正整數,但它可能不從 1 開始,或者每次不遞增 1,因為政治變化可能導致日期跳過或重複。但總的來說,day 單調遞增並隨著月份的推進而重置。
除了 year,對於使用時代的日曆,年份也可以透過 era 和 eraYear 的組合唯一標識。例如,公曆使用“CE”(公元)和“BCE”(公元前)時代,年份 -1 與 { era: "bce", eraYear: 2 } 相同(請注意,年份 0 始終存在於所有日曆中;對於公曆,由於天文年編號,它對應於公元前 1 年)。era 是一個小寫字串,eraYear 是一個任意整數,可以是零或負數,甚至可能隨著時間減少(通常用於最古老的時代)。
注意:始終成對使用 era 和 eraYear;不要在沒有使用另一個屬性的情況下使用其中一個。此外,為避免衝突,在指定年份時不要將 year 與 era/eraYear 組合使用。選擇一種年份表示並始終如一地使用它。
請注意以下關於年份的錯誤假設
- 不要假設
era和eraYear總是存在;它們可能是undefined。 - 不要假設
era是使用者友好的字串;請使用toLocaleString()來格式化你的日期。 - 不要假設來自不同日曆的兩個
year值是可比較的;請使用compare()靜態方法。 - 不要假設年份有 365/366 天和 12 個月;請使用
daysInYear和monthsInYear。 - 不要假設閏年(
inLeapYear為true)多一天;它們可能多一個月。
除了 month,一年中的月份也可以透過 monthCode 唯一標識。monthCode 通常對映到月份的名稱,但 month 不對映。例如,在陰陽曆的情況下,兩個具有相同 monthCode 的月份,其中一個屬於閏年而另一個不屬於,如果它們在閏月之後,則由於插入了一個額外的月份,它們將具有不同的 month 值。
注意:為避免衝突,在指定月份時不要將 month 與 monthCode 組合使用。選擇一種月份表示並始終如一地使用它。如果你需要一年中月份的順序(例如,迴圈遍歷月份時),month 更實用,而如果你需要月份的名稱(例如,儲存生日時),monthCode 更實用。
請注意以下關於月份的錯誤假設
- 不要假設
monthCode和month總是對應。 - 不要假設月份的天數;請使用
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 或更多天,甚至完全沒有固定的天數。要獲取日期一週中的具體天數,請使用日期的 daysInWeek。Temporal 透過 weekOfYear 和 yearOfWeek 的組合來標識周。weekOfYear 是一個正整數,每次遞增 1,從 1 開始,然後隨著年份的推進重置回 1。yearOfWeek 通常與 year 相同,但在每年的開始或結束時可能不同,因為一週可能跨越兩年,並且 yearOfWeek 根據日曆規則選擇其中一年。
注意:始終成對使用 weekOfYear 和 yearOfWeek;不要使用 weekOfYear 和 year。
請注意以下關於周的錯誤假設
- 不要假設
weekOfYear和yearOfWeek總是存在;它們可能是undefined。 - 不要假設周總是 7 天長;請使用
daysInWeek。 - 請注意,當前的
TemporalAPI 不支援年-週日期,因此你無法使用這些屬性構造日期或將日期序列化為年-周表示。它們僅是資訊性屬性。
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.Instant和Temporal.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 |
瀏覽器相容性
載入中…