为何 Intl.DateTimeFormat 无法格式化 Temporal.ZonedDateTime?
报错的原因是 Intl.DateTimeFormat.prototype.format() 不接受 Temporal.ZonedDateTime。可以调用该对象的 toLocaleString();如果需要复用格式化器,可以先转换为 Temporal.Instant,再在格式化器中明确指定时区。
这个异常符合 Temporal 的接口约束,不能据此判定 Brave 尚未完整实现 Temporal。Temporal 规范中的 HandleDateTimeValue 明确要求对 ZonedDateTime 抛出 TypeError。
为什么其他 Temporal 对象可以格式化?
实际使用的 API 名称是 Intl.DateTimeFormat,不是 Intl.DateFormat。
支持 Temporal 的格式化实现可以处理 PlainDate、PlainTime、PlainDateTime、Instant 等类型,但并非所有 Temporal 对象都能直接传给 format()。
ZonedDateTime 自带时区,例如日志中的 America/Toronto。Intl.DateTimeFormat 也有自己的时区,即使没有显式设置 timeZone,它仍会采用默认时区。调用 format(zonedDateTime) 时,接口不会自动决定使用哪一方的时区,而是拒绝该参数类型。
例如,下面的对象可以正常创建,但传给 format() 时会报错:
const zdt = Temporal.Now.zonedDateTimeISO();
console.log(zdt); // 对象可以正常创建
const formatter = new Intl.DateTimeFormat("zh-CN", {
dateStyle: "long",
timeStyle: "long",
});
formatter.format(zdt); // TypeError
Vue 的渲染警告来自模板读取 todayTemporalTimestamp 时,计算属性中触发的异常。问题发生在 format() 调用处,调整模板或 computed() 的写法无法解除参数限制。
最直接的修改:调用 toLocaleString()
将原来的 todayTemporalTimestamp 替换为:
const todayTemporalTimestamp = computed(() =>
Temporal.Now.zonedDateTimeISO().toLocaleString(locale.value, {
dateStyle: "long",
timeStyle: "long",
}),
);
模板可以继续使用:
<p>{{ todayTemporalTimestamp }}</p>
这会使用 ZonedDateTime 自身的时区,并根据 locale.value 格式化日期和时间。计算属性读取了 locale.value,切换语言时会重新求值。
toLocaleString() 接受与 Intl.DateTimeFormat 类似的语言和格式选项。如果需要切换显示时区,可以先使用 withTimeZone():
const todayTemporalTimestamp = computed(() => {
const zdt = Temporal.Now.zonedDateTimeISO();
return zdt.withTimeZone("America/Toronto").toLocaleString(locale.value, {
dateStyle: "long",
timeStyle: "long",
});
});
withTimeZone() 保留同一个时间点,改变其对应的本地日期、时间和时区表示。相关行为见 Temporal.ZonedDateTime 文档。
需要复用 Intl.DateTimeFormat 时怎么写?
如果演示需要保留独立的格式化器,可以先将 ZonedDateTime 转成 Instant。Instant 表示时间点,显示时区由格式化器指定:
const displayTimeZone = "America/Toronto";
const temporalFormatter3 = computed(
() => new Intl.DateTimeFormat(locale.value, {
dateStyle: "long",
timeStyle: "long",
timeZone: displayTimeZone,
}),
);
const todayTemporalTimestamp = computed(() => {
const zdt = Temporal.Now.zonedDateTimeISO(displayTimeZone);
return temporalFormatter3.value.format(zdt.toInstant());
});
这个方案要求当前使用的 Intl.DateTimeFormat 实现支持 Temporal.Instant。转换后的 Instant 不再携带原对象的时区,因此需要显式设置 timeZone,以确保按目标时区显示。Temporal 格式化规范 对 Instant 和 ZonedDateTime 使用不同的处理分支。
如果输入来自已有的 zdt 对象,应使用 zdt.timeZoneId 创建或选择对应的格式化器,以保留它的显示时区。
Intl 尚不能接收 Temporal 对象时的兜底方法
如果运行环境或 polyfill 的组合不能让 Intl.DateTimeFormat 接收 Temporal.Instant,可以通过 epochMilliseconds 转成传统 Date:
const todayTemporalTimestamp = computed(() => {
const zdt = Temporal.Now.zonedDateTimeISO();
const formatter = new Intl.DateTimeFormat(locale.value, {
dateStyle: "long",
timeStyle: "long",
timeZone: zdt.timeZoneId,
});
return formatter.format(new Date(zdt.epochMilliseconds));
});
Date 保存的是毫秒级时间点,不保存 America/Toronto 这样的时区标识,所以仍需通过 timeZone 指定显示时区。转换会舍去毫秒以下的精度,对于这里的日期和秒级时间展示通常没有影响。
需要注意什么?
不要为了消除异常就直接改成 zdt.toPlainDateTime()。这个转换只保留当地日期和时间字段,会丢失时区及其与具体时间点的关联;仅在确实只需要这些字段时使用。Temporal.ZonedDateTime 文档 对这种转换及夏令时相关影响有专门说明。
引入 polyfill 后仍然报错,也不能直接说明 polyfill 失效。符合这一接口约束的实现仍会拒绝 format(zonedDateTime)。由于问题没有给出 polyfill 的名称、版本和接入方式,无法进一步判断它是否提供了额外的 Intl 集成。
另外,Temporal.Now 不会自动成为 Vue 的响应式依赖。上述 computed() 会在语言等响应式依赖变化时重新计算,但不会自行按秒刷新。如果演示需要实时走动的时钟,还需要用定时器更新一个响应式时间值。
备注:内容仅供参考。
