为什么Temporal.Now.zonedDateTimeISO() 在Intl.DateFormat中不起作用?
我在为一个关于Javascript中 i18n的演示做示例,具体是Vue 3.x,我想展示Intl.DateFormat可以同时与旧的Date对象和新的Temporal对象一起工作。
到目前为止,Temporal.Now.plainDateISO() 和Temporal.Now.plainTimeISO() 在 Intl.DateFormat 上工作正常,但当我尝试把Temporal.Now.zonedDateTimeISO() 与 Intl.DateFormat 一起使用时,会出现运行时错误;而 Temporal.Now.zonedDateTimeISO() 在不把它与 Intl.DateFormat 一起使用时工作正常。
下面的代码是我执行的:
<script setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
const { t, locale, availableLocales } = useI18n();
/* Lists */
const calendars = Intl.supportedValuesOf("calendar");
const collations = Intl.supportedValuesOf("collation");
const currencies = Intl.supportedValuesOf("currency");
const numberingSystems = Intl.supportedValuesOf("numberingSystem");
const timeZones = Intl.supportedValuesOf("timeZone");
const units = Intl.supportedValuesOf("unit");
/* Date/Time/Timezones (Date/Temporal) */
const dateFormatter1 = computed(
() => new Intl.DateTimeFormat(locale.value, { weekday: "long", day: "numeric", year: "numeric", month: "long", era: "short" }),
);
const todayDate = computed(() => dateFormatter1.value.format(new Date()));
const dateFormatter2 = computed(() => new Intl.DateTimeFormat(locale.value, { timeStyle: "long" }));
const todayTime = computed(() => dateFormatter2.value.format(new Date()));
const dateFormatter3 = computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: "long", timeStyle: "long"}));
const todayTimestamp = computed(() => dateFormatter3.value.format(new Date()));
const temporalFormatter1 = computed(() => new Intl.DateTimeFormat(locale.value, { weekday: "long", day: "numeric", year: "numeric", month: "long", era: "short"}));
const todayTemporalDate = computed(() => temporalFormatter1.value.format(Temporal.Now.plainDateISO()));
const temporalFormatter2 = computed(() => new Intl.DateTimeFormat(locale.value, { timeStyle: "long"}));
const todayTemporalTime = computed(() => temporalFormatter2.value.format(Temporal.Now.plainTimeISO()));
const temporalFormatter3 = computed(() => new Intl.DateTimeFormat(locale.value, { dateStyle: "long", timeStyle: "long"}));
console.log(Temporal.Now.zonedDateTimeISO());
const todayTemporalTimestamp = computed(() => temporalFormatter3.value.format(Temporal.Now.zonedDateTimeISO()));
</script>
<template>
<header>
<nav>
<div>
<a href="#">{{ t("home") }}</a>
<a href="#">{{ t("about") }}</a>
</div>
<span>
<select v-model="locale" class="localeList">
<option
v-for="oneLocale in availableLocales"
:key="oneLocale"
:value="oneLocale"
>
{{ oneLocale }}
</option>
</select>
</span>
</nav>
</header>
<main>
<h1>{{ t("greeting") }}</h1>
<h2>Date/Time Formats using Date</h2>
<p>{{ todayDate }}</p>
<p>{{ todayTime }}</p>
<p>{{ todayTimestamp }}</p>
<h2>Date/Time Formats using Temporal</h2>
<p>{{ todayTemporalDate }}</p>
<p>{{ todayTemporalTime }}</p>
<p>{{ todayTemporalTimestamp }}</p>
<h2>Calendars</h2>
<p>These are the available calendars:</p>
<ul>
<li v-for="oneCalendar in calendars" :key="oneCalendar">{{ oneCalendar }}</li>
</ul>
<h2>Collations</h2>
<p>These are the available collations:</p>
<ul>
<li v-for="oneCollation in collations" :key="oneCollation">{{ oneCollation }}</li>
</ul>
<h2>Currencies</h2>
<p>These are the available currencies:</p>
<ul v-for="oneCurrency in currencies" :key="oneCurrency">
<li>{{ oneCurrency }}</li>
</ul>
<h2>Numbering Systems</h2>
<p>These are the available numbering systems:</p>
<ul>
<li v-for="oneNumberingSystem in numberingSystems" :key="oneNumberingSystem">{{ oneNumberingSystem }}</li>
</ul>
<h2>Timezones</h2>
<p>These are the available timezones:</p>
<ul>
<li v-for="oneTimeZone in timeZones" :key="oneTimeZone">{{ oneTimeZone }}</li>
</ul>
<h2>Units</h2>
<p>These are the available units:</p>
<ul>
<li v-for="oneUnit in units" :key="oneUnit">{{ oneUnit }}</li>
</ul>
</main>
</template>
<style scoped>
header {
padding: 1rem;
background: #333;
width: 100%;
}
h2 {
margin-top: 1rem;
}
nav {
display: flex;
justify-content: space-between;
}
.localeList {
font-size: 1.5rem;
}
a {
text-decoration: none;
color: #fff;
font-size: 1.5rem;
padding: 0 1rem;
margin: 0 1rem;
}
main {
padding: 2rem;
}
</style>
这是控制台输出:
ZonedDateTime {timeZoneId: 'America/Toronto', calendarId: 'iso8601', era: undefined, eraYear: undefined, year: 2026, …}calendarId: "iso8601"day: 19dayOfWeek: 4dayOfYear: 78daysInMonth: 31daysInWeek: 7daysInYear: 365epochMilliseconds: 1773963013444epochNanoseconds: 1773963013444030029nhour: 19hoursInDay: 24microsecond: 30millisecond: 444minute: 30month: 3monthCode: "M03"monthsInYear: 12nanosecond: 29offset: "-04:00"offsetNanoseconds: -14400000000000second: 13timeZoneId: "America/Toronto"weekOfYear: 12year: 2026yearOfWeek: 2026[[Prototype]]: Temporal.ZonedDateTime
main.js:22 [Vue warn]: Unhandled error during execution of render function
at <App>
warn$1 @ chunk-6PFJYSIW.js?v=cc6eb311:2386
logError @ chunk-6PFJYSIW.js?v=cc6eb311:2597
handleError @ chunk-6PFJYSIW.js?v=cc6eb311:2589
renderComponentRoot @ chunk-6PFJYSIW.js?v=cc6eb311:6930
componentUpdateFn @ chunk-6PFJYSIW.js?v=cc6eb311:8451
run @ chunk-6PFJYSIW.js?v=cc6eb311:689
setupRenderEffect @ chunk-6PFJYSIW.js?v=cc6eb311:8586
mountComponent @ chunk-6PFJYSIW.js?v=cc6eb311:8358
processComponent @ chunk-6PFJYSIW.js?v=cc6eb311:8310
patch @ chunk-6PFJYSIW.js?v=cc6eb311:7814
render2 @ chunk-6PFJYSIW.js?v=cc6eb311:9125
mount @ chunk-6PFJYSIW.js?v=cc6eb311:6579
app.mount @ chunk-6PFJYSIW.js?v=cc6eb311:12813
(anonymous) @ main.js:22
App.vue:30 Uncaught TypeError: Invalid argument for Temporal [object Temporal.ZonedDateTime]
at ComputedRefImpl.fn (App.vue:30:72)
at refreshComputed (chunk-6PFJYSIW.js?v=cc6eb311:843:29)
at get value (chunk-6PFJYSIW.js?v=cc6eb311:2098:5)
at unref (chunk-6PFJYSIW.js?v=cc6eb311:1953:30)
at Object.get (chunk-6PFJYSIW.js?v=cc6eb311:1959:64)
at Proxy._sfc_render (App.vue:64:47)
at renderComponentRoot (chunk-6PFJYSIW.js?v=cc6eb311:6894:17)
at ReactiveEffect.componentUpdateFn [as fn] (chunk-6PFJYSIW.js?v=cc6eb311:8451:46)
at ReactiveEffect.run (chunk-6PFJYSIW.js?v=cc6eb311:689:19)
at setupRenderEffect (chunk-6PFJYSIW.js?v=cc6eb311:8586:5)
(anonymous) @ App.vue:30
refreshComputed @ chunk-6PFJYSIW.js?v=cc6eb311:843
get value @ chunk-6PFJYSIW.js?v=cc6eb311:2098
unref @ chunk-6PFJYSIW.js?v=cc6eb311:1953
get @ chunk-6PFJYSIW.js?v=cc6eb311:1959
_sfc_render @ App.vue:64
renderComponentRoot @ chunk-6PFJYSIW.js?v=cc6eb311:6894
componentUpdateFn @ chunk-6PFJYSIW.js?v=cc6eb311:8451
run @ chunk-6PFJYSIW.js?v=cc6eb311:689
setupRenderEffect @ chunk-6PFJYSIW.js?v=cc6eb311:8586
mountComponent @ chunk-6PFJYSIW.js?v=cc6eb311:8358
processComponent @ chunk-6PFJYSIW.js?v=cc6eb311:8310
patch @ chunk-6PFJYSIW.js?v=cc6eb311:7814
render2 @ chunk-6PFJYSIW.js?v=cc6eb311:9125
mount @ chunk-6PFJYSIW.js?v=cc6eb311:6579
app.mount @ chunk-6PFJYSIW.js?v=cc6eb311:12813
(anonymous) @ main.js:22
如果把polyfill包含进来,错误仍然会出现,仿佛没有使用它一样。我的浏览器是Brave:
Brave已是最新版
Brave 1.88.134 (Official Build) (64-bit)
Chromium: 146.0.7680.153
这是浏览器尚未完全实现Temporal对象的情况,还是还有其他问题?
解决方案
为了避免两个对象的时区冲突,不能用 Intl.DateTimeFormat 来格式化一个 Temporal.ZonedDateTime。你应该执行下列任一操作:
- 如果你只是想在外部进行简单格式化,且不打算格式化大量时间戳,请对那个
Temporal.ZonedDateTime对象调用toLocaleString。这个方法接受与Intl.DateTimeFormat构造函数相同的参数,尽管因为每次都需要创建一个新的格式化器,所以速度会慢一些。 - 如果你需要不同的格式(例如
formatToParts)或要格式化大量时间戳,考虑使用Intl.DateTimeFormat,像这样:
js
zdt = Temporal.ZonedDateTime.from('2026-03-19T00:00[America/Los_Angeles]');
new Intl.DateTimeFormat({ timeZone: zdt.timeZoneId }).format(zdt.toInstant());
将 Temporal.ZonedDateTime 与 Intl.DateTimeFormat 一起格式化时的难点在于它们都可能携带时区。
zdt = Temporal.ZonedDateTime.from('2026-03-19T00:00[America/Los_Angeles]');
new Intl.DateTimeFormat({ timeZone: 'Asia/Tokyo' }).format(zdt);
因此你需要决定如果它们冲突时应该怎样处理。我们(设计Temporal API的 TC39成员)就这个问题争论了很长时间,但未能就具体行为达成一致。
https://github.com/js-temporal/proposal-temporal-v2/issues/38 对迄今为止所做工作的总结相当全面。由于当前Temporal的解决方案是抛出异常,这为未来的TC39提案留出了空间,以提出一种不抛出异常的解决方案。