JavaScriptを触ったことがあるすべてのエンジニアが、人生で一度は叫んだことがあるはずです。「なぜ1月が『0』で、12月が『11』なんだ!?」「なぜ日付を変更しただけで元のオブジェクトまで勝手に書き換わるんだ!?」と。
1995年、Netscapeのブレンダン・アイク(Brendan Eich)氏がわずか10日間でJavaScriptを設計した際、当時のJava 1.0に存在していた悪名高き java.util.Date の実装をそのまま突貫工事で移植してしまった――この約30年前に生まれた歴史の負債が、現代に至るまでWebフロントエンド界隈に数え切れないバグと阿鼻叫喚をもたらしてきました。
Moment.js、date-fns、Day.js、Luxonといった外部ライブラリを入れなければまともに日付の足し算やタイムゾーン変換すらできなかった日々に、ついに終止符が打たれます。JavaScriptの標準化団体(ECMA TC39)によって長年にわたり徹底的に議論が重ねられてきた待望の次世代日時標準仕様「Temporal(テンポラル)API」が、Stage 3を経て主要ブラウザへのネイティブ実装段階に入りました!
なぜ従来の Date はこれほどまでに危険だったのか?そして「Temporal」はそれをどのように美しく解決するのか?基本設計から実践コード、ブラウザ対応状況まで徹底的に解説します。
1. 従来の「new Date()」が抱える4大凶悪トラップ
Temporalの素晴らしさを実感するために、私たちが30年間戦わされ続けてきた従来の Date オブジェクトの「4大凶悪トラップ」をおさらいしておきましょう。
罠①:月がゼロオリジン(0〜11問題)
// 2026年10月21日を作りたいのに…
const d = new Date(2026, 10, 21);
console.log(d.getMonth()); // 10 が返るが、これは「11月」!
// 正しくは new Date(2026, 9, 21) と書かなければならない!
「年は西暦、日は1から始まるのに、なぜか月だけが0から始まる」。人類の歴史上、getMonth() + 1 を忘れたことによるバグで失われたエンジニアの睡眠時間は計り知れません。
罠②:ミュータブル(破壊的変更)による恐怖の参照バグ
const today = new Date();
const nextWeek = today; // 参照渡し
nextWeek.setDate(today.getDate() + 7);
// nextWeek を変更しただけなのに、なんと today まで書き換わってしまう!
console.log(today.toDateString()); // 1週間後の日付になっている!
Date オブジェクトはミュータブル(変更可能)です。関数に Date を渡して内部で setDate() などを呼ぶと、呼び出し元の変数の値まで勝手に変わってしまいます。これを防ぐには毎回 new Date(d.getTime()) のように自前でクローンを作らなければなりませんでした。
罠③:タイムゾーンの曖昧さとローカル時刻の混同
従来の Date オブジェクトは、内部的には「UTC 1970年1月1日からのミリ秒数(UNIXエポック)」を1つ持っているだけです。「タイムゾーン情報」そのものをオブジェクト内に保持していません。そのため、ブラウザが実行されているPCのシステムタイムゾーン(ローカル時刻)に勝手に依存して文字列化され、海外サーバーとの通信で日付が1日ズレる「時差バグ」が頻発しました。
罠④:文字列パースの怪奇現象(ブラウザ依存)
new Date("2026-10-21") とハイフン区切りで渡すとUTC午前0時として解釈され、日本時間(JST: +09:00)では午前9時になります。しかし new Date("2026/10/21") とスラッシュ区切りで渡すとローカル時刻午前0時として解釈されるなど、ブラウザの実装によってパース挙動が異なる地獄が存在していました。
2. Temporal APIの基本思想:完全イミュータブルと「責務の分離」
Temporal APIの最大の特徴は、「すべてのオブジェクトが完全なイミュータブル(不変)」であり、さらに「用途に応じて明確にクラス(型)が分かれている」ことです。
1つの万能神クラス Date にすべてを押し込むのをやめ、「カレンダー上の日付」「目覚まし時計の時刻」「地球上の絶対時間」「特定のタイムゾーンの日時」を厳密に区別して扱います。
| Temporalクラス | 保持する情報 | 具体的な用途例 |
|---|---|---|
Temporal.PlainDate |
年・月・日(時間なし・タイムゾーンなし) | 誕生日、祝日、創立記念日、イベントの開催日 |
Temporal.PlainTime |
時・分・秒・ミリ秒(日付なし・タイムゾーンなし) | 開店時間(10:00)、始業ベル(09:00)、アラーム |
Temporal.PlainDateTime |
年・月・日・時・分・秒(タイムゾーンなし) | カレンダーの予定枠(タイムゾーンを固定しない枠組み) |
Temporal.ZonedDateTime |
完全な日時 + タイムゾーン(Asia/Tokyo等) | 国際オンライン会議、航空便の離着陸時刻、金融取引 |
Temporal.Instant |
UTC基準の絶対エポック時間(ナノ秒精度) | サーバーログのタイムスタンプ、DBの作成日時 |
Temporal.Duration |
時間の長さ(年・月・日・時間・分・秒) | タイマー計測、「残り5分30秒」、アニメーション継続時間 |
3. 実践コードで見る!Temporalの劇的な進化
① 現在日時の取得:もう「1月=0」に悩まされない!
// 今日の日付を取得
const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // "2026-10-21"
// 月の数値を取得(当たり前のように 1月は 1、10月は 10!)
console.log(today.month); // 10 🎉
console.log(today.day); // 21
today.month は直感通り 10 を返します。もう + 1 を足す必要も、忘れてバグを出すこともありません。
② 日付の計算:安全で直感的な「.add()」と「.subtract()」
const start = Temporal.PlainDate.from('2026-10-21');
// 3ヶ月と5日後を計算!
const future = start.add({ months: 3, days: 5 });
console.log(future.toString()); // "2027-01-26"
console.log(start.toString()); // "2026-10-21"(元の start は絶対に壊れない!)
オブジェクトは完全イミュータブルなので、add() や subtract() を呼ぶと必ず新しいオブジェクトが返されます。元のオブジェクトが書き換わる心配はゼロです。
③ 二つの日時の差分を求める:「.since()」と「.until()」
const date1 = Temporal.PlainDate.from('2026-10-21');
const date2 = Temporal.PlainDate.from('2026-12-31');
// 2026年大晦日まであと何日?
const diff = date1.until(date2, { largestUnit: 'day' });
console.log(diff.days); // 71日!
console.log(diff.toString()); // "P71D"(ISO 8601期間フォーマット)
これまで (d2 - d1) / (1000 * 60 * 60 * 24) のようにミリ秒から手動計算して端数やうるう秒、夏時間(サマータイム)に怯えていた日々が、完全に過去のものになります。
④ タイムゾーン変換:ZonedDateTimeの圧倒的安心感
// 東京の現在日時を取得
const tokyoTime = Temporal.Now.zonedDateTimeISO('Asia/Tokyo');
console.log(tokyoTime.toString());
// "2026-10-21T10:45:00+09:00[Asia/Tokyo]"
// ロンドン(Europe/London)時間へワンライナーで変換!
const londonTime = tokyoTime.withTimeZone('Europe/London');
console.log(londonTime.toString());
// "2026-10-21T02:45:00+01:00[Europe/London]"
タイムゾーンのサマータイム切り替えルール(Daylight Saving Time)も、ブラウザに内蔵された最新のIANAタイムゾーンデータベース(tzdata)に基づいて自動的に完璧に処理されます。
4. ブラウザ対応状況と、今すぐプロダクションで使う方法
「こんな神仕様、今すぐ使いたい!でもいつから使えるの?」という開発者のために、現状の実装ステータスを整理します。
主要ブラウザの実装状況(2026年現在)
- Firefox(SpiderMonkey):Nightlyビルド等で先行実装され、最も早くから実験的サポートを推進。
- Chrome / Chromium系(V8エンジン):フラグ付き機能(
--js-flags="--harmony-temporal")から正式標準化へ向けた安定版ロールアウトが進行中。 - Safari(JavaScriptCore / WebKit):WebKitエンジニアリングチームが精力的に実装を進めており、主要仕様のテストパス率が急上昇中。
- Node.js / Deno / Bun:最新のV8およびJavaScriptCoreのアップデートに追従し、サーバーサイドJavaScriptランタイムでも標準搭載が進んでいます。
今すぐ手元のプロジェクトに導入する(公式Polyfill)
ブラウザのネイティブ対応が全ユーザーに完全に行き渡るまでの間は、TC39公式に準拠した高機能Polyfill @js-temporal/polyfill を使用することで、今すぐ実戦投入が可能です。
npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
// ネイティブと100%同じAPIで即座に利用可能!
const now = Temporal.Now.plainDateISO();
console.log(now.toString());
5. まとめ:さようならMoment.js、Web標準の勝利へ
長年にわたりフロントエンドのバンドルサイズを肥大化させてきた「巨大な日付ライブラリ(Moment.jsなど)」の時代は、Temporal APIの登場によって静かに終焉を迎えようとしています。
ブレンダン・アイク氏が10日間で駆け抜けた1995年の苦肉の策から約30年。Web標準はついに、世界で最も使われているプログラミング言語に、世界で最も洗練された日時処理システムを授けました。
イミュータブルで、タイムゾーンに強く、直感的で、バグが入り込む隙間がない。フロントエンド開発者の長年の悲願であった「Temporal API」という魔法の杖を手に入れて、日時の呪縛から解放された新しいJavaScript開発を存分に楽しみましょう!
- Dateの歴史的負債:1995年にJava 1.0の欠陥仕様を10日間で突貫移植した結果、30年間バグの温床に。
- 従来の4大欠陥:月が0始まり、ミュータブル(参照渡しで破壊)、タイムゾーン情報なし、文字列パースがブラウザ依存。
- Temporalの革命:完全イミュータブル設計、PlainDate(日付のみ)やZonedDateTime(タイムゾーン付き)など責務ごとに厳格に型分離。
- 計算が超快適:
.add()、.subtract()、.since()、.until()でライブラリ不要の精密計算。 - 今すぐ使える:公式Polyfill
@js-temporal/polyfillにより、既存のプロジェクトでも先行導入可能。
