JavaScript 已经有 Date 了,为什么还需要 Temporal?

AI 概述
本文介绍JS全新**Temporal时间API**,用于解决传统Date解析不可靠、月份从零计算、时区支持差、对象可变、运算繁琐等诸多缺陷。Temporal划分精细化时间模型,区分纯日历时间、绝对时间点、时区时间与时长,提供Plain系列、Instant、ZonedDateTime、Duration等对象,支持纳秒精度、多历法、时区转换与便捷时间运算,适配各类业务场景,是现代化的JS时间解决方案,目前浏览器兼容性仍有限。
目录
文章目录隐藏
  1. Temporal 是什么?
  2. Temporal 怎么用?
  3. Temporal 有什么用?
  4. 兼容性
  5. 写在最后

在日常 JS 前端开发中,日期时间处理是绕不开的需求,但原生的 Date API 属实槽点满满。解析规则混乱、月份从 0 开始、不支持多时区、对象可变易出 BUG、缺少便捷的时间计算方法,很多时候我们只能手写兼容逻辑、依赖第三方库。为了解决这些长期存在的痛点,官方推出了全新的 Temporal API,重构了 JS 的时间处理体系,把模糊的时间概念精细化拆分,彻底替代老旧的 Date。本文详细拆解 Temporal 的设计思想、各类核心对象、使用场景与基础用法。

首先,说个结论

Temporal 是 JavaScript 中一套用于管理日期和时间的 API

结束了

当然不是

看到结论,你可能会有疑问:Javascript 中管理日期、时间不是已经有 Date 了吗

Javascript 中管理日期、时间

先观察下上面的两个 Date 实例,猜猜各自代表的日期是什么?

Date 实例代表日期是什么?

这是 Date 的一大弊端:解析器行为不可靠。

以上体现的是 Date 的两种解析用法,一种是日期字符串,另一种是分量数字参数。

  • 通过字符串的解析,虽然看着似乎结果正确,没什么问题,但它是不可靠的,以至于在 MDN 的语法介绍中用了强烈不推荐来表示它通过字符串的解析
  • 至于数字参数的解析,虽然结果似乎可靠,但某些分量参数却比较“反直觉”,如月份是有 0 开始计算,即一月份为 0,十二月份为 11

Date 还有许多其它的弊端,如:

  • 对时间的“建模”过于简单,很难区分“时间”与“时间点”;有时我们想表达的可能只是“某年某月某日”这个日期,但 Date 本质只能表示一个时间点,内部以 Unix 时间戳表示,这导致一些单纯表示“日期时间”的数据,被迫和具体的时间、时区联系起来
  • 只支持 UTC 时间与用户本地设备的时间,不支持其它时区的时间,也没有对应的 API 能够处理、切换时区
  • 缺少关于时间计算的 API,如两个时间的长短比较,加减运算等,这些基本都需要开发者自己设计处理或借助第三方库
  • 不支持格里高利历(公历)以外的历法
  • 日期对象是可变的,Date 提供了一些 set*方法,用于改变日期等分量,这也常被用来实现如天数+1 等操作,但是这些方法均会改变原对象,容易产生意料之外的副作用

Date 其它的弊端

Date 从 1995 年跟随 JavaScript 诞生之后,至今已有 30 多年时间,它能够处理一些简单的时间问题,但现代 Web 应用面对的时间问题已经远远超出了 Date 对象能够清晰表达的范围了

Temporal 的出现,就是为了修复 Date 存在的弊端,为 JavaScript 带来现代化的日期/时间 API

Temporal 是什么?

Temporal 是 ECMAScript 提供的新一代日期与时间 API,旨在解决传统的 Date API 在日期、时间等方面长期存在的问题。

它采用 RFC 9557 指定的格式进行序列化/反序列化表达日期时间,该格式基于 ISO 8601 / RFC 3339,结构关系大致可参考如下:

Temporal 是什么?

Temporal 为 Js 建立了一套全新的、更现代化的时间模型,它支持纳秒级的精度,拥有明确的时间/时间点区分,能够实现日期时间的运算及长度表示等。

Tips:高精度不意味着你的系统实际拥有纳秒级时钟,它主要意味着 Temporal 的时间值模型可以表达这种精度。

Temporal 怎么用?

Temporal 并不像 Date 一样是一个类;它更像一个全局对象(命名空间);在 Js 中,就像 Math、JSON、Intl 等 API 位于顶层命名空间,可以通过 Temporal.*调用内部的属性或类

关于 Temporal API 的结构关系大致可参考下图:

Temporal API 的结构关系

时刻

主要用于描述“什么时候?”,按含义场景可继续细分为时间与时间点。

时间

即没有时区的“日历时间”,它不代表时间轴上固定的一点,主要是描述日历和钟表上的一个时间。

如:2026-09-07,它表示 2026 年 9 月 7 日;它只表示这一天,并不能表示时间轴上固定的一点,在不同时区,2026-09-07 可能代表时间轴上不同的点,并不一定是“同一天”。

在 Temporal 中,提供了一系列对象(类)来描述这种不需要时区概念的时间,它们有一个统一的前缀—Plain。

PlainDate

表示一个日历日期(即不含时间与时区),它包含年、月、日字段,及相关的日历系统(历法)。

Temporal 主要提供了两种方式来创建一个 PlainDate 对象。

第一种为构造函数,语法大致如下:

new Temporal.PlainDate(year, month, day, calendar?)
  • year:一个 number 值,会截断为一个整数;表示 ISO 日历系统中的年份;
  • month:一个 number 值,会截断为一个整数;表示 ISO 日历系统中的月份;
  • day:一个 number 值,会截断为一个整数;表示 ISO 日历系统中的月份日期;
  • calendar:可选值,一个字符串值,表示使用的日历系统,默认为 iso8601;可通过如下语句获取支持的日历类型。
    Intl.supportedValuesOf('calendar')
    // return: ["chinese","gregory","indian","iso86e1","japanese", ...]

Tips:无论使用哪种历法,年、月、日值均需符合 ISO 8601 日历系统。

另一种为通过静态方法from(),传入一个日期的序列化字符串创建对象,字符串格式符合 RFC 9557 格式如下:

YYYY-MM-DD[u-ca=calendar_id]

两个方式的具体使用如下:

new Temporal.PlainDate(2026, 9, 9);
new Temporal.PlainDate(2026, 9, 9, 'chinese');
Temporal.PlainDate.from('2026-09-09');
Temporal.PlainDate.from('2026-09-09[u-ca=chinese]');

PlainDate 除了用于创建实例的 from()方法,还有一个用于比较的方法

  • compare():传入两个日期,返回一个数字(-1/0/1),表示第一个日期是早于、等于还是晚于第二个日期
  • 值需为 Temporal.PlainDate.from()可转换的形式
    const date1 = new Temporal.PlainDate(2026, 9, 7); // 2026-09-07
    const date2 = new Temporal.PlainDate(2026, 9, 9); // 2026-09-09
    Temporal.PlainDate.compare (date1, date2)         // -1

PlainDate 提供了一系列实例属性,方便我们获取日历日期的信息:

  • calendarId:一个字符串,表示关联的日历系统;
  • day:一个正整数,表示日期所在月份中的索引(由 1 开始);
  • dayOfWeek:一个正整数,表示该日期是一周内的第几天,范围为 1 至 daysInWeek;如 1 通常表示星期一(与所用日历系统相关);
  • dayOfYear:一个正整数,表示该日期是一年内的第几天;
  • daysInWeek:一个正整数,表示该日期所在的一周共有几天;
  • daysInMonth:一个正整数,表示该日期所在月份的天数;
  • daysInYear:一个正整数,表示该日期所在年份的天数;
  • weekOfYear:一个正整数,表示该日期为所在年份(yearOfWeek)的第几周;
  • month:一个正整数,表示该日期所在年份中的月份索引;年份中的第一个月索引为 1,最后一个月索引为 monthsInYear,取决于日历系统;
  • monthCode:一个字符串,表示该日期的月份;一般为’M’加上月份编号,对于闰月,则会在月份编号后加’L’;
  • monthsInYear:一个正整数,表示该日期所在年份的月数(由 1 开始计数);
  • year:一个正整数,表示该日期的年份;
  • yearOfWeek:一个正整数,表示该日期所在周的年份;通常与 year 一致,但一年中的前几天或最后几天可能被划入上一年的最后一周或下一年的第一周,故值可能与 year 相差 1;
  • era:一个小写字符串,表示该日期的纪元;对于不使用纪元的日历系统,则返回 undefined;
  • eraYear:一个非负整数,表示该日期在纪元内的年份索引,年份索引一般由 1(常见)或 0 开始;对于不使用纪元的日历系统,则返回 undefined;
  • inLeapYear:一个布尔值,表示该日期是否为闰年。
const date = new Temporal.PlainDate(2026, 9, 9);
date.calendarId; // "iso8601"
date.day;        // 9
date.dayOfWeek;  // 3
date.dayOfYear;  // 252
date.daysInWeek; // 7
date.weekOfYear; // 37
date.month;      // 9
date.monthCode;  // 'M09'
date.year;       // 2026
date.inLeapYear; // false

PlainDate 还提供了一系列实例方法,用于操作、计算日期,常见如下:

  • add():返回一个新的 PlainDate 对象,表示该日期增加给定的持续时间后的日期,值需为 Temporal.Duration.from()可转换的形式;
  • subtract():返回一个新的 PlainDate 对象,表示该日期减少给定的持续时间后的日期;
  • equals():用于与另一个日期进行比较,相等返回 true,否则为 false,值需为 Temporal.PlainDate.from()可转换的形式;
  • since():返回一个新的 Temporal.Duration 对象,表示该日期与传入日期之间的持续时长;
  • 处理以上提到的实例方法,还有将日期转换为其它格式的 toPlain*方法与序列化的 toString 等方法,需要可自行了解。

const date1 = new Temporal.PlainDate(2026, 9, 9); // 2026‑09‑09

const date2 = date1.add({ days: 1 }); // 2026‑09‑10

const date3 = date2.subtract({ years: 1, months: 1, days: 1 });
// 2026‑09‑10 减去 1 年 1 月 1 天 → 2025‑08‑09
console.log(date3.toString());  // “2025-08-09”

const dur = date1.since(date2);
// date1 相对于 date2 的时间差:往前 1 天
// dur = Temporal.Duration { days: -1 }

date1.equals(‘2026-09-09’);
// equals 支持字符串参数,返回 true

适用于表示节假日、截止日期、日期计算等场景。

PlainTime

表示一个时间(不包含日期及时区),它包含时、分、秒、毫秒、微秒、纳秒等字段。

用法与实例包含的方法与 PlainDate 是类似的,本质上 Plain 系列对象均差不多,只不过针对的情况不同;实例属性存在一些差别。

// PlainTime(hour?, minute?, second?, millisecond?, microsecond?, nanosecond?)
new Temporal.PlainTime (11,25,17)
// HH:mm:ss.sssssssss
Temporal.PlainTime.from('11:15:17')

PlainTime 包含的实例属性如下:

  • hour:一个 0~23 之间的整数,表示时间的小时部分;
  • minute:一个 0~59 之间的整数,表示时间的分钟部分;
  • second:一个 0~59 之间的整数,表示时间的秒数部分;
  • millisecond:一个 0~999 之间的整数,表示时间的毫秒部分;
  • microsecond:一个 0~999 之间的整数,表示时间的微秒部分;
  • nanosecond:一个 0~999 之间的整数,表示时间的纳秒部分。
const time = new Temporal.PlainTime (11, 15, 17);
time.hour;   // 11
time.minute; // 15
time.second; // 17

适用于表示如营业时间、时间计算等场景。

PlainDateTime

表示一个日期与时间(不含时区),它包含日期与时间包含的字段及相关的日历系统(历法),相当于 PlainDate 与 PlainTime 的组合:

PlainDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar)
new Temporal.PlainDateTime(2026, 9, 9, 11, 15, 17)
// YYYY-MM-DDTHH:mm:ss.sssssssss[u-ca=calendar_id]
Temporal.PlainDateTime.from('2026-09-09T11:15:17')

它包含 PlainDate 与 PlainTime 的大部分属性与方法,适用于表示如用户预约、会议时间等场景。

PlainYearMonth

表示日历日期的年与月(不含日与时区),默认表示为 ISO 8601 日历日期,包含年份与月份字段及相关的日历系统。

在非 ISO 8601 的日历系统中,可能会有日字段,用于消除年&月可能存在的歧义。

使用上基本与 PlainDate 类似,适用于表示月度账单、月度报表、周期账单等场景。

PlainMonthDay

表示日历日期的月与日(不含年份与时区),默认表示为 ISO 8601 日历日期,包含月份与日期字段及相关的日历系统

当使用非 ISO 8601 的日历系统时,可能会有年份字段,用于区分月份与日期。

本质上,PlainYearMonth 相当于 PlainDate 对象的月&日部分,不含年份。同时由于月&日的含义可能因年份而异,所以该对象本身不提供太多功能(如比较、加减法等)。

适用于表示如生日、纪念日等场景。

时间点

即时间轴上确定的一点,是一个“绝对时间点”。

Instant

表示一个具有纳秒级精度的唯一时间点,本质上表示为自 Unix 纪元(1970 年 1 月 1 日午夜,UTC)以来的纳秒数,不包含任何时区或日历系统。

Instant 在语义上与原先的 Date 类似,都是封装一个时间戳,只不过 Instant 的精度更细,达到了纳秒级。同时,它避免了 Date 的缺陷,没有隐含的日历系统与时区,如果需要读取日期或时间信息,需给定具体时区将其转换为 Temporal.ZonedDateTime。

我们可以通过对应的构造函数创建一个 Instant 实例,语法如下:

new Temporal.Instant(epochNanoseconds)
  • epochNanoseconds:一个 BigInt 值,表示自 Unix 纪元以来经过的纳秒数。除了构造方法外,Temporal.Instant 还提供了 3 个静态方法用于创建 Instant 对象。
  • from():从另一个 Temporal.Instant 对象或一个 RFC 9557 格式字符串创建一个新的 Temporal.Instant 对象。Temporal.Instant 对象
  • fromEpochMilliseconds():从自 Unix 纪元以来的毫秒数创建一个新的 Temporal.Instant 对象,可用于将 Date 对象转换为 Instant 对象。
  • fromEpochNanoseconds():从自 Unix 纪元以来的纳秒数创建一个新的 Temporal.Instant 对象。

使用大致如下:

new Temporal.Instant(1788807377000000000n)
Temporal.Instant.from('2026-09-07T18:56:17Z');
Temporal.Instant.fromEpochMilliseconds(Date.now());
Temporal.Instant.fromEpochNanoseconds (1788807377000000000n);

创建的 Instant 实例主要拥有两个实例属性

  • epochMilliseconds:一个整数,表示自 Unix 纪元以来经过的毫秒数;等效于 epochNanoseconds 除以并取整
  • epochNanoseconds:一个整数,表示自 Unix 纪元以来经过的纳秒数

除了实例属性,还有许多实用的实例方法,如:

  • add()与 subtract():返回一个新的 Instant 对象,表示对该时间点增加或减少给定的持续时间,值需为能被 Temporal.Duration.from()解析的形式;
  • equals():将当前时间点与另一个 Instant 进行比较,相等为 true,反之为 false;其等效于
    Temporal.Instant.compare(this, other) === 0
  • since()与 until():传入一个 Instant 对象,返回一个 Duration 对象,表示当前时间点与传入时间点之间的持续时长;
  • since 主要计算另一个时间点到当前时间点的时长;而 until 计算当前时间点到另一个时间点的时长;
  • round():传入一个单位(如:hour、minute 等),返回一个新的 Instant 对象,表示当前时间点四舍五入到给定单位后的时间点;
  • toZonedDateTimeISO():返回一个新的 ZonedDateTime 对象,该对象使用 ISO 8601 日历系统表示指定时区中的此时刻。
/**
 * @param timeZone 可以是 字符串 或 Temporal.ZonedDateTime 实例,表示使用的时区
 * 字符串,表示时区的标识符,如'Asia/Shanghai'
 * 支持值可参考 Intl.supportedValuesOf('timeZone')
 * Temporal.ZonedDateTime 实例,会使用实例中的时区信息
 */
toZonedDateTimeISO(timeZone)
/** 使用示例 */
const date = new Temporal.Instant(1788807377000000000n)
date.toZonedDateTimeISo('Asia/Shanghai')

ZonedDateTime

表示带有时区的日历日期与时间,相当于 Instant 与时区、日历系统的结合。

它可同时表示历史上的某一时间点(如 Temporal.Instant)和本地实际时间(如 Temporal.PlainDateTime),它包含时间、时区及日历系统等信息;时区用于时间点与本地时间之间进行转换,而日历系统则用于解释本地时间。

同样可通过构造方法或静态 from()方法创建一个 ZonedDateTime 实例:

/**
 * ZonedDateTime(epochNanoseconds, timeZone, calendar?)
 * @param epochNanoseconds 时间戳,单位纳秒,BigInt
 * @param timeZone         时区标识,如'Asia/Shanghai'
 * @param calendar         日历,如'iso8601'
 */
new Temporal.ZonedDateTime(1788807377000000000n, 'Asia/Shanghai', 'iso86e1')

/**
 * YYY-MM-DD T HH:mm:ss.sssssssss Z/HH:mm [time_zone_id] [u-ca=calendar_id]
 * Tips:此处空格用于分隔辨识,实际无需包含
 */
Temporal.ZonedDateTime.from('2026-09-07T18:56:17Z[Asia/Shanghai][u-ca=iso8601]')

ZonedDateTime 的实例包含以上介绍对象实例的大部分属性与方法,可以说是 Temporal 中最“丰富”的类。

时长

表示一段时间,即时长、“多久?”,如一天、24 小时、十分钟等。

Duration

表示两个时间点之间的差值,一般可用于日期/时间的计算。

支持使用构造方法或静态 from()方法创建实例。

使用构造方法或静态 from()方法创建实例

Duration 常用于计算时间差,既可以两个时长之间进行计算,也可以与日期/时间进行计算

现在

表示此时此刻,即现在

Now

与其它 Temporal 对象不一样的是,Now 并不是一个构造函数,也不能创建对应的 Now 实例,Temporal.Now 上的所有属性与方法都是静态的(类似于 Math),Temporal 为其提供了一系列以不同格式获取当前时间的方法:

  • instant():返回一个表示当前时间点的 Instant 对象;
  • plainDateISO():返回一个表示当前日期的 PlainDate 对象,采用 ISO 8601 日历系统,可传入一个参数,表示使用的时区;
  • plainTimeISO():返回一个表示当前时间的 PlainTime 对象,可传入一个参数,表示使用的时区;
  • plainDateTimeISO():返回一个表示当前日期时间的 PlainDateTime 对象,采用 ISO 8601 日历系统,可传入一个参数,表示使用的时区;
  • timeZineId():返回一个表示当前系统时区的时区标识符;
  • zonedDateTimeISO():返回一个表示当前时间点的 ZonedDateTime 对象,采用 ISO 8601 日历系统,可传入参数指定使用的时区,默认使用本地时区。

Temporal 有什么用?

Temporal API 为 JavaScript 重新梳理了日期与时间的各种概念,并通过不同的 API 区分表示它们。

不需要再像传统 Date 一样,用一个对象处理所有时间问题,而是可以根据实际业务选择需要的不同类型,如:

  • 哪一天 — PlainDate
  • 几点 — PlainTime
  • 哪一天几点 — PlainDateTime
  • 哪年哪月 — PlainYearMonth
  • 哪月哪日 — PlainMonthDay
  • 时间轴上的哪个瞬间 — Instant
  • 某个时区的当地时间 — ZonedDateTime
  • 持续了多久 — Duration
  • 现在是什么时间 — Now

兼容性

Temporal API 还处于比较“新”的阶段,各大主流浏览器支持不一。

Temporal API 兼容性

写在最后

Temporal 通过对日期、时刻、时区、时长进行清晰建模,从根源上规避了传统 Date 的设计缺陷,提供了纳秒级时间精度、多历法与时区支持、不可变对象以及原生时间计算能力。受浏览器兼容现状限制,现阶段生产环境落地仍需酌情考量,但它代表了 JavaScript 日期处理的发展方向。熟悉 Temporal 的类型划分与 API 设计思路,能够优化时间相关业务代码,降低潜在 bug,逐步摆脱对第三方时间库的依赖。

以上关于JavaScript 已经有 Date 了,为什么还需要 Temporal?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

33

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » JavaScript 已经有 Date 了,为什么还需要 Temporal?

发表回复