JavaScript Temporal がやってくる
(developer.mozilla.org)- JavaScript の新しい Temporal オブジェクトがブラウザの実験的リリースに入り始めており、日付・時刻・期間・カレンダー処理を組み込み API でより正確かつ一貫して扱おうとする変化である
- 既存の
Dateは、ユーザーのローカル時刻と UTC を中心にした構造、不安定なパース、mutable な動作のため、タイムゾーン処理とデバッグが難しかった - Temporal は
Temporal.Instant,Temporal.ZonedDateTime,Temporal.PlainDateTime,Temporal.Duration,Temporal.Nowなどによって、時点・ローカル時刻・期間を分離し、200 以上のユーティリティメソッドを提供する - 安定したクロスブラウザ対応まではまだ時間が必要で、Firefox Nightly では
javascript.options.experimental.temporal設定の背後で Temporal を試せる - MDN には今週 270 ページ以上の Temporal ドキュメントが追加され、
@js-temporal/polyfillがある TC39 のドキュメントページではフラグ変更なしで例を実行できる
JavaScript Date の長年の限界
- JavaScript が 1995 年に作られたとき、
Dateオブジェクトは Java の初期java.util.Date実装をコピーしたもので、Java は 1997 年にこれを置き換えたが、JavaScript はほぼ 30 年間同じ API を維持してきた - 最大の問題は タイムゾーン対応の不足 と不安定なパース動作である
- ユーザーのローカル時刻と UTC しかサポートしない
Date自体が mutable なので、追跡しにくいバグを生む可能性がある- Daylight Saving Time(DST) や歴史的なカレンダー変更をまたぐ計算も扱いにくい
- こうした制約のため、多くの開発者はアプリケーションの日付・時刻処理に Moment.js や date-fns のような専用ライブラリに依存してきた
Temporal が変える日付・時刻モデル
- Temporal は
Dateオブジェクトの 完全な代替 として設計されており、日付と時刻の管理をより信頼でき、予測可能にすることを目指している - タイムゾーンとカレンダー表現をサポートし、変換・比較・計算・フォーマットのための組み込みメソッドを提供する
- API 表面には 200 を超えるユーティリティメソッドがあり、MDN の Temporal ドキュメント に説明と例が追加された
Temporal の主要な型
- Temporal は 固有の時点、地域の壁時計時刻、期間をそれぞれ別の型として区別する
- 主な構造は次のとおり
Temporal.Duration: 2 つの時点の差Temporal.Instant: タイムスタンプで表した固有の時点Temporal.ZonedDateTime: タイムゾーンを含む日付・時刻Temporal.PlainDateTime: タイムゾーンのない完全な日付・時刻Temporal.PlainDate: 日付のみを表現Temporal.PlainYearMonth: 年と月Temporal.PlainMonthDay: 月と日Temporal.PlainTime: 時刻のみを表現
Temporal.Now: 現在時刻を複数のクラスインスタンスや特定の形式で取得する API
例で見る使い方
- 現在の日付と時刻を ISO 文字列で取得でき、複数のメソッドに タイムゾーン を渡せる
Temporal.Now.plainDateTimeISO()はシステムタイムゾーンの現在の日付・時刻を返すTemporal.Now.plainDateTimeISO("America/New_York")は New York タイムゾーンの現在の日付・時刻を返す
- Gregorian 以外のカレンダーも扱え、Hebrew、Chinese、Islamic のようなカレンダー体系の日付を作成できる
- Chinese カレンダーの
M011 日を使って次の Chinese New Year を計算する - 執筆時点の例の出力では、次の Chinese New Year は
1/29/2025である
- Chinese カレンダーの
- Unix Epoch ミリ秒タイムスタンプを
Temporal.Instant.fromEpochMilliseconds()で時点に変換し、Temporal.Now.instant()と比較して残り時間を計算できる- 例のタイムスタンプ
1851222399924について、smallestUnit: "hour"で現在からの時間を計算する @js-temporal/polyfillの出力は31,600 hr、Firefox Nightly の出力はPT31600Hである
- 例のタイムスタンプ
- Firefox 実装の
toLocaleStringは現在 ロケール依存の文字列 を出力しない- この挙動は技術的な限界というより、設計上の判断に近い
- polyfill と Firefox 実装は今後収束する可能性がある
compare()メソッドは配列のソートに活用できるdurations.sort(Temporal.Duration.compare)を使うと、PT1H,PT1H30M,PT1H45M,PT2Hの順にソートされる
ブラウザ対応と試す方法
- Temporal 対応は実験的なブラウザリリースに少しずつ入り始めており、現時点では Firefox の実装が最も成熟しているように見える
- Firefox では Nightly バージョン で
javascript.options.experimental.temporal設定の背後に Temporal がビルドされている - 実装追跡用の主要なブラウザ issue は次のとおり
- Firefox: Build temporal in Nightly by default
- Safari: [JSC] Implement Temporal
- Chrome: Implement the Temporal proposal
- 全体の互換性状況は MDN の Temporal object Browser Compatibility section で確認できる
- TC39 Temporal ドキュメント には
@js-temporal/polyfillがあり、どのブラウザでもフラグや設定変更なしで開発者ツールのコンソールから例を実行できる
1件のコメント
Hacker News の意見
Temporal は素晴らしい。しばらく polyfill を本番環境で使ってきたが、従来の
Date()API で遭遇していた多くの問題を解決してくれるRust の
chrono、Java の Joda Time のような優れた日時ライブラリに着想を得て、使いやすい API にうまくまとめた感じだ単純な時刻、時点、タイムゾーン付き時刻を分けているので少し複雑ではあるが、時間そのものが複雑なので、開発者はその複雑さに向き合う必要がある
「このタイムスタンプに 1 日を足す」といった処理でも、特定のタイムゾーン基準なのかを必ず決めなければならず、そうしないと夏時間やユーザーのタイムゾーン変更、サーバーのタイムゾーン差によってバグが残る
固定オフセットのタイムスタンプ(
2025-01-01T00:00+02:00)と特定のタイムゾーン(Europe/Paris)のシリアライズ上の違いも解決してくれるchronoを含む他の日付・時刻クレートとの比較もまとめている: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...個人的には、Temporal が Rust の
chronoから大きく影響を受けたとは見ておらず、Joda や最近のjava.time、そして date-fns や Moment.js のような JavaScript の日付・時刻ライブラリからの影響のほうが大きいように見えるEurope/Parisがフランスの広い地域を指すのかパリだけを意味するのか調べようとせず、自分がパリに住んでいると言っているように感じて苛立つオンラインコミュニティのイベントなどを予約するとき、この点はつらかった。人々は固定オフセットを使いたがり、
CET/CESTのような表現は許容するが、Europe/yourexactcoordinatesのようなものは嫌がる実際にはほとんどのタイムゾーンがかなり広い地域だということは分かっているが、プライバシーを気にしつつも「時刻」と「タイムゾーン」の違いを調べる気はない人が多い
chronoと Joda Time は別の時代のものだ。Rust 1.0 が出たとき、Java はすでに標準ライブラリの java.time でこのアプローチを実装しており、この用途にサードパーティライブラリは不要だったドキュメントで見つけるのにかなり時間がかかったが、Temporal.ZonedDateTime.prototype.withTimeZone() があるタイムゾーンから別のタイムゾーンへ変換してくれる: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Temporal.ZonedDateTime.from("2021-08-01T12:00[America/New_York]")で作った値をwithTimeZone("Europe/Paris")に渡すと、2021-08-01T18:00:00+02:00[Europe/Paris]になるタイムゾーン変換と期間の扱いはライブラリが必ず扱うべき中核機能だが、Temporal がその両方を扱ってくれるのはよい。ただし
Temporal.Durationには、duration.format('H\hmm\m')のように1h03mにするカスタムformat関数はなさそうだ1 yr、3 hours、3mのような形だ: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...icu4x ライブラリに一部実装したことがある
現在 TC39 には熟しつつある提案がかなり多く、Temporal は早くリリースされてほしいものの一つ
主要ブラウザが新機能をサポートするには、v8、JSC、SpiderMonkey のエンジニアたちの開発努力が必要だということは分かっているので、進展を生み出している人たちには本当に感謝している
せっかちな立場としては「Records と Tuples も早くやって! Pipe 構文戦争は飛ばしてもいいから!」と応援したくなる
ブラウザベンダーは、この時点で新しいプリミティブ型を追加することに、すでにかなり強く反対していると理解している。等価性がどう動作すべきかという難点は、このスレッドを見ると説明がある: https://github.com/tc39/proposal-record-tuple/issues/387
BigInt が当初ここまで多く使われるとは期待していなかったが、実際には 構造的等価性 を持つコレクションが必要になる場面が何度も出てきて惜しく感じる
新しいプリミティブ型の追加への反対がパターンマッチングにも影響していると理解しているが、その理由はよく分からず、その提案はあまり追えていない
パイプライン、パターンマッチング、Records+Tuples はどれも 4 年以上作業中で、まだステージ 1〜2 にとどまっている。この 1 年で意味のある進展は、パターンマッチング程度を除けばほとんどなさそうに見える
Records と Tuples のリポジトリの issue によると、値セマンティクス、つまり
===が動作し得るかについて反発があった。値セマンティクスを抜くと、提案の有用性は大きく下がるこれらがステージ 3 に到達するには少なくともさらに 1〜2 年かかるか、そもそも到達できないかもしれない。それでも配列のグループ化関数はできた
まだステージ 1 なので、悲しいことにあと数年はかかりそう
AS3 の Dictionary は、オブジェクト参照、文字列、数値を一意なキーとして使い、任意の型の値を紐づけられる型だった。ディクショナリオブジェクト自体が生きている間は弱参照にならないよう、ガベージコレクションも処理されていた
JavaScript の Set を思い浮かべつつ、指定したどんな種類のキーでも強い型で使い、コンパイル時にエラーを投げる形だった。あの言語が懐かしい
そのアップデート群が事実上 JavaScript エコシステムを壊し、今では JS にコンパイルされる言語だけが残ったように感じる。いまだにバニラ JS を使っている人は、アセンブリでアプリを作っている人のようだ
Temporal の中で各タイムゾーンデータがどのように最新に保たれるのか気になる
TC39 がどこかでデータを更新し、各ブラウザがそのデータを内部にコピーして新バージョンを出す構造なのか、ユーザーが新しいデータの入ったブラウザへ更新していなければ、自分のウェブサイトで誤った時刻を見ることになるのか気になる
例えばメキシコは 2022 年に 夏時間 を廃止した: https://en.wikipedia.org/wiki/Time_in_Mexico
pytzやmoment-timezoneのようなサードパーティライブラリならサーバー側ライブラリを更新すればよく、標準ライブラリで処理するなら言語バージョンを上げればよいが、Temporal と訪問者のブラウザではどうなるのか分からないユーザーのコンピュータもこのデータベースにアクセスでき、更新を処理しそうだが確信はない。ブラウザが別途このデータベースをダウンロードして維持することもあり得るし、実装によって異なりそう
関連データベース: https://en.wikipedia.org/wiki/Tz_database
Intlヘルパーのためにロケールも最新に保つ必要があるので、そちらを調べれば似た答えが得られる可能性が高いtzdbを使うユーザーが新しいタイムゾーンデータを反映するようブラウザやシステムを更新していなければ、誤った時刻を見る可能性があるというのは概ね正しそう。ただし、これは現在の一般的な慣行と大きく異なるものではない
代替案は、すべてのアプリケーションが独自の
tzdbを積むことだが、一部のアプリはそうしており、正当な場合もある。しかし一般的には無駄であり、アプリケーション間の不一致を増やし、更新周期もより遅くなる可能性が高い誰かがタイムゾーンを変更したときに検知するイベントを追加する必要がある
「プログラマーが時間について信じている誤り」のリストに、「プログラマーは使用中にタイムゾーンが固定されていると信じている」も入れられる。現実には毎日何百万人もの人がタイムゾーンを移動している
もちろんライブラリには役立つかもしれない
.Nowが関数呼び出しではない点が非常に残念。プロパティであるべきではないループ内で
.Nowを複数回評価すると、毎回異なる値が出る可能性があり、これは予想外C#も同じ選択をしたが、そうすべきではなかった: https://ericlippert.com/2014/05/19/when-should-i-write-a-pro...
Nowは実際の値を取得する関数群の名前空間で、たとえばNow.instant()のような形Temporal提案は本当に気に入っているが、1つ引っかかる点がある。比較で参照同一性に依存していること
つまり
Temporal.Instant.from('2020-01-01') != Temporal.Instant.from('2020-01-01')になる本質的に悪いわけではないが、これらのオブジェクトを
MapのキーにしたりSetに集めたりする能力を事実上なくしてしまう。なぜそう決めたのかは分かるが、できないのは残念いつかRecordsとTuplesが実際に出てくれば、それを基盤にしたバージョンが生まれるかもしれない
==で参照ではない同等性を持つものはある?.epochMillisecondsをMap/Setのキーや要素として使えそう。ただしMapでタイムゾーンデータの保持やInstantの追加演算が必要なら、値側に元のInstantを保持する必要があるSetの場合はMap{v.epochMilliseconds:v}のようにしてInstantを保持できる素晴らしいわけではないが、これはTemporalよりもJavaScript自体を責めるべき問題。ランタイムに処理させることもできるだろうが、そうするとポリフィルが失敗する
Map/Setで使うのは浮動小数点数をMap/Setで使うのに似ていて、普通は悪い考えString()や'2020-01-01'を使えばいいのでは?既存APIと一緒に動作する比較関数を提供するのは堅実な設計。新しい型を追加するときの必須条件にしてもいいくらい
たとえば
Temporal.Duration.compareをsortに渡せば、PT1H、PT1H30M、PT1H45M、PT2Hの順にソートできる。設計者たちに拍手を送りたいTemporalという名前がMomentやDatetimeのような一般的な時間オブジェクトとの衝突を避ける意図なのは分かるが、名前はいまいち初めて見たときはガベージコレクション制御のようなものかと思った
temporalには、永遠性と対比される時間に関するもの、文法上の時制や時間区分に関するもの、空間と区別される時間に関するもの、という意味がある: https://www.merriam-webster.com/dictionary/temporal悪くない名前に聞こえる
まず
Temporalは名詞ではなく形容詞。時間に関係はあるが、直感的にしっくりこないより重要なのは、古いコードとの衝突確率を下げるために変な名前を選ぶと、時間が経つほど言語がより不透明になるという点
PromiseやGeneratorを追加したときも、そのオブジェクト名を使うライブラリは多かった。予約語と違って組み込みオブジェクトは上書きできるので、大きな問題ではなかった標準委員会は言語の長期的な健全性のために、ぶれずに**
Time**を使うべきだと思うもちろんこの議論はもう過ぎたことで、ブラウザベンダーとJSエンジン実装者たちは合意しているのだろう
DateV2のほうがまし後方互換性が必要なのは分かるが、
Temporalは響きがいまいち。DateTimeはJS標準にある名前でもないのに、なぜそれを使わなかったのか分からない。それまではconst DateTime = Temporalとしておくつもり関連記事:
JavaScript Temporal Is Coming - https://news.ycombinator.com/item?id=42809834 - 2025年1月、コメント18件
Mozilla: Temporal (Limited Availability) - https://news.ycombinator.com/item?id=42776548 - 2025年1月、コメント1件
Is It Time for the JavaScript Temporal API? - https://news.ycombinator.com/item?id=29712118 - 2021年12月、コメント100件
Temporal: Getting started with JavaScript's new date time API - https://news.ycombinator.com/item?id=27661667 - 2021年6月、コメント194件
JavaScript Temporal - https://news.ycombinator.com/item?id=27395236 - 2021年6月、コメント1件
JavaScript Proposal Temporal for Dates is now stage 3 - https://news.ycombinator.com/item?id=26432346 - 2021年3月、コメント1件
大きな船は舵を切るのに時間がかかる、という言葉がまさに当てはまる