- JavaScriptの既存の Dateオブジェクトの不完全さと非一貫性 を指摘し、それに代わる Temporal API を紹介
- Dateは ミュータブルオブジェクト(mutable object) として動作するため、実際の日付という概念とずれがあり、パースエラー・タイムゾーン処理の限界 など構造的な問題がある
- Temporalは イミュータブル性を基盤とした新しい日付・時刻処理モデル を提供し、
PlainDate, ZonedDateTime, Duration など細分化されたクラスを含む
- Temporalのメソッドは既存のオブジェクトを変更せず 新しいオブジェクトを返す ため、明確で安全な チェーン演算 が可能
- Temporalは現在 標準化ステージ 3(Stage 3) にあり、ChromeやFirefoxなどの最新ブラウザで実験的にサポートされている
JavaScriptのDateオブジェクトの問題点
Date コンストラクタは 不一致なパース規則 と 直感的でないインデックス により混乱を招く
- 例: 月(month)は0から始まる一方、日(day)と年(year)は1から始まる
- 文字列
"99" は1999年として、"100" は0100年として解釈されるなど、一貫性に欠ける
Date は 時間(time) を中心に設計されており、内部的には Unixタイムスタンプ(ミリ秒単位) として保存される
- タイムゾーン(time zone) のサポートが限定的で、夏時間(DST) や グレゴリオ暦以外 を認識できない
- こうした限界のため、Moment.js、date-fns などの大規模なサードパーティライブラリへの依存が一般的であり、これは パフォーマンス低下 につながる
イミュータブル性と参照概念の衝突
- JavaScriptの プリミティブ値(primitive) はイミュータブルで値そのものとして保存されるが、オブジェクト(object) は参照(reference)として保存されるため変更可能
Date は コンストラクタ(constructor) を通じて作られるオブジェクトなので ミュータブル である
- 例:
setMonth() や setDate() を呼ぶと元のオブジェクトが直接変更される
- そのため、同じオブジェクトを参照する変数間で 予期しない値の変更 が発生する
- 例:
today を引数として渡した関数が内部で日付を修正すると、元の today も変更される
Temporal: 新しい日付・時刻API
Temporal は コンストラクタではなく名前空間オブジェクト(namespace object) であり、Math に似た構造を持つ
- 主な構成要素:
PlainDate, PlainDateTime, PlainTime, ZonedDateTime, Duration, Now など
Temporal.Now は現在時点をさまざまな形で返す
plainDateISO() → ISO形式の日付
zonedDateTimeISO() → タイムゾーンを含む時刻
- Temporalオブジェクトは 明確なメソッド体系 を提供する
add({ days: 1 }), subtract({ years: 2 }) などにより 明示的な単位演算 を実行
- 既存オブジェクトを変更せず 新しいオブジェクトを返す ことで、イミュータブル性を維持する
Temporalの動作方式と利点
- Temporalオブジェクトは依然としてオブジェクト型だが、意図されたイミュータブルな利用パターン に従う
- 例:
today.add({ days: 1 }) は新しい日付オブジェクトを返し、元の today は変更されない
Date と比べて 簡潔で明確な構文 を提供
- タイムゾーン指定、期間計算、ISOフォーマット維持 など現代的な要件に合致する
add, subtract, since, until などのメソッドチェーンにより 複雑な日付計算 を簡潔に表現できる
標準化の現状と今後の展望
Temporal は ECMAScript提案ステージ 3(Stage 3) に到達しており、ブラウザ実装が推奨される段階にある
- Chrome と Firefox ではすでに実験的サポートが始まっており、他のブラウザでも導入予定
- 開発者は現在から テストとフィードバック提供 を通じて仕様改善に参加できる
Date は今後も残るだろうが、将来的には Temporalが標準的な日付処理方式 として定着する見込み
- 記事は「1995年に置き換えられているべきだったが、今からでも Temporal.Now は最適なタイミングだ」と締めくくっている
1件のコメント
Hacker Newsの意見
この記事は、JavaScript の Date コンストラクタが持つさまざまな奇妙な挙動を扱っている
特に
'YYYY-MM-DD'形式が UTC の深夜0時として解釈されるため、ローカルタイムゾーンでは日付が1日ずれてしまう問題を説明している本来 ISO 8601 では、タイムゾーン指定がない場合はローカル時間として扱うべきだが、ES5 の仕様策定中のミスで “Z”(UTC) として処理されるようになった
その後 ES2015 で修正しようとしたものの、非常に多くの Web サイトが既存の誤った挙動に依存していたため、Web 互換性を理由に差し戻された
詳細は Broken Parser セクション を参照
'use strict'のような'strict datetime'ディレクティブがあればよかったのでは、という意見そうすれば既存コードとの 非互換性問題なしに、正しい挙動を選択的に適用できたはず
あるいは
import Date from 'browser:date'のように、内部モジュールとして 修正済みのグローバルオブジェクトを読み込む方式も考えられたはず誕生日のように単に日付だけを意味する値が、タイムゾーンのせいで変わるのはおかしい
昔 Outlook が誕生日をタイムゾーン付きで保存していたせいで、国を移動するたびに誕生日が1日ずれていたのを覚えている
ただ、代案があったのか? 昔の IE5 時代のように、ブラウザのバージョンごとの分岐処理を強制する方がもっと悪かったかもしれない
Rails と Ruby の 時間処理のやり方が本当にうらやましい
Time.current.in_time_zone('America/Los_Angeles') + 3.days - 4.months + 1.hourのような API は直感的で強力だRuby は Time オブジェクトを 一貫した単一のオブジェクトとしてオーバーロードしており、変換やキャストに悩むことがほとんどない
JS でも
new Date().add({ days: 1 })のように簡単に書けたらどれほどよいかと思うまた、コアライブラリをオーバーロードするのが本当に良いアプローチかどうかも議論の余地がある
Safari がまだ Temporal API をサポートしていないのが残念
来年くらいには対応してほしい
JavaScript の Date には多くの問題があるが、オブジェクトであること自体は大きな問題ではないと思う
不変オブジェクトであってほしかったが、可変オブジェクトが変更されること自体は驚くことではない
可変性の本当の危険はローカルではなく、非局所的な変更にある
Temporal API が うるう秒 (leap second) の情報をまったく扱わないのが不便
天文計算向けの JS ツールを作りたいが、UTC 変換にはうるう秒データが必要になる
temporal-taiのような回避策はあるものの、クライアント側でうるう秒ファイルを維持しなければならず面倒だSOP(CORS ポリシー) のせいで外部サイトからファイルを直接取得することもできない
ブラウザは定期的に更新されるのに、なぜうるう秒情報を内蔵しないのか疑問だ
サーバー側で
Access-Control-Allow-Originヘッダーを設定するか、JS ファイルの形で提供すれば可能だただし、ブラウザが独自にうるう秒データを含めて維持するのは コストの高い作業かもしれない
UTC は本来うるう秒を含む時間尺度なので、実際には POSIX 時間だけを扱っていると見るのが正しい
コード例では
"33"ではなく"50"から1900年代として扱うべきだ、という指摘 — 単なる 誤字の指摘Temporal polyfill を使っているが、今のところ非常に満足している
サーバーや大規模アプリには問題ないが、小さなアプリには負担になるかもしれない
記事が妙なことに
Date.now()にまったく触れていないTemporal と比較するなら
Date.now()を基準に説明すべきだったこの関数は 1970年1月1日からの ミリ秒単位の経過時間を返す
Temporal の方が親切な API ではあるが、根本的には時間の 相対的な距離を表現するのが目的だ
では Date を経由せずに、望む形式へ変換するにはどうすればよいのか気になる
“daylight savings time” ではなく “daylight saving time” が正しい、という 細かいが重要な訂正
これまで JS Date がここまでひどいものだとは知らなかった
あの時もう少し慎重であれば、多くの開発者がこうした落とし穴にはまらずに済んだはずだ