Dateの何が悪かったのか?
Dateとは、1995年に登場したJavaScriptの初期バージョンから存在する、日時を取り扱うための組み込みオブジェクトです。
このDateにはいろいろな問題が指摘されてきました。そもそもDateは、JavaのDateクラスをそのまま取り入れたものとされていて、こともあろうに問題点もそっくり引き継いでしまったと言われています。これがDateの利用を難しくし、しかもバグの温床になりやすい構造を生み出してきたのです。
具体的にどういった問題があったのでしょうか。まずは、誰もが口を揃えて指摘するDateの問題点をざっくり挙げてみましょう。
- 月の指定が0から始まり直感的でない
- タイムゾーン/時差の取り扱いが難しい
- オブジェクトがミュータブル(可変)である
- 日時文字列パースのルールが分かりにくい
- グレゴリオ暦しか使えない
- 精度がミリ秒単位なので、より高い精度が求められるユースケースには向かない
- 複雑な日時計算に不向きで、他ライブラリへの依存が増える
以降、それぞれについて、Temporalではどうなったのかも併せて、詳しく見ていきましょう。
[NOTE]JavaのDateはどうなったの?
JavaScriptのDateのベースとなったJavaのDateクラスでは、0から始まる月、ミュータブルなオブジェクト、任意のタイムゾーンを扱えないなどの問題を言語リリース当初から抱えていました。後継のCalendarクラスでも多くの問題は解決されず、2014年リリースのJava 8においてようやく上記の問題がすべて解決されたDate and Time APIがjava.timeパッケージにて提供され、利用可能になりました。Temporalは、Date and Time APIをベースに規格化が進められたと言われています。
Dateの問題点1:月の指定が0から始まり直感的でない
まず分かりやすいところを挙げれば、Dateでは月を表す数値が0から始まるというところでしょう。
これはシンプルである分、切実です。日常的な感覚では月は1からなので、1月を指定しようとして1を指定して2月になってしまったり、1が得られたので"January"と置き換えてしまったりといったバグを産み出してきました。
以下のリストでは、これを確かめています。
const date = new Date(2026, 0, 1); cconsole.log(date); // Thu Jan 01 2026 00:00:00 GMT+0900 (Japan Standard Time) const date2 = new Date(2026, 12, 1); console.log(date2); // Fri Jan 01 2027 00:00:00 GMT+0900 (Japan Standard Time) const date3 = new Date(2026, 1, 29); console.log(date3); // Sun Mar 01 2026 00:00:00 GMT+0900 (Japan Standard Time)
月に0を指定しましたが、これはJan(1月)と認識されます。12月を指定しようと12を与えると、翌年の1月というやや不可解な結果になります。同様に、2026年2月29日というあり得ない日付も受け入れられてしまいます。
対処法としては、当たり前ともいえますがDateに渡す月の値を常に-1するか、new Date('2026-05-01')のように文字列でパースするといったものがあります。前者はルールを徹底できるかといった問題がありますし、後者はいささか冗長です。単純に、月を1から始めればよいのにと思いますよね。日は1からなので、一貫性についても疑問です。
Temporalでは月は1から始まり直感的
ここから、Dateの問題点に対するTemporalの対応を見ていきましょう。Temporalでは、1月は1、12月は12というように直感的に月を表すことができます。また、あり得ない日時の指定は、以下のリストのようにエラーとなって拒否されるので安全です。
const date = new Temporal.PlainDate(2026, 1, 1); console.log(date.toString()); // 2026-01-01 console.log(date.toLocaleString()); // 1/1/2026 console.log(date.toJSON()); // 2026-01-01 const date2 = new Temporal.PlainDate(2026, 13, 1); // error: Invalid ISO date const date3 = new Temporal.PlainDate(2026, 2, 29); // error: Invalid ISO date
ここでは、コンストラクタを使ってPlainDateのインスタンスを生成しています。コンストラクタを使う場合の基本は、日時の要素を個別に引数に指定することです。必要に応じて暦情報を付加することもできます。
なお、Temporalのインスタンスは文字列化のメソッドを揃えており、上記で使ったtoStringメソッドを含めて以下のような使い分けとなっています。
- toString:RFC 9557フォーマットで文字列化
- toLocaleString:現在のロケールに基づき文字列化
- toJSON:JSON.stringfyで使うJSONを前提とした文字列化(toStringと同じ)
Dateの問題点2:タイムゾーン/時差の取り扱いが難しい
Dateはタイムゾーン情報を持てません。正確に言うと、UTC(世界協定時)しか持てないのです。
このため、Dateでは時差を含めたタイムゾーンの取り扱いが難しいものとなっています。Dateオブジェクトを生成すると、まずはローカルタイムゾーンに基づき日時情報が生成されます。
ただしtoISOStringメソッドを使うと本来の日時であるUTC時間(JST-9時間)に変換されることから、あくまでもDateオブジェクトが持っているのはUTCであることが分かります。
const date = new Date(2026, 5, 2, 12, 34, 56); console.log(date.toString()); // Tue Jun 02 2026 12:34:56 GMT+0900 (Japan Standard Time) console.log(date.toISOString()); // 2026-06-02T03:34:56.000Z
これは、月が0から始まるといった不便さではなく、機能の欠如なので、Dateオブジェクトでタイムゾーンを取り扱うには、標準機能であるIntl.DateTimeFormatを使うか、後述する外部ライブラリを使う必要があります。特にIntl.DateTimeFormatは出力にしか使えないので、複雑な日時の計算や変換には後者のライブラリを使うのが最適解とされています。
Temporalでは時差とタイムゾーンの情報を持てる
Temporalでは、UTC、ローカルタイムゾーンはもちろんのこと、任意のタイムゾーン、夏時間を扱うことができます。これを担うのがZonedDateTimeです。
const str = "2026-06-02T10:00:00+09:00[Asia/Tokyo]"; console.log(Temporal.ZonedDateTime.from(str).toLocaleString()); // 6/2/2026, 10:00:00 AM GMT+9
ZonedDateTimeクラスのfromメソッドを使うと、後述するRFC 9557に基づく書式で構成された日時文字列からインスタンスを生成できます。リスト中の"+09:00"が時差であり、"[Asia/Tokyo]"がタイムゾーンです。以下の図に、次回で紹介する暦(カレンダー)も含めた日次文字列の書式を示します。
タイムゾーンは、以下のコードで返される値のいずれかを指定します。
console.log(Intl.supportedValuesOf("timeZone"))
たとえば"Asia/Tokyo"、"America/NewYork"といったものが返されますが、その数は400にも及ぶので、こちらのタイムゾーンデータベースのページなどを参照するとよいでしょう。
なお、夏時間を導入している地域のタイムゾーン(たとえば"America/NewYork"など)を指定すると、夏時間を考慮した出力、演算が自動で実行されます。
昨今のサーヒスは、特定の地域(たとえば日本国内)に限定して運用されるというよりは、グローバルに提供するものが増加しているので、タイムゾーンへの対応が必須といえます。
