はじめに
Temporalは現状 TC39 proposal stage 3 のものを使用しています
今後APIが変わる可能性もあるのでご注意ください
Temporalのpolyfill1に実装されているIntl
を拡張したモジュールを使ってPlainMonthDay
のフォーマットをしようとした時に、以下のようなエラーが出てハマった部分があったので説明します。
intl.js:400 Uncaught RangeError: cannot format PlainMonthDay with calendar iso8601 in locale with calendar Gregory
at extractOverrides (intl.js:400:1)
at DateTimeFormatImpl.format (intl.js:143:1)
at App (App.tsx:76:1)
Temporalとは
TC39 proposalのTemporalについてはこちらの記事で説明していますので、ご覧ください。
Intlについて
IntlはES20172で導入された、国際API 名前空間オブジェクトで、Temporalのpolyfillで実装されているIntlは、これを拡張してTemporalの日時を扱えるようにしているものです。
ECMAScriptのIntlには、Intl.ListFormat()
やIntl.NumberFormat()
などがありますが、TemporalではIntl.DateTimeFormat()
が拡張されています。
Intl.DateTimeFormat()
の基本的な使い方について
基本的なIntl.DateTimeFormat()
の使い方はこちらをご確認ください。
一部引用すると、以下のように使えます。
const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
// Results below assume UTC timezone - your results may vary
console.log(new Intl.DateTimeFormat('en-US').format(date));
// expected output: "12/20/2020"
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }).format(date));
// Expected output "Sunday, 20 December 2020 at 14:23:16 GMT+11"
PlainYearMonthやPlainMonthDayのフォーマットについて
Intl.DateTimeFormat().format()
でフォーマットできるオブジェクトは以下のように型定義されています。
type Formattable =
| Date
| Temporal.Instant
| Temporal.ZonedDateTime
| Temporal.PlainDate
| Temporal.PlainTime
| Temporal.PlainDateTime
| Temporal.PlainYearMonth
| Temporal.PlainMonthDay;
そこで、タイトルにあるように、PlainMonthDay
をformatしてみると、以下のようなエラーが発生します。
new Intl.DateTimeFormat().format(
Temporal.Now.plainDateISO().toPlainMonthDay()
)
/*
intl.js:400 Uncaught RangeError: cannot format PlainMonthDay with calendar iso8601 in locale with calendar Gregory
at extractOverrides (intl.js:400:1)
at DateTimeFormatImpl.format (intl.js:143:1)
at App (App.tsx:76:1)
*/
これがエラーになる原因については、こちらで説明されていますが、これはTemporalの機能のCalendarに関係してきます。
Temporal.Calendar
について
カレンダーという概念についてはこちらで説明されています。
日時を扱う際に、世界中で多く使われているグレゴリオ暦以外の暦を扱うことができ、例えばユダヤ暦、中国暦、また和暦もあります。
これらの暦の使い方として、ドキュメントにある例を引用すると「グレゴリオ暦のMarch 4, 2021
をユダヤ暦では20 Adar 5781
と表現する」などのような変換をするものがあります。
Temporalでは内部的にはISO8601カレンダーを使用していますが、表示の際に他のカレンダーを使用することができます。
あらためて、PlainMonthDayのフォーマットについて
このカレンダーについての考え方を元に、先程のissueで説明されている内容を確認してみます。
Dates can be converted between calendars.
The same is not true for Temporal.PlainMonthDay or Temporal.PlainYearMonth. The 5th month of the Chinese calendar year 2022 has no relationship to the 5th month of the Hebrew year 2022. Similarly, the 5th day or the 8th month of the Gregorian calendar is unrelated to the 5th day of the 8th month of the Chinese or Hebrew or Islamic calendar. There is no way to convert them.
このように、ある暦の2022年の5ヶ月目は別の暦の2022年の5ヶ月目と全く関係がないため、変換ができません。
For this reason, localized formatting of Temporal.PlainMonthDay or Temporal.PlainYearMonth requires that the calendar of the Temporal.PlainMonthDay or Temporal.PlainYearMonth instance must exactly match the calendar of the locale. Because the default calendar is iso8601 (a calendar that no locale uses), the caller must opt into using a specific calendar option when formatting these temporal types.
そのため、Temporal.PlainMonthDay
やTemporal.PlainYearMonth
をローカライズフォーマットしたい時は、「インスタンスの暦」と「ロケールの暦」を一致させる必要があるようです。
// インスタンスの暦を和暦に設定
const yearMonth = Temporal.Now.plainDate('japanese').toPlainYearMonth()
// ロケールの暦を和暦に設定
console.log(yearMonth.toLocaleString('ja', { calendar: 'japanese' }))
// -> R4/03
また、Intl.DateTimeFormat
を使った場合も同様です。
const yearMonth = Temporal.Now.plainDate('japanese').toPlainYearMonth()
console.log(
new Intl.DateTimeFormat('ja', {
calendar: 'japanese',
year: 'numeric',
}).format(yearMonth)
)
// 令和4年
まとめ
Temporal.PlainMonthDay
やTemporal.PlainYearMonth
をローカライズフォーマットしたい時は、「インスタンスの暦」と「ロケールの暦」を一致させる必要がある
参考
- ECMAScript® 2021 Internationalization API Specification
- Intl - JavaScript | MDN
Intl.DateTimeFormat().format
can't formatTemporal.PlainMonthDay
· Issue #2075 · tc39/proposal-temporal- Principles to resolve issues with MonthDay and YearMonth? · Issue #874 · tc39/proposal-temporal
- Temporal documentation