JavaScriptの新しい標準日付ライブラリ Temporalまとめ
JavaScriptの標準ライブラリに入っているDateはタイムゾーンを扱いづらいところがあって、悩みのタネになることがあります。
そこでTemporalです。Temporalには下記のような特徴があり、タイムゾーン問題を始めDateで悩みのタネであった課題を多く解決しています。
- 日付と時刻を操作する簡単で使いやすい API を提供します
- DST を考慮した演算と、すべてのタイムゾーンをサポートします
- オブジェクトは特定の日時や時刻を明確に表します
- 厳格に定義された文字列をパースします
- グレゴリオ暦以外のカレンダーをサポートします
さらにプロポーザルはStage 3まで進んでいて、将来標準ライブラリ入りする可能性も高いです。
特にサーバサイドJavaScript環境では、今後Temporalをベースに日時を処理する、というのが主流になるのではないかと思ったので、そのAPIや使い方について調べました。
Temporalはドキュメントが充実しており、読めばだいたいのことがわかります。
ポリフィル
既にいくつか実装があるので、それを動かして確認すると良いです。
各クラスの役割
オブジェクトの関係図を見ると理解しやすいです。
https://tc39.es/proposal-temporal/docs/ja/index.html#object-%E3%81%AE%E9%96%A2%E4%BF%82%E5%9B%B3
TemporalのコアコンセプトはWall-Clock TimeとExact Timeの区別にあります。
Wall-Clock Timeはカレンダーを持っていて(PlainTimeを除く)、現地時間を知っているものです。
Exact Timeはエポックからの経過時間(UNIX時間)を知っているものです。
日時を表現するクラスについてざっくりまとめます。
クラス名 | 説明 |
---|---|
PlainXX | 日時のみを持つ。UNIX時間やタイムゾーンの情報は持っていない |
ZonedDateTime | UNIX時間とタイムゾーンを両方持ってる。Exact TimeとWall-Clock Timeの両方に属する |
Instant | UNIX時間しか知らない。タイムゾーンを持ってないので、このクラス単体では日付や時間は得られない。そういうことをしたければZonedDateTimeを使う |
これに加えて、ZonedDateTimeとPlainXX(PlainTime除く)は「カレンダー」というものを持ってるのですが、これについては後述します。
残りのTimeZone、Calendar、 Durationは補助的な役割のクラスで、ざっくりまとめると下記のようになります。
クラス名 | 説明 |
---|---|
Duration | 時刻の加算・減算をするときなどに使う |
TimeZone | タイムゾーンを指定するときに使える。例えばIsntantからZonedDateTimeの変換をするときなど。タイムゾーンは文字列(IANA Name)で指定することもできる |
Calendar | 後述 |
このあたりは、文字列表現から見るとイメージしやすいかもしれません。
カレンダーについて
例えば、中国暦やユダヤ暦など一部の暦だと、うるう年は1年が13ヶ月になる、というようなiso8601のカレンダーと異なる特殊なケースがあります。そういった特殊な事情に対応するための抽象化がカレンダーです。
Intl.DateTimeFormat で使えるカレンダーがそのまま使えます。
例えばjapaneseカレンダーだと、iso8601カレンダーをベースに和暦に対応している、といった感じです。
const { Temporal, Intl, toTemporalInstant } = require('@js-temporal/polyfill');
// 2023/04/01
// (iso8601カレンダーだとera, eraYearはundefinedになる)
const { era, eraYear } = Temporal.Now.zonedDateTime("japanese", "Asia/Tokyo");
// reiwa
// 5
console.log(era, eraYear);
フォーマットについて
各オブジェクトの toLocaleString が使えます。
toLocaleString の引数にはとIntl.DateTimeFormatで使う localeとoptionsを渡せます。
ただ、@js-temporal/polyfill だと、試した範囲でフォーマットが正常に動作してないように見えるところがありました。under constructionと書いてるので、まだ実装できてない箇所があるのかもしれません。
import { Temporal } from "@js-temporal/polyfill";
// こっちだとうまく動く
// import { Temporal, Intl, toTemporalInstant } from "temporal-polyfill";
const now = Temporal.Now.instant();
const fmtArgs = [
["ja-JP-u-ca-japanese"],
{
era: "long",
year: "numeric",
month: "long",
day: "numeric",
}
];
// actual: “令和5/4/1”
// expected: “令和5年2月6日”
console.log(now.toLocaleString(...fmtArgs));
// UNIX時間に変換してIntl.DateTimeFormat.format を直接呼ぶと期待通りに動く
// "令和5年4月1日"
console.log(new Intl.DateTimeFormat(...fmtArgs).format(now.epochMilliseconds));
Discussion