Get to know MDN better
此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。
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 交互时,时间只能处于两个时区之一:UTC 时区和本地(设备)时区,并且无法指定任意时区。此外还缺少“无时区”的概念:无时区被称为日历日期(对应日期)或墙钟时间(对应时间),即你“从日历或时钟上看到的”的时间。例如,如果你在设置每天的起床闹钟时,不管时下是否处于夏令时,也不管你是否旅行至不同的时区等情况,你都会希望它始终设为“上午 8:00”。
Date 还缺少的第二个特性是日历系统。大多数人可能熟悉公历(格里高利历),它有公元前(BC)和公元后(AD)两个纪元;有 12 个月;每个月的天数不同;每 4 年有一个闰年等等。然而,当你使用其他日历系统,比如希伯来日历、中国农历、日本日历等等,一些公历的概念就可能不适用了。使用 Date 时,你只能采用公历模型。
Date 还有许多不理想的历史遗留问题,例如所有 setter 都可变(这常常导致不必要的副作用),日期时间字符串格式无法以一致的方式解析等等。最终,最佳解决方案是重新构建一个 API,而这正是 Temporal。
Temporal 是一个与 Intl 类似的命名空间,包括若干类和命名空间,每个类和命名空间都旨在处理日期与时间管理的某个特定方面。类可以被归为以下几组:
此外,还有另一个实用命名空间 Temporal.Now,提供以不同格式获取当前时间的方法。
Temporal 命名空间中包含许多类,但它们共享很多相似的方法。下表列出了每个类的所有方法(不包括类之间的转换方法):
下表总结了每个类可用的属性,让你了解每个类能表示哪些信息。
下表总结了各类之间存在的所有转换方法。
| 转换自 | ||||||||
| 转换为 | Instant/ | toInstant() | 先转换为 ZonedDateTime | |||||
| toZonedDateTimeISO() | / | toZonedDateTime() | toZonedDateTime() | PlainDate#toZonedDateTime()(作为参数传入) | 先转换为 PlainDate | |||
| 先转换为 ZonedDateTime | toPlainDateTime() | / | toPlainDateTime() | PlainDate#toPlainDateTime()(作为参数传入) | ||||
| toPlainDate() | toPlainDate() | / | 信息不重叠 | toPlainDate() | toPlainDate() | |||
| toPlainTime() | toPlainTime() | 信息不重叠 | / | 信息不重叠 | ||||
| 先转换为 PlainDate | toPlainYearMonth() | 信息不重叠 | / | 先转换为 PlainDate | ||||
| toPlainMonthDay() | 先转换为 PlainDate | / | ||||||
通过这些表格,你应该对如何使用 Temporal API 有了基本认识。
日历是一种组织日期的方法,通常按周、月、年和纪元划分时期。世界上大多数地区使用公历,但也在使用许多其他的日历,尤其是在宗教与文化背景下。默认情况下,所有与日历相关的 Temporal 对象都使用 ISO 8601 日历系统,该系统基于公历并定义了额外的周编号规则。Intl.supportedValuesOf() 列出了浏览器可能支持的大多数日历。这里我们简要概述日历系统是如何形成的,帮助你理解哪些因素可能因日历而异。
地球上有三个显著的周期性事件:绕太阳公转(一次 365.242 天)、月球绕地球公转(从一次新月到下一次新月约 29.53 天)、地球自转(从日出到日出约 24 小时)。每种文化对“一天”的衡量都是 24 小时。偶然的变化(如夏令时)不在日历范畴,而是时区信息的一部分。
在 Temporal 中,同一日历系统下的每个日期都由三个组件唯一标识:year、month 和 day。year 通常为正整数,但也可能为 0 或负数,并随时间单调递增。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 是任意整数,可以为 0 或负数,甚至可能随时间递减(通常用于最早的纪元)。
备注:始终将 era 和 eraYear 成对使用;不要只用其中一个。此外,为避免冲突,在指定年份时不要把 year 与 era/eraYear 混用。选择一种年份表示方式并保持一致。
注意以下对年份的错误假设:
除了 month 以外,一年中的月份还可以由 monthCode 唯一标识。monthCode 通常对应月份名称,但 month 不对应。例如在阴阳历中,若两个月份的 monthCode 相同,其中一个属于闰年而另一个不属于闰年,那么在闰月之后,它们的 month 值会因插入了一个额外的月份而不同。
备注:为避免冲突,在指定月份时不要混用 month 与 monthCode。选择一种月份表示方式并保持一致。如果你需要一年中月份的顺序(例如循环遍历月份)时,month 更有用;如果你需要月份的名称(例如用于储存生日)时,monthCode 更有用。
注意以下对月份的错误假设:
除了 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。
注意以下对“周”的错误假设:
所有 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 对象会拒绝构造超出其限制的日期/时间实例。这包括:
表示两个时间点之间的差值,可用于日期/时间运算。它基础表示为年、月、周、日、时、分、秒、毫秒、微秒和纳秒数值的组合。
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 |
启用 JavaScript 以查看此浏览器兼容性表。