Node.js 26でTemporal APIが標準に!Dateの月末ズレ・夏時間をTemporalで書き直してみた
🙌 結論から
Node.js 26 から、日付・時刻の新しい API である Temporal がフラグなしでそのまま使えるようになりました!
JavaScript の Date で月末の計算やタイムゾーンに悩んだことがある人なら、正直マジで嬉しいアップデートです。
手元に Node.js 26.10.0 を入れて、Date でつまずきやすい処理を Temporal と並べて動かしてみました(・∀・)
「1月31日の1か月後」が3月3日にならない、それだけでも乗り換える理由になると感じました。
ただ、ブラウザ側や TypeScript の設定など、すぐ全部を置き換えるには少し準備がいるので、そのあたりも正直に書いておきます(^^)
💡 Node.js 26 で何が変わったのか
Node.js 26.0.0 は2026年5月5日にリリースされました。
V8 が 14.6 に上がり、その中で Temporal がデフォルトで有効になっています🎉
Temporal は TC39 で2026年3月に Stage 4 に到達していて、ES2026 の仕様に入りました!
Node.js 26 は今は Current ですが、公式のスケジュールでは 2026年10月に LTS に移行する予定です。
本番のサーバーを LTS で運用しているなら、ちょうど次のアップグレードで Temporal が手に入るタイミングなんですよね😊
ちなみに Node.js 22 で typeof Temporal を実行すると undefined が返ってきます。
今のところ、使えるのは 26 以降だけです( .. )
👀 Date と Temporal を並べて動かしてみた
まずは、Date で一度は踏みがちな「月末に1か月足す」処理です。
const d = new Date(2026, 0, 31); // 1月31日
d.setMonth(d.getMonth() + 1);
console.log(d.toDateString()); // Tue Mar 03 2026
const p = Temporal.PlainDate.from('2026-01-31');
console.log(p.add({ months: 1 }).toString()); // 2026-02-28
Date は2月31日を作ろうとしてあふれ、3月3日まで進みます(´・ω・`)
Temporal は月末に丸めて 2月28日を返してくれました。
丸めずにエラーにしたいときは { overflow: 'reject' } を渡すと RangeError が投げられます🔥
請求の締め日のように「勝手に丸められると困る」処理では、こっちを使うのがよさそうです👍
次は日付文字列のパースです。
new Date('2026-09-28') は UTC の0時として解釈されるので、TZ=America/New_York で実行すると 前日の9月27日 20:00 になりました😭
同じ文字列を Temporal.PlainDate.from() に渡すと、どのタイムゾーンで動かしても 2026-09-28 のままです。
「日付だけ」を扱う型があるのが、Temporal の一番わかりやすい違いだと思います!
あと、new Date(2026, 9, 1) が10月1日になる「月が0始まり」問題も、Temporal では month: 9 がそのまま9月です。
これだけでレビューで「この 9 って何月?」と確認する手間が減りますね(^^)
🔥 夏時間をまたぐ計算が一番ありがたかった
一番「これは Date では書きたくない・・・」と思ったのが夏時間です。
ニューヨークでは2026年11月1日に夏時間が終わるので、その日の 0:30 を起点に試しました。
const ny = Temporal.ZonedDateTime.from('2026-11-01T00:30[America/New_York]');
ny.add({ days: 1 }).toString();
// 2026-11-02T00:30:00-05:00[America/New_York]
ny.add({ hours: 24 }).toString();
// 2026-11-01T23:30:00-05:00[America/New_York]
「1日後」と「24時間後」が別の結果になるのが、ちゃんとコードで書き分けられます!
Date だとミリ秒を足すしかないので、この区別は自分で気をつけるしかありませんでした(T_T)
海外拠点と会議時間をやり取りするような処理も、withTimeZone() で変換できます。
const tokyo = Temporal.ZonedDateTime.from('2026-09-28T09:00[Asia/Tokyo]');
tokyo.withTimeZone('America/New_York').toString();
// 2026-09-27T20:00:00-04:00[America/New_York]
期間の計算も until() で素直に書けて、9月28日から12月25日までを月単位で出すと P2M27D(2か月と27日)でした(。•̀ᴗ-)✧
🔧 TypeScript で型を通すには lib の指定が必要
Node.js で動くからといって、TypeScript がそのまま型を知っているわけではありません。
TypeScript 7.0.2 で --lib es2024 のままチェックすると、Cannot find namespace 'Temporal' で止まりました(・_・;)
Temporal の型は TypeScript 6.0 から同梱されていて、esnext.temporal(または esnext)を lib に足すと通ります。
{
"compilerOptions": {
"target": "es2024",
"lib": ["es2024", "esnext.temporal"]
}
}
名前が temporal.esnext ではなく esnext.temporal なのは要注意です。
TypeScript 6.0 の公式ブログで逆の表記になっていたことがあって、GitHub の Issue で指摘されていました🤔
私の手元の TypeScript 7.0.2 では、esnext.temporal で型チェックが通るのを確認しています✅
🤔 すぐ全部置き換えるのはまだ早いと思った点
推したうえでですが、気になった点も2つあります。
1つ目はブラウザです。
Chrome と Edge は 144、Firefox は 139 から Temporal が入っていますが、Safari の対応状況は使う前に Can I use で確認しておきたいところです。
フロントでも使うなら、当面は temporal-polyfill や @js-temporal/polyfill のようなポリフィルと組み合わせる前提になりそうです💪
2つ目は比較の書き方です。
Date のノリで a < b と書くと、TypeError: Do not use Temporal.PlainDate.prototype.valueOf; use Temporal.PlainDate.prototype.compare for comparison. で落ちます。
Temporal.PlainDate.compare(a, b) を使えば -1 / 0 / 1 が返ってくるので、ソート関数にもそのまま渡せます。
エラーメッセージが親切なので、気づけないまま残ることはなさそうですね(^_^)
私はまず、サーバー側の締め日計算やバッチの日付処理から Temporal に寄せていこうと思っています😎
🔧 Laravel の API と日付をやり取りするときの書き方
Laravel のバックエンドと日付をやり取りするときに、私が Temporal でやっている書き方をまとめておきます(^^)
基本は、API では UTC の時刻(ISO 8601 の文字列)でやり取りして、画面に出すときだけ日本時間にするです。
// API から受け取った "2026-10-01T03:00:00Z" を日本時間で表示する
const instant = Temporal.Instant.from(res.created_at);
const jst = instant.toZonedDateTimeISO('Asia/Tokyo');
console.log(jst.toPlainDateTime().toString()); // 2026-10-01T12:00:00
// 日付だけ(誕生日・締め日など)は PlainDate で送る
const due = Temporal.PlainDate.from('2026-10-31');
await api.post('/tasks', { due_date: due.toString() }); // "2026-10-31"
ポイントは、「時刻」と「日付だけ」を型で分けることです👀
締め日や誕生日のように時刻を持たない値を Date で扱うと、タイムゾーンの変換で1日ずれることがありました(・_・;)
PlainDate なら時刻を持たないので、このずれが起きません✨
Laravel 側も、created_at は UTC、締め日は date 型のカラム、と分けておくと、フロントとサーバーで扱いが揃います。
パパ系Webエンジニア〜愛用ガジェット一覧 🛍️
この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌
👍 まとめ
Node.js 26 の Temporal は、Date で遠回りしていた月末・タイムゾーン・夏時間の処理を素直に書けるようにしてくれる API でした。
LTS 移行が2026年10月予定なので、サーバー側から少しずつ使い始めるにはちょうどいい時期だと思います!
TypeScript なら lib に esnext.temporal を足すのだけは忘れないようにしたいです。
Node.js で TypeScript をそのまま動かす話は Node.js 24 で TypeScript がそのまま動く時代になってた話 にも書いているので、あわせてどうぞ🙌
※ バージョン番号・料金・機能は今後も変わる可能性があります。最新情報は公式サイトをご確認ください👀