过去这些年,Dayjs 之所以能火,很大程度上不是因为它做了什么惊天动地的新能力,而是因为原生 Date 实在太难用了。
解析不稳,时区容易错,setMonth 这类 API 还带副作用,稍不注意就会掉坑里。
所以前端这些年才会长期维持一个奇怪局面:明明“时间”是最基础的能力之一,但大家写项目时,几乎都默认要额外装一个日期库。
而 Temporal 的出现,本质上就是标准库亲自下场,把这件事重新做一遍。
如果你今天主要用 Dayjs 来做这些事:
- 解析时间
- 格式化时间
- 做加减运算
- 比较时间先后
- 处理时区
- 表达“只有日期”或“只有时间”的业务语义
那么 Temporal 确实已经可以覆盖绝大部分核心场景。
但要先把一句话说清楚:
Temporal不是Dayjs的同名替身,它不是把dayjs().add().format()原样搬进标准库,而是换了一套更清晰的对象模型。
也就是说,想通过 Temporal 完全替换 Dayjs,关键不是“找一个一模一样的 API”,而是把你的时间代码按语义重新拆开。
先说结论
如果你只想先知道结论,可以直接看这几条:
Temporal可以替掉Dayjs的大部分核心能力,尤其是解析、运算、比较、时区和不可变数据这几块。Temporal并不是单对象链式 API,而是PlainDate、PlainDateTime、Instant、ZonedDateTime、Duration等一组对象协同工作。Dayjs最常用的format('YYYY-MM-DD HH:mm:ss')这种 token 格式化,Temporal没有直接内置同款 API,通常要改成Intl或自己封一层格式化函数。Dayjs的自定义格式解析插件很方便,但Temporal不提供任意 token 字符串解析,复杂输入需要你自己写解析函数。- 到了 2026 年,
Temporal在新版 Chrome、Edge、Firefox 和 Deno 上已经可用,但Node.js、Bun、Safari、iOS Safari侧仍然不能裸用,想“完全替换”就得配polyfill。
第一步:先解决运行时支持
如果你跑在新版浏览器里,可能已经能直接用 Temporal。
但如果你的代码跑在下面这些环境里:
Node.jsBunSafariiOS Safari
那就不要直接上原生 Temporal,而是先接上 polyfill。
pnpm add @js-temporal/polyfill
import { Temporal as TemporalPolyfill } from "@js-temporal/polyfill";
export const TemporalX = globalThis.Temporal ?? TemporalPolyfill;
后面所有示例里的 Temporal,你都可以理解成:
const Temporal = globalThis.Temporal ?? TemporalPolyfill;
这一层很重要。
因为很多人以为自己是在“迁移 API”,实际上第一个坑就死在运行时兼容上了。
第二步:先换思维,不要急着换 API
Dayjs 的思路很统一:
- 基本上什么都先变成一个
dayjs()对象 - 然后在这个对象上链式操作
而 Temporal 的思路不是这样。
它把不同语义的“时间”拆开了。
| 业务语义 | Dayjs 常见写法 | Temporal 推荐对象 |
|---|---|---|
| 只有日期,比如生日、账单日、签到日 | dayjs('2026-05-06') | Temporal.PlainDate |
| 只有时间,比如每天 09:30 | dayjs(...).format('HH:mm') | Temporal.PlainTime |
| 日期 + 时间,但还没绑定时区 | dayjs('2026-05-06 14:30:00') | Temporal.PlainDateTime |
| 绝对时间戳,比如数据库 created_at | dayjs(value) / dayjs.utc(value) | Temporal.Instant,前提是输入本身就是带偏移量的绝对时间 |
| 带时区的真实业务时间,比如上海时间 9 点开会 | dayjs.tz(...) | Temporal.ZonedDateTime |
| 时间差 | dayjs.duration(...) | Temporal.Duration |
这一步是迁移成功的关键。
很多人一上来就想把所有 dayjs() 都换成 Temporal.ZonedDateTime,结果代码越写越乱。
正确姿势是:
- 先问自己这段值到底是“日期”还是“瞬时点”
- 再选合适的
Temporal类型
当前时间、固定时间、UTC 时间怎么换
这是最常见的一批替换。
1. 当前本地时间
// Dayjs
const now = dayjs();
// Temporal
const now = Temporal.Now.zonedDateTimeISO();
如果你只是要“当前日期”,那就不要拿一个完整日期时间对象硬扛:
// Dayjs
const today = dayjs().format("YYYY-MM-DD");
// Temporal
const today = Temporal.Now.plainDateISO();
2. 解析一个纯日期
// Dayjs
const date = dayjs("2026-05-06");
// Temporal
const date = Temporal.PlainDate.from("2026-05-06");
3. 解析一个不带时区的日期时间
// Dayjs
const dateTime = dayjs("2026-05-06 14:30:00");
// Temporal
const dateTime = Temporal.PlainDateTime.from("2026-05-06T14:30:00");
注意这里的区别。
Temporal.PlainDateTime 表达的是“墙上时钟看到的时间”,它本身不带时区,不等于某个绝对瞬时点。
4. 处理 UTC / 绝对时间
// Dayjs
const utcTime = dayjs.utc("2026-05-06T06:30:00Z");
// Temporal
const instant = Temporal.Instant.from("2026-05-06T06:30:00Z");
如果你要把它展示成上海时区时间:
const shanghaiTime = instant.toZonedDateTimeISO("Asia/Shanghai");
console.log(shanghaiTime.toString());
// 2026-05-06T14:30:00+08:00[Asia/Shanghai]
5. Unix 时间戳
// Dayjs
const t1 = dayjs.unix(1714977000);
const unix = dayjs().unix();
// Temporal
const t1 = Temporal.Instant.fromEpochMilliseconds(1714977000 * 1000);
const unix = Math.floor(Temporal.Now.instant().epochMilliseconds / 1000);
格式化怎么替换
这是 Dayjs 用户迁移到 Temporal 时最别扭的地方。
因为 Dayjs 最顺手的能力之一就是:
dayjs().format("YYYY-MM-DD HH:mm:ss");
而 Temporal 不提供一套一模一样的 token 格式化器。
这不代表它不能格式化,而是思路变了。
方案一:固定机器格式,直接用 toString() 或自己拼
如果你要的是固定格式,例如 YYYY-MM-DD、YYYY-MM-DD HH:mm:ss,那自己拼往往最稳。
function pad(value, length = 2) {
return String(value).padStart(length, "0");
}
function formatPlainDate(date) {
return `${date.year}-${pad(date.month)}-${pad(date.day)}`;
}
function formatPlainDateTime(dateTime) {
return [`${dateTime.year}-${pad(dateTime.month)}-${pad(dateTime.day)}`, `${pad(dateTime.hour)}:${pad(dateTime.minute)}:${pad(dateTime.second)}`].join(" ");
}
const date = Temporal.PlainDate.from("2026-05-06");
const dateTime = Temporal.PlainDateTime.from("2026-05-06T14:30:45");
console.log(formatPlainDate(date));
// 2026-05-06
console.log(formatPlainDateTime(dateTime));
// 2026-05-06 14:30:45
对应的 Dayjs 写法:
console.log(dayjs("2026-05-06").format("YYYY-MM-DD"));
console.log(dayjs("2026-05-06 14:30:45").format("YYYY-MM-DD HH:mm:ss"));
方案二:面向用户显示,改用 Intl
如果你要的是“给用户看”的本地化格式,那 Intl 比 Dayjs 那套 token 更合理。
const meeting = Temporal.PlainDateTime.from("2026-05-06T14:30:00");
console.log(
meeting.toLocaleString("zh-CN", {
dateStyle: "full",
timeStyle: "medium"
})
);
如果是带时区对象:
const meeting = Temporal.PlainDateTime.from("2026-05-06T14:30:00").toZonedDateTime("Asia/Shanghai");
console.log(
meeting.toLocaleString("zh-CN", {
dateStyle: "full",
timeStyle: "medium"
})
);
对应的 Dayjs 写法往往是:
dayjs.extend(localizedFormat);
console.log(dayjs("2026-05-06 14:30:00").format("LLLL"));
方案三:封一层最常用 token 兼容函数
如果你项目里满地都是:
YYYY-MM-DDYYYY-MM-DD HH:mm:ssHH:mm:ss
那与其全项目手工改字符串,不如自己封一层有限 token formatter。
function formatTemporal(value, pattern = "YYYY-MM-DD HH:mm:ss") {
const source = value instanceof Temporal.ZonedDateTime ? value : value instanceof Temporal.PlainDateTime ? value : value instanceof Temporal.PlainDate ? value.toPlainDateTime(Temporal.PlainTime.from("00:00:00")) : value.toZonedDateTimeISO("UTC");
const year = String(source.year);
const month = String(source.month).padStart(2, "0");
const day = String(source.day).padStart(2, "0");
const hour = String(source.hour ?? 0).padStart(2, "0");
const minute = String(source.minute ?? 0).padStart(2, "0");
const second = String(source.second ?? 0).padStart(2, "0");
return pattern.replaceAll("YYYY", year).replaceAll("MM", month).replaceAll("DD", day).replaceAll("HH", hour).replaceAll("mm", minute).replaceAll("ss", second);
}
const value = Temporal.PlainDateTime.from("2026-05-06T14:30:45");
console.log(formatTemporal(value, "YYYY-MM-DD"));
console.log(formatTemporal(value, "YYYY-MM-DD HH:mm:ss"));
这不是说你要重写一个完整的 Dayjs,而是说你可以把业务里最常见的 3 到 5 种格式先吃掉,迁移成本会立刻下降很多。
加减时间怎么替换
这一块 Temporal 其实很顺。
1. 加减天、月、年
// Dayjs
const a = dayjs("2026-05-06").add(7, "day");
const b = dayjs("2026-05-06").subtract(1, "month");
// Temporal
const a = Temporal.PlainDate.from("2026-05-06").add({ days: 7 });
const b = Temporal.PlainDate.from("2026-05-06").subtract({ months: 1 });
2. 处理月底这种边界
// Dayjs
console.log(dayjs("2024-01-31").add(1, "month").format("YYYY-MM-DD"));
// 2024-02-29
// Temporal
console.log(Temporal.PlainDate.from("2024-01-31").add({ months: 1 }).toString());
// 2024-02-29
3. 日期时间加减
// Dayjs
const result = dayjs("2026-05-06 14:30:00").add(2, "hour").subtract(15, "minute");
// Temporal
const result = Temporal.PlainDateTime.from("2026-05-06T14:30:00").add({ hours: 2 }).subtract({ minutes: 15 });
比较、判等、diff 怎么替换
1. 比较大小
// Dayjs
dayjs(a).isBefore(b);
dayjs(a).isAfter(b);
// Temporal
Temporal.Instant.compare(a, b) < 0;
Temporal.Instant.compare(a, b) > 0;
如果你比较的是纯日期:
Temporal.PlainDate.compare(dateA, dateB) < 0;
2. 判断是否相等
// Dayjs
dayjs(a).isSame(b);
// Temporal
a.equals(b);
3. 判断“是否同一天”
// Dayjs
dayjs(a).isSame(b, "day");
// Temporal
const sameDay = a.toPlainDate().equals(b.toPlainDate());
如果 a、b 本身就是 PlainDate,那直接 a.equals(b) 即可。
4. 时间差
// Dayjs
const days = dayjs("2026-05-10").diff(dayjs("2026-05-06"), "day");
// Temporal
const days = Temporal.PlainDate.from("2026-05-06").until(Temporal.PlainDate.from("2026-05-10"), {
largestUnit: "day"
}).days;
如果你比较的是带时区的绝对时间:
const start = Temporal.Instant.from("2026-05-06T00:00:00Z");
const end = Temporal.Instant.from("2026-05-06T01:30:00Z");
const diff = start.until(end, { largestUnit: "hour" });
console.log(diff.hours); // 1
console.log(diff.minutes); // 30
startOf / endOf 怎么替换
Dayjs 的 startOf() / endOf() 很顺手,但 Temporal 没有照着抄一遍。
不过这不难,自己封一下反而更清楚。
1. 一天的开始和结束
// Dayjs
const start = dayjs(value).startOf("day");
const end = dayjs(value).endOf("day");
// Temporal
const zdt = Temporal.PlainDateTime.from("2026-05-06T14:30:00").toZonedDateTime("Asia/Shanghai");
const start = zdt.startOfDay();
const end = zdt.startOfDay().add({ days: 1 }).subtract({ nanoseconds: 1 });
2. 一个月的开始和结束
function startOfMonth(zdt) {
return zdt.with({
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
nanosecond: 0
});
}
function endOfMonth(zdt) {
return startOfMonth(zdt).add({ months: 1 }).subtract({ nanoseconds: 1 });
}
const source = Temporal.PlainDateTime.from("2026-05-06T14:30:00").toZonedDateTime("Asia/Shanghai");
console.log(startOfMonth(source).toString());
console.log(endOfMonth(source).toString());
时区处理怎么替换
这部分是 Temporal 最大的价值之一。
因为 Date 和很多旧代码最大的问题就在于:
- 到底当前值是本地时间,还是 UTC,还是某个业务时区时间
- 到底是在“解释一个墙上时间”,还是在“转换一个绝对瞬时点”
Temporal 把这两件事分得很清楚。
场景一:把“上海时间 2026-05-06 09:00”解释成一个带时区的业务时间
// Dayjs
dayjs.extend(utc);
dayjs.extend(timezone);
const meeting = dayjs.tz("2026-05-06 09:00:00", "Asia/Shanghai");
// Temporal
const meeting = Temporal.PlainDateTime.from("2026-05-06T09:00:00").toZonedDateTime("Asia/Shanghai");
这一步表示的是:
“2026-05-06 09:00:00” 这串墙上时间,属于上海时区。
场景二:把同一个瞬时点换个时区展示
// Dayjs
const shanghai = dayjs.tz("2026-05-06 09:00:00", "Asia/Shanghai");
const newYork = shanghai.tz("America/New_York");
// Temporal
const shanghai = Temporal.PlainDateTime.from("2026-05-06T09:00:00").toZonedDateTime("Asia/Shanghai");
const newYork = shanghai.withTimeZone("America/New_York");
场景三:后端给你 UTC 时间戳,前端按用户时区展示
// Dayjs
const result = dayjs.utc("2026-05-06T01:00:00Z").tz("Asia/Shanghai");
// Temporal
const result = Temporal.Instant.from("2026-05-06T01:00:00Z").toZonedDateTimeISO("Asia/Shanghai");
这类代码迁完后,可读性会高很多。
因为你一眼就能看出:
Instant是绝对时间ZonedDateTime是带时区展示
相对时间、duration 怎么替换
1. Duration 对象
// Dayjs
dayjs.extend(duration);
const d = dayjs.duration({ hours: 2, minutes: 30 });
// Temporal
const d = Temporal.Duration.from({ hours: 2, minutes: 30 });
2. fromNow / toNow 这类相对时间
Temporal 本身不提供 fromNow() 这种文案函数,但配合 Intl.RelativeTimeFormat 就够用了。
// Dayjs
dayjs.extend(relativeTime);
const text = dayjs("2026-05-10").from(dayjs("2026-05-06"));
// Temporal
const rtf = new Intl.RelativeTimeFormat("zh-CN", { numeric: "auto" });
const start = Temporal.PlainDate.from("2026-05-06");
const end = Temporal.PlainDate.from("2026-05-10");
const diff = start.until(end, { largestUnit: "day" });
const text = rtf.format(diff.days, "day");
console.log(text);
// 4 天后
如果你要的是“2 小时前”“3 分钟后”这类文案,也可以按同样方式封装。
自定义格式解析怎么替换
这是 Dayjs 相比 Temporal 仍然很方便的一点。
比如你以前可能会这样写:
// Dayjs + customParseFormat
dayjs("20260506 143045", "YYYYMMDD HHmmss");
Temporal 不提供这种“任意 token 输入直接解析”的能力。
它更偏向:
- 解析标准格式
- 或者你先把输入处理干净,再构造对象
比如:
function parseCompactDateTime(input) {
const match = input.match(/^(\d{4})(\d{2})(\d{2})\s(\d{2})(\d{2})(\d{2})$/);
if (!match) {
throw new Error("Invalid datetime format");
}
const [, year, month, day, hour, minute, second] = match;
return Temporal.PlainDateTime.from({
year: Number(year),
month: Number(month),
day: Number(day),
hour: Number(hour),
minute: Number(minute),
second: Number(second)
});
}
const value = parseCompactDateTime("20260506 143045");
console.log(value.toString());
// 2026-05-06T14:30:45
这看起来比 Dayjs 多写了几行,但好处是:
- 解析规则完全透明
- 不依赖插件黑盒
- 失败条件更明确
给你一套最实用的迁移对照
如果你要从 Dayjs 批量迁 Temporal,下面这张对照表最有用。
| Dayjs 写法 | Temporal 写法 |
|---|---|
dayjs() | Temporal.Now.zonedDateTimeISO() |
dayjs('2026-05-06') | Temporal.PlainDate.from('2026-05-06') |
dayjs('2026-05-06 14:30:00') | Temporal.PlainDateTime.from('2026-05-06T14:30:00') |
dayjs.utc(value) | Temporal.Instant.from(value),前提是 value 本身带 Z 或偏移量 |
dayjs.tz(value, 'Asia/Shanghai') | Temporal.PlainDateTime.from(value).toZonedDateTime('Asia/Shanghai') |
value.format('YYYY-MM-DD') | plainDate.toString() 或自定义 formatter |
value.format('LLLL') | value.toLocaleString('zh-CN', options) |
value.add(1, 'day') | value.add({ days: 1 }) |
value.subtract(1, 'month') | value.subtract({ months: 1 }) |
value.diff(other, 'day') | value.until(other, { largestUnit: 'day' }).days |
value.isBefore(other) | Temporal.PlainDate.compare(a, b) < 0 或 Temporal.Instant.compare(a, b) < 0 |
value.isSame(other) | value.equals(other) |
value.startOf('day') | zdt.startOfDay() |
value.endOf('day') | zdt.startOfDay().add({ days: 1 }).subtract({ nanoseconds: 1 }) |
dayjs.duration(...) | Temporal.Duration.from(...) |
value.unix() | Math.floor(instant.epochMilliseconds / 1000) |
如果你真想“完全替掉 Dayjs”,建议按这个顺序迁
不要全局搜 dayjs( 然后暴力替换。
那样你很快就会发现:
- 有的是纯日期
- 有的是带时区时间
- 有的是 UTC 时间戳
- 有的是只拿来格式化
- 有的是自定义格式输入
更稳的迁法是:
第一批先迁最容易的
addsubtractdiffisBeforeisAfterisSameunix
这些最容易替换,而且改完收益立刻可见。
第二批迁语义最清晰的
- 生日
- 签到日期
- 账单日期
- 日历选择器结果
这类值优先改成 Temporal.PlainDate,比继续塞在 dayjs() 里清爽得多。
第三批处理时区
- 国际化业务
- 预约系统
- 会议时间
- 日志展示
这批应该统一改成 Instant + ZonedDateTime 的组合。
最后再处理格式化和自定义解析
这两块最容易卡住,也是 Dayjs 生态最舒服的部分。
我的建议很直接:
- 面向用户显示,用
Intl - 面向固定模板输出,自己封一个很小的 formatter
- 面向自定义输入,自己写解析函数
这样迁出来的代码会比“继续模拟 Dayjs 整套 token 体系”更干净。
最后一句话
Temporal 真正厉害的地方,并不只是它能不能替掉 Dayjs,而是它第一次让 JavaScript 标准库把“日期”“时间”“时区”“瞬时点”“时长”这些概念彻底拆开了。
这意味着以后很多以前必须靠第三方库硬补的能力,终于可以在标准层面被清晰表达了。
所以更准确的说法不是:
Temporal有一个 API 可以 1:1 替掉Dayjs
而是:
Temporal提供了一套更底层、更明确、更适合长期维护的时间模型,让你不再必须依赖Dayjs这类库来给Date打补丁。
如果你的目标是“彻底摆脱 Date 和 Dayjs 的历史包袱”,那从今天开始用 Temporal,方向是对的。
如果你的项目目前还 heavily 依赖:
- 自定义 token 解析
- 大量
format('YYYY/MM/DD HH:mm:ss')模板 - 旧运行时兼容
那就不要一步到位,先 polyfill,再按场景逐批迁移,才是最稳的方案。
