JavaScript の新しい日付時刻 API の Temporal を知っていますか?

記事執筆時点(2026/08)でのステータスは、プロポーザルが「Stage4」、Baseline が「Limited availability」となっています。MDN でも実験的な機能と紹介されており実用段階ではありませんが Google Chrome v144 や Node.js v26 で標準搭載されるなどランタイム環境のサポートは広がりつつあります。Safari で実装されればこの勢いはさらに加速するかもしれません。

とはいえ Temporal のインターフェイスはシンプルで直感的とは思えず、お試しコードを書くところからつまづいてしまいました。リファレンスを見ながら考え込んでしまった自分に「最初はこういうコードから試してみるといいよ!」と説明するつもりで入門記事を書いてみます。

目次

ざっくり読みたい方向け(所要時間 5 分)

じっくり読みたい方向け(所要時間 15 分)

1章) 概要を知る

Temporal 名前空間

Temporal 名前空間にはいくつかのクラスがあります。
これらは、日付、時刻、日付時刻など用途に応じて呼び分けます。

クラス 用途 値の例
Temporal.PlainDate 日付 2026-08-01
Temporal.PlainTime 時刻 10:00:00
Temporal.PlainDateTime 日付時刻 2026-08-01T09:00:00
Temporal.PlainMonthDay 月日 08-01
Temporal.ZonedDateTime 日付時刻(タイムゾーン付き) 2026-08-01T09:00:00[Asia/Tokyo]
Temporal.Instant 日付時刻(ナノ秒精度) 1785542400000000000n
Temporal.Duration 日付時刻(差分) P3D

はじめに PlainDateTime ZonedDateTime を使ったコードを見てみましょう。どちらも日付時刻を扱うクラスで、タイムゾーンありなしの違いがあります。

文字列またはオブジェクトを渡し、インスタンスを生成します。

const t1 = Temporal.PlainDateTime.from('2026-08-01T09:00:00');
const t2 = Temporal.ZonedDateTime.from('2026-08-01T09:00:00[Asia/Tokyo]');
const t1 = Temporal.PlainDateTime.from({
  year: 2026, month: 8, day: 1, hour: 9, minute: 0, second: 0,
});

const t2 = Temporal.ZonedDateTime.from({
  timeZone: 'Asia/Tokyo',
  year: 2026, month: 8, day: 1, hour: 9, minute: 0, second: 0,
});

文字列またはプロパティで値を取り出します。

t1.toString(); // 2026-08-01T09:00:00
t2.toString(); // 2026-08-01T09:00:00+09:00[Asia/Tokyo]
t1.year; // 2026
t1.month; // 8
t1.day; // 1
t1.hour; // 9
t1.minute; // 0
t1.second; // 0

t2.timeZoneId; // Asia/Tokyo
t2.year; // 2026
t2.month; // 8
t2.day; // 1
t2.hour; // 9
t2.minute; // 0
t2.second; // 0

Temporal の文字列表現

Temporal の入出力は RFC 3339 をベースとした文字列を扱います。

08-01
2026-08-01
2026-08-01T09:00:00+09:00

タイムゾーンを持つ場合、その情報が追加されます。

2026-08-01T09:00:00+09:00[Asia/Tokyo]

暦を指定した場合、カレンダー注釈が追加されます。

2026-08-01T09:00:00+09:00[Asia/Tokyo][u-ca=japanese]

from() は文字列表現を入力で受け取ります。

const t1 = Temporal.PlainDateTime.from('2026-08-01T09:00:00');
const t2 = Temporal.ZonedDateTime.from('2026-08-01T09:00:00+09:00[Asia/Tokyo]');
const t3 = Temporal.ZonedDateTime.from('2026-08-01T09:00:00+09:00[Asia/Tokyo][u-ca=japanese]');

toString() は文字列表現を出力します。

t1.toString(); // 2026-08-01T09:00:00
t2.toString(); // 2026-08-01T09:00:00+09:00[Asia/Tokyo]
t3.toString(); // 2026-08-01T09:00:00+09:00[Asia/Tokyo][u-ca=japanese]

ECMAScript 拡張の ISO-8601 と RFC 3339
https://tc39.es/proposal-temporal/docs/ja/iso-string-ext.html

Temporal では ISO8601 以外の暦も扱うことができます。
いろいろに変化させられるのがおもしろいですね。

const t = Temporal.ZonedDateTime.from('2026-08-01T09:00:00[Asia/Tokyo]');

t.withCalendar('iso8601');
// 西暦 2026年8月1日
// {
//   era: undefined, eraYear: undefined,
//   year: 2026, month: 8, day: 1,
// }

t.withCalendar('japanese');
// 日本の暦 令和8年8月
// {
//   era: 'reiwa', eraYear: 8,
//   year: 2026, month: 8, day: 1,
// }

t.withCalendar('hebrew');
// ヘブライ歴 5786年11月
// {
//   era: undefined, eraYear: undefined,
//   year: 5786, month: 11, day: 18,
// }

現在時刻

Temporal.Now を使って現在時刻を取り出します。
Now は Temporal 名前空間のクラスではなくユーティリティオブジェクトです。

このオブジェクトは他のクラスへの変換メソッドを持っています。

const t1 = Temporal.Now.plainDateTimeISO(); // to PlainDateTime
const t2 = Temporal.Now.zonedDateTimeISO(); // to ZonedDateTime

ISO というサフィックスの通り、デフォルトで ISO8601 の暦が適用されます。

t1.calendarId; // iso8601
t2.calendarId; // iso8601

クラスに変換した後は文字列またはプロパティで値を取り出せます。

t1.toString();

t1.timeZoneId;
t1.year;
t1.month;
t1.day;
t1.hour;
t1.minute;
t1.second;

日付時刻の構成要素を表すクラス

カレンダーや時計の「2026年8月1日」「9時0分」は、見ている人それぞれのエリアでの時間を表すものです。Temporal のドキュメントでは壁時計時間(wall-clock time)と説明されています。新しい年が始まる「1月1日0時0分」のラベリングは世界共通ですが祝うタイミングは違いますよね。このように、タイミングよりも年月日が意味を持つケースがあります。

Temporal には「年月日時分秒」といった日付時刻の構成要素を扱う専用クラスが存在します。これらはタイムゾーンを持たない素の値として Plain プレフィクスが付いています。

クラス 表す値
Temporal.PlainDate 年月日 2026-08-01
Temporal.PlainTime 時分秒 10:00:00
Temporal.PlainDateTime 年月日時分秒 2026-08-01T09:00:00
Temporal.PlainMonthDay 月日 08-01

Plain プレフィクスのクラスは目的に沿った値だけを扱います。

const t = Temporal.PlainDate.from('2026-08-01');

t.toString(); // '2026-08-01'
t.year; // 2026
t.month; // 8
t.day; // 1
const t = Temporal.PlainTime.from('09:01:02');

t.toString(); // '09:01:02'
t.hour; // 9
t.minute; // 1
t.second; // 2
const t = Temporal.PlainDateTime.from('2026-08-01T09:01:02');

t.toString(); // '2026-08-01T09:01:02'
t.year; // 2026
t.month; // 8
t.day; // 1
t.hour; // 9
t.minute; // 1
t.second; // 2
const t = Temporal.PlainMonthDay.from('08-01');

t.toString(); // '08-01'
t.month; // 8
t.day; // 1

Plain プレフィクスのクラスにタイムゾーンを与えると ZonedDateTime に変換できます。

// TZ=Africa/Abidjan として Node.js を実行
const t = Temporal.PlainDate.from('2026-08-01');

// 2026-08-01T00:00:00+00:00[Africa/Abidjan]
t.toZonedDateTime(Temporal.Now.timeZoneId()).toString();

// 日本のタイムゾーンに変換(UTC との時差 9 時間)
// 2026-08-01T00:00:00+09:00[Asia/Tokyo]
t.toZonedDateTime('Asia/Tokyo').toString(); 

// イギリスのタイムゾーンに変換(8 月はサマータイムで UTC との時差 1 時間)
// 2026-08-01T00:00:00+01:00[Europe/London]
t.toZonedDateTime('Europe/London').toString();

特定の瞬間を表すクラス

人類がはじめて月面に降り立ったのは 1969年7月21日2時56分15秒(UTC)です。その様子は世界中でリアルタイム中継され人類史上の価値ある瞬間となりました。このような場合は時分秒よりも、時間軸上のプロットである世界共通の「瞬間」に価値が置かれます。Temporal のドキュメントではカレンダーや場所によらない特定の時点での時刻(exact time)と説明されています。

Temporal には瞬間を表現するクラスが存在します。

クラス 表す値
Temporal.ZonedDateTime 瞬間
タイムゾーンによる解釈
2026-08-01T09:00:00+0900[Asia/Tokyo]
Temporal.Instant 瞬間
ナノ秒精度
1785542400000000000n
// 月面に降り立った瞬間
const t = Temporal.ZonedDateTime.from('1969-07-21T02:56:15Z[UTC]');

// コートジボワールの都市アビジャン: 深夜 2 時
// 1969-07-21T02:56:15+00:00[Africa/Abidjan]
t.withTimeZone('Africa/Abidjan').toString();

// アームストロング船長の故郷オハイオ州: 夜 10 時
// 1969-07-20T22:56:15-04:00[America/New_York]
t.withTimeZone('America/New_York').toString();

// 東京: 昼 11 時
// 1969-07-21T11:56:15+09:00[Asia/Tokyo]
t.withTimeZone('Asia/Tokyo').toString();

瞬間を表現する ZonedDateTime Now は Instant を取り出すメソッドを持っています。Instant は時間軸上のゼロ地点(エポックタイム)からの相対位置を示すプロパティを提供します。

const t1 = Temporal.Now.instant();
t1.epochMilliseconds; // ミリ秒: 1785542400000
t1.epochNanoseconds; // ナノ秒: 1785542400000000000n

const t2 = Temporal.ZonedDateTime.from(...).toInstant();
t2.epochMilliseconds;
t2.epochNanoseconds;

const t3 = Temporal.PlainDateTime.from(...).toInstant();
// ERROR: PlainDateTime は瞬間を表現しないため Instant に変換できない

📖 目次に戻る

2章) Temporal の日付時刻演算を知る

比較

equals() で日付時刻が等しいかどうか判定できます。

const t1 = Temporal.PlainDateTime.from('2026-05-01T09:00:00');
const t2 = Temporal.PlainDateTime.from('2026-05-01T09:00:00');
t1.equals(t2); // true
const t1 = Temporal.ZonedDateTime.from('2026-05-01T00:00:00[Africa/Abidjan]');
const t2 = Temporal.ZonedDateTime.from('2026-05-01T09:00:00[Asia/Tokyo]');
t1.equals(t2); // true

compare() で大小の関係を数値化できます。

// Temporal.<クラス名>.compare(t1, t2);
// [-1] t2 が大きい
// [0] t1 と t2 が等しい
// [1] t1 が大きい

クラス名は比較対象のスコープを限定するため、日付のみ比較、日付時刻で比較、といった呼び分けができます。

const t1 = Temporal.PlainDateTime.from('2026-08-01T09:01:01');
const t2 = Temporal.PlainDateTime.from('2026-08-01T10:02:02');

// PlainDate: 日付で比較すると t1,t2 は等しい
Temporal.PlainDate.compare(t1, t2); // 0

// PlainDateTime: 日付時刻で比較すると t2 が大きい
Temporal.PlainDateTime.compare(t1, t2); // -1

compare を応用すると日付時刻のソートができます。

const dates = [
  Temporal.PlainDateTime.from('2026-08-01T13:15:00'),
  Temporal.PlainDateTime.from('1995-08-01T23:00:00'),
  Temporal.PlainDateTime.from('2026-08-01T09:30:00'),
].sort(Temporal.PlainDateTime.compare);

dates.map(d => d.toString());
// [
//   '1995-08-01T23:00:00',
//   '2026-08-01T09:30:00',
//   '2026-08-01T13:15:00'
// ]

差分

日付時刻の差分は専用クラスの Duration が担います。

クラス 表す値 値の例
Temporal.Duration 日付時刻(差分) P3D

Duration は since() until() の結果として得られます。
文字列またはプロパティで値を取り出します。

const past = Temporal.PlainDateTime.from('2026-08-01T00:00:00');
const future = Temporal.PlainDateTime.from('2026-08-02T10:45:00');
// since: 過去にさかのぼった差分
const d1 = future.since(past);

d1.toString(); // P1DT10H45M
d1.days; // 1
d1.hours; // 10
d1.minutes; // 45
d1.seconds; // 0
// until: 未来に到達するまでの差分
const d2 = past.until(future);

d2.toString(); // P1DT10H45M
d2.days; // 1
d2.hours; // 10
d2.minutes; // 45
d2.seconds; // 0

加算

N 日後、N 時間後、といった加算には add() を使用します。

const t = Temporal.PlainDateTime.from('2026-08-01T09:00:00');

// 1 ヶ月後 / 2026-09-01T09:00:00
t.add({ months: 1 }).toString();

// 30 日後 / 2026-08-31T09:00:00
t.add({ days: 30 }).toString();

// 5 時間後 / 2026-08-01T14:00:00
t.add({ hours: 5 }).toString();

減算

N 日前、N 時間前、といった減算には subtract() を使用します。

const t = Temporal.PlainDateTime.from('2026-08-01T09:00:00');

// 1 ヶ月前 / 2026-07-01T09:00:00
t.subtract({ months: 1 }).toString();

// 30 日前 / 2026-07-02T09:00:00
t.subtract({ days: 30 }).toString();

// 5 時間前 / 2026-08-01T04:00:00
t.subtract({ hours: 5 }).toString();

更新

年月日時分秒の置き換えには with() を使用します。

const t = Temporal.PlainDateTime.from('2026-08-01T00:00:00');

// 日付を更新 / 2026-09-02T00:00:00
t.with({ year: 2026, month: 9, day: 2 }).toString();

// 時刻を更新 / 2026-08-01T12:13:14
t.with({ hour: 12, minute: 13, second: 14 }).toString();

時間の丸め

round() を使うと時間の丸め処理ができます。ビルトインでできる計算の幅が広がりましたね。

const t = Temporal.PlainTime.from('18:42:15');

// trunc(0 方向に切り捨て) / 18:30:00
t.round({
  smallestUnit: 'minutes',
  roundingMode: 'trunc',
  roundingIncrement: 30,
}).toString();

// expand(0 から外側に切り上げ) / 19:00:00
t.round({
  smallestUnit: 'minutes',
  roundingMode: 'expand',
  roundingIncrement: 30,
}).toString();

// halfCeil(四捨五入) / 18:30:00
t.round({
  smallestUnit: 'minutes',
  roundingMode: 'halfCeil',
  roundingIncrement: 30,
}).toString();

オプションの説明 - duration.round
https://tc39.es/proposal-temporal/docs/ja/duration.html#round

Temporal お試し環境

この記事で紹介したコードは Node.js v26 で実行可能です。
暦指定など一部でエラーが出た場合は https://github.com/tc39/proposal-temporal@js-temporal/polyfill をお試しください。

すでに実装が始まっている Google Chrome でも、デベロッパーツールで Temporal とタイピングすればいろいろ遊んでみることができます。


📖 目次に戻る

所感 〜関心の分離のトレードオフ〜

Temporal では既存の Date オブジェクトの課題解決にあたり、日付時刻の扱いについて根本から見直しが入りました。

名前空間には 7 つのクラスが並び、それぞれが概念を取り扱う専用道具として機能します。その道具のひとつを取り出せば、適度に抽象化され理路整然と並んだメソッドで日付時刻の演算ができます。このようなインターフェイスに、私たちが日常的に心がけている「関心の分離」を思い起こしました。

現実世界にあてはめてみると、包丁棚に出刃包丁、菜切り包丁、中華包丁、柳刃包丁…とズラリと並んだような状態でしょうか。玄人向けの調理器具ですね。リアルな体験としてじゃがいもを調理しようと思った時、菜切り包丁では芽がくりぬけず、出刃包丁では皮がむけず、なんか違うと思いながら刺身用のめちゃ長い柳刃包丁を使ったことがあります。結局のところ欲しかったのはプロ並の切れ味はなくとも使いやすい万能包丁でした。

包丁と Temporal はまったく違うものですが利用者としての体験は似ています。たくさんの専用道具を提示されると、どれを使えば良いのか分からず、最初に手にしたものが正解なのか分からず、迷いながら使いつづけます。日付時刻を扱うシーンは往々にして、関数の中だけの狭いスコープの演算と、フロントエンド外のレイヤーとのやり取りでタイムゾーンの解釈に気を配るくらいです。選択の余地がない Date オブジェクトのほうが手に取りやすい万能包丁に似ています。

Temporal は関心や責務という意味において「シンプル」です。しかしながら身近でなじみがある「イージー」ではありません。値をナノ秒単位で扱うことができ、世界各国のタイムゾーンや暦に変換できる機能性の高さを実現しながら(おそらく世界中のフロントエンドエンジニア待望の)柔軟な文字列フォーマッターが提供されなかったことも、イージーを感じさせない要因です。

Simple Made Easy | Rich Hickey
シンプルさとは使いやすいことだと思っているのならそれは間違っています。
これらは全くもって違うものです。Easy と Simple は違います。

私たちの開発体験でもシンプルを重要視する場面は数多くあります。ポリモーフィズムを適用したクラスの分離や、レイヤードアーキテクチャによる構造の分離、マイクロサービス化など、さまざまな粒度でシンプルを追求します。シンプルは保守の観点で大きなメリットをもたらしつつも、利用者の前に専用道具をずらりと並べて理解を強制します。手作業の更新を補助する開発者用コマンド、ファサードクラスの導入など、イージーの提供がないと後のメンテナーには辛いものになるかもしれません。

Temporal も然り、どうにかイージーに寄せる方法としてラッパーなど作ると思考実験としておもしろそうですね。設計やコードリーディングの学びの機会にもなりそうです。

/**
 * こういうインターフェイスはどうだろう
 * TemporalHarness() - now
 * TemporalHarness(new Date()) - legacy date
 * TemporalHarness('2026-08-01T00:00:00[Asia/Tokyo]') - RFC 9559
 * TemporalHarness('2026年08月01日', 'yyyy年MM月dd日') - string parser
 */
const th = new TemporalHarness('2026-08-01')
  .withTimeZone('Asia/Tokyo')
  .withTime('09:00:00')
  .addDays(3)
;

th.format('MM/dd hh:mm'); // 08/04 09:00
th.month; // 8
th.milliSeconds; // 123456789...
th.toLegacyDate(); // Date {}
th.toDuration(new TemporalHarness()); // { days:-11,minutes:-12,seconds:-13... }

読者のみなさんは Temporal をどのように受け止めましたか?

この記事が Temporal にこれから入門するどなたかの参考になれば幸いです。