新的 JavaScript Temporal 对象的实现开始在浏览器的实验性版本中推出。 这对网络开发者来说是个重大消息,因为在 JavaScript 中处理日期和时间将大大简化并实现现代化。
依赖于调度、国际化或对时间敏感的数据的应用程序将能够使用内置功能来高效、精确且一致地处理日期、时间、持续时间和日历。 距离实现稳定且跨浏览器的支持还有很长的路要走,而且随着实现的发展可能会有变化,但我们现在已经可以看看 Temporal 当前的情况、它令人兴奋之处以及它解决了哪些问题。
为了帮助您快速上手,本周在 MDN 上新增了超过 270 页的 Temporal 文档,其中包含详细的解释和示例。
什么是 JavaScript 时间(Temporal)
要理解 Temporal,我们可以看看 JavaScript 的 Date 对象。 1995 年 JavaScript 创立时, Date 对象是从 Java 早期有缺陷的 Date 实现复制而来的。 Java 在 1997 年就替换了这一实现,但 JavaScript 却在近 30 年的时间里一直沿用着同样的 API,尽管存在已知的问题。
JavaScript 的 Date 对象存在的主要问题在于它仅支持用户本地时间和协调世界时(UTC),并且不支持时区。此外,其解析行为非常不可靠,而且 Date 本身是可变的,这可能会引入难以追踪的错误。还有其他一些问题,比如跨越夏令时(DST)和历史历法变更的计算,这些问题一直以来都很难处理。
所有这些问题都使得在 JavaScript 中处理日期和时间变得复杂且容易出错,这可能会给某些系统带来严重后果。 大多数开发人员依靠像 Moment.js 和 date-fns 这样的专用库来更好地处理其应用程序中的日期和时间。
Temporal 被设计为 Date 对象的全面替代品,使日期和时间管理变得可靠且可预测。 Temporal 增加了对时区和日历表示的支持,提供了许多内置方法用于转换、比较、计算、格式化等操作。 其 API 接口包含超过 200 个实用方法,您可以在 MDN 上的 Temporal 文档中找到有关所有这些方法的信息。
核心概念
在 Temporal 中,关键概念是它具有瞬间(历史中的独特点)、实时时钟时间(区域时间)和持续时间。 其 API 具有这种总体结构来处理这些概念:
- 持续时间:
Temporal.Duration
两个时间点之间的差值 - 时间点:
- 独特的时刻:
- 作为时间戳:
Temporal.Instant
- 带有时区的日期时间:
Temporal.ZonedDateTime
- 作为时间戳:
- 不考虑时区的日期/时间(“普通”):
- 完整日期和时间:
Temporal.PlainDateTime
- 仅日期:
Temporal.PlainDate
- 年份和月份:
Temporal.PlainYearMonth
- 月份和日期:
Temporal.PlainMonthDay
- 年份和月份:
- 正是时候:
Temporal.PlainTime
- 仅日期:
- 完整日期和时间:
- 独特的时刻:
- 现在: 使用
Temporal.now
获取当前时间作为各种类的实例,或者以特定格式获取
时间的例子
Temporal 的一些最基本用法包括获取当前日期和时间的 ISO 字符串,但从下面的示例中我们可以看到,现在我们可以在许多方法中提供时区,这可以处理您可能自己进行的复杂计算:
// The current date in the system's time zone const dateTime = Temporal.Now.plainDateTimeISO(); console.log(dateTime); // e.g.: 2025-01-22T11:46:36.144 // The current date in the "America/New_York" time zone const dateTimeInNewYork = Temporal.Now.plainDateTimeISO("America/New_York"); console.log(dateTimeInNewYork); // e.g.: 2025-01-22T05:47:02.555
使用不同的日历也变得简单了,因为可以创建除公历之外的其他日历系统中的日期,例如希伯来历、中国历和伊斯兰历等。 下面的代码可以帮助您找出下一个中国新年是什么时候(很快就要到了!):
// Chinese New Years are on 1/1 in the Chinese calendar const chineseNewYear = Temporal.PlainMonthDay.from({ monthCode: "M01", day: 1, calendar: "chinese", }); const currentYear = Temporal.Now.plainDateISO().withCalendar("chinese").year; let nextCNY = chineseNewYear.toPlainDate({ year: currentYear }); // If nextCNY is before the current date, move forward by 1 year if (Temporal.PlainDate.compare(nextCNY, Temporal.Now.plainDateISO()) <= 0) { nextCNY = nextCNY.add({ years: 1 }); } console.log( `The next Chinese New Year is on ${nextCNY.withCalendar("iso8601").toLocaleString()}`, ); // The next Chinese New Year is on 1/29/2025 (at the time of writing)
使用 Unix 时间戳是一种非常常见的用例,因为许多系统(API、数据库)都使用这种格式来表示时间。 以下示例展示了如何获取以毫秒为单位的 Unix 纪元时间戳,从中创建一个瞬间,使用 Temporal.Now 获取当前时间,然后计算从现在到该 Unix 时间戳还有多少小时:
// 1851222399924 is our timestamp const launch = Temporal.Instant.fromEpochMilliseconds(1851222399924); const now = Temporal.Now.instant(); const duration = now.until(launch, { smallestUnit: "hour" }); console.log(`It will be ${duration.toLocaleString("en-US")} until the launch`); // "It will be 31,600 hr until the launch" <- @js-temporal/polyfill // "It will be PT31600H until the launch" <- Firefox Nightly
目前,在 Firefox 实现中, toLocaleString 不会输出本地化敏感的字符串,因此超过 ( PT31600H ) 的持续时间将以非本地化敏感的持续时间格式返回。 这可能会改变,因为这更多是一个设计决策,而非技术限制,因为格式化持续时间是可行的,所以填充实现和 Firefox 实现最终可能会趋同。 有很多值得强调的地方,但在 API 中我觉得有趣的一个模式是 compare() 方法,它能让你以一种优雅且高效的方式对持续时间进行排序:
const durations = [ Temporal.Duration.from({ hours: 1 }), Temporal.Duration.from({ hours: 2 }), Temporal.Duration.from({ hours: 1, minutes: 30 }), Temporal.Duration.from({ hours: 1, minutes: 45 }), ]; durations.sort(Temporal.Duration.compare); console.log(durations.map((d) => d.toString())); // [ 'PT1H', 'PT1H30M', 'PT1H45M', 'PT2H' ][
随着实验性实现的落地,现在是尝试 Temporal 并熟悉它将成为 JavaScript 中处理日期和时间的现代方法的好时机。