旧ブログ(ISSEN)から移行しました

【JavaScript】Temporalによる日時操作を学ぶ

【JavaScript】Temporalによる日時操作を学ぶ

杉田侑祐
杉田侑祐22分で読めます
はてなブックマーク

はじめに

この記事の概要

こんにちは、株式会社TOKOSのスギタです!
今回はJavaScriptで標準化された(2026年3月現在)Temporal APIについて解説します。
Temporal APIはDateオブジェクトの問題点を解決するためのAPIです。

対象読者

  • フロントエンドエンジニアの方

この記事で扱う内容、扱わない内容

この記事で扱う内容は下記です。

  • Dateオブジェクトの問題点
  • Temporal APIについて

この記事で扱わない内容は下記です。

  • JavaScriptの基本的な使い方

JavaScriptの標準化について

今回のTemporal APIを学ぶ前にJavaScriptの標準化されるまでの流れを簡単に紹介します。
JavaScriptは現在ECMAScriptという規格で標準化されています。
標準化を行う際にTC39という委員会が標準化を行っています。

TC39 - Specifying JavaScript.

tc39.es

標準化をする際はProposalという形で提案されます。

ProposalはTC39のメンバーが提案し、Stage 0からStage 4まで(Stage 2.7を含む6段階)で進んでいきます。

  • Stage 0 : Strawperson(アイデア段階)
    • 目的 : 問題領域の探索とアイデア出し
    • 意味 : 新しいアイデアの初期探索段階
  • Stage 1 : Proposal(提案)
    • 目的 : 特定のアプローチで解決策を設計する
    • 意味 : 委員会がその問題領域を検討することにコミット
  • Stage 2 : Draft(草案)
    • 目的 : 詳細の詰め、スペックレビュー、実験的実装
    • 意味 : 委員会が優先する解決方向を選択(大きな設計変更はまだあり得る)
  • Stage 2.7 : Candidate(候補 - テスト段階)
    • 目的 : テストコードの作成とスペック準拠のプロトタイプによる検証
    • 意味 : 解決策は完成しており、変更はテストや実装からのフィードバックに限定
  • Stage 3 : Candidate(候補 - 実装段階)
    • 目的 : 実装経験の蓄積と互換性の問題発見
    • 意味 : 実装が推奨される。基本的に変更は想定されない
  • Stage 4 : Finished(完了)
    • 目的 : 年次出版の標準への統合
    • 意味 : ECMAScript標準に組み込まれる

月に一度会議が開かれ、Proposalが議論されています。

GitHub - tc39/agendas: TC39 meeting agendas

TC39 meeting agendas. Contribute to tc39/agendas development by creating an account on GitHub.

github.com

また、今現在も魅力的な様々なProposalが存在しています。

GitHub - tc39/proposals: Tracking ECMAScript Proposals

Tracking ECMAScript Proposals. Contribute to tc39/proposals development by creating an account on GitHub.

github.com

ちなみにTemporalは2017年に提案されました。

なぜ今Temporalを学ぶのか

では、なぜ今Temporalを紹介するのか、その理由は下記になります。

  • 最近Stage 4になったため
  • 主要ブラウザで対応されたため

2026年3月11日に開催された、第113回のTC39の総会でStage 4になりました。
また、Chrome 144以降で利用可能になります。
対応ブラウザ及び、バージョンは下記です。

  • Google Chrome 144
  • Firefox 139
  • Edge 144

Temporal - JavaScript | MDN

Temporal オブジェクトは、組み込みのタイムゾーンおよび暦の表現、実時刻の変換、算術演算、書式化など、さまざまなシナリオで日付と時刻の管理を可能にします。これは、 Date オブジェクトを完全に置き換えるものとして設計されています。

developer.mozilla.org

まだ、Safari等使用できないブラウザもありますが、今後対応されることが期待されています。
というわけで、今後はDateオブジェクトからTemporalが主流になっていく事が考えられます。

Dateオブジェクトの問題点とTemporalの改善

現在主流であるDateオブジェクトの日時操作の問題点とTemporalによって改善された点を簡単に紹介します。
Dateオブジェクトに関しては下記の記事で詳しく紹介しています。

【JavaScript】Dateオブジェクトとタイムゾーンと規格について

この記事ではJavaScriptでDateを扱う際の注意点と扱い方の基礎が学べます。今現在時刻の呼び出しや計算方法も付随して学べます。

ミュータブル(可変)である

Dateオブジェクトはミュータブル(可変)であるため、不整合が発生しやすいです。
setMonthメソッドなどを呼ぶと、元のDateオブジェクトが変更されてしまいます。

Dateオブジェクトのミュータブル問題
const meeting = new Date(2026, 2, 16)
const reminder = meeting // 参照コピー!
 
reminder.setDate(meeting.getDate() - 1)
 
console.log(meeting.toDateString())
// → "Sun Mar 15 2026"
// meeting も変わってしまう!

Temporalはイミュータブル(不変)であるため、元のオブジェクトが変更されずに新しいオブジェクトが作成されます。

Temporalのイミュータブル
const meeting = Temporal.PlainDate.from("2026-03-16")
const reminder = meeting.subtract({ days: 1 })
 
console.log(meeting.toString())
// → "2026-03-16" ✅ 元のまま
 
console.log(reminder.toString())
// → "2026-03-15" ✅ 新しいオブジェクト

タイムゾーンのサポートが不十分

DateはUTCとローカル時間しか扱えず、任意のタイムゾーンを明示的に指定して操作する仕組みがありません。
異なるタイムゾーン間の変換が非常に面倒です。

Dateオブジェクトのタイムゾーン問題
// UTC と ローカルしかない
const date = new Date()
 
console.log(date.toISOString())
// → UTC のみ
 
console.log(
  date.toLocaleString("ja-JP", {
    timeZone: "America/New_York",
  }),
)
// → 文字列としては表示できるが
//   Date オブジェクトとして
//   NYの時刻を持つことはできない 😱

Temporalではタイムゾーンを持つ日時を表すZonedDateTimeを使用することで任意のタイムゾーンを扱うことができます。

Temporalのタイムゾーンサポート
// 任意のタイムゾーンをネイティブにサポート
const tokyo = Temporal.Now.zonedDateTimeISO("Asia/Tokyo")
const ny = tokyo.withTimeZone("America/New_York")
 
console.log(tokyo.toString())
// → "2026-03-16T15:30:00+09:00[Asia/Tokyo]"
 
console.log(ny.toString())
// → "2026-03-16T01:30:00-05:00[America/New_York]"
// ✅ 型としてタイムゾーン情報を保持

月が0から始まる

new Date(2026, 0, 1)が1月1日を意味するという直感に反する仕様で、off-by-oneエラーが頻発します。

Dateオブジェクトの月が0始まり問題
// 3月を指定したつもりが...
const date = new Date(2026, 3, 1)
 
console.log(date.toDateString())
// → "Wed Apr 01 2026" 😱
// 3 は4月!3月は 2 と書く必要がある
 
const march = new Date(2026, 2, 1)
console.log(march.getMonth())
// → 2  🤔 3月なのに2

Temporalでは直感的に記述できます。

Temporalの月指定
// 素直に3月と書ける
const date = Temporal.PlainDate.from({
  year: 2026,
  month: 3,
  day: 1,
})
 
console.log(date.toString())
// → "2026-03-01" ✅
 
console.log(date.month)
// → 3 ✅ 3月は3

月のオーバーフロー

存在しない月日を指定すると、繰り越されてしまいます。
エラーや警告が出ないため、バグに気づきにくいです。

Dateオブジェクトの月オーバーフロー
const jan31 = new Date(2026, 0, 31)
 
// 2月にしたい
jan31.setMonth(1)
 
console.log(jan31.toDateString())
// → "Tue Mar 03 2026" 😱
// 2/31は存在しない → 3/3に繰り越し
// エラーも警告もなし!

Temporalではこのような問題が発生しません。

Temporalのオーバーフロー制御
const jan31 = Temporal.PlainDate.from("2026-01-31")
 
// overflow: "constrain" で安全に丸める
const feb = jan31.with({ month: 2 }, { overflow: "constrain" })
console.log(feb.toString())
// → "2026-02-28" ✅
 
// overflow: "reject" でエラーにもできる
Temporal.PlainDate.from({ year: 2026, month: 2, day: 31 }, { overflow: "reject" })
// → RangeError ✅ 明示的にエラー

日付/時刻のみの参照ができない

Dateは常に日時の両方を持つため、「誕生日」のように日付だけが必要なケースや、「営業時間」のように時刻だけが必要なケースに適しません。

Dateオブジェクトは常に日時両方を持つ
// 誕生日を表現したいだけなのに...
const birthday = new Date(1990, 4, 15)
 
console.log(birthday.toISOString())
// → "1990-05-14T15:00:00.000Z"
// 不要な時刻情報がついてくる
// しかもタイムゾーンで日付がズレる!
 
// 営業時間 "9:00-17:00" も表現できない
// 必ず日付が必要

Temporalでは任意の年月日や時刻等が取得できます。

Temporalの日付・時刻の個別取得
// 日付だけ
const birthday = Temporal.PlainDate.from("1990-05-15")
console.log(birthday.toString())
// → "1990-05-15" ✅ 日付のみ
 
// 時刻だけ
const openTime = Temporal.PlainTime.from("09:00")
const closeTime = Temporal.PlainTime.from("17:00")
console.log(openTime.toString())
// → "09:00:00" ✅ 時刻のみ
 
// 年月だけ
const billing = Temporal.PlainYearMonth.from("2026-03")
// → "2026-03" ✅

文字列パースが不安定

Date.parse()new Date("...")の挙動がブラウザによって異なり、同じ文字列が異なる結果になることがあります。

Dateオブジェクトの文字列パース問題
// ブラウザによって結果が違う!
const d1 = new Date("2026-03-16")
// → UTC? ローカル? ブラウザ次第
 
const d2 = new Date("03/16/2026")
// → パース可能だが非標準
 
const d3 = new Date("March 16, 2026")
// → 動くかもしれないし動かないかも
 
const d4 = new Date("abc")
// → Invalid Date(例外ではない!)
console.log(d4.getTime()) // → NaN

TemporalではISO 8601/RFC 9557に準拠しています。

Temporalの厳密なパース
// 厳密なISO 8601 / RFC 9557 準拠
const d1 = Temporal.PlainDate.from("2026-03-16")
// → 常に同じ結果 ✅
 
// 不正な文字列は即座にエラー
try {
  Temporal.PlainDate.from("abc")
} catch (e) {
  console.log(e) // → RangeError ✅
}
 
// オブジェクト形式も使える
const d2 = Temporal.PlainDate.from({
  year: 2026,
  month: 3,
  day: 16,
})
// → 明確で安全 ✅

上記で挙げたもの以外にもDateオブジェクトの問題点はあり、多くのプロダクトでは日時操作系のライブラリを使用していると思います。

Temporalの全体像

Temporalは大別すると2つに分けることができます。

  • タイムゾーンなし
    • PlainDateTime
  • タイムゾーンあり
    • ZonedDateTime

下記が図解になります。

PlainDateTimeについて

PlainDateTimeの概要

PlainDateTimeはタイムゾーンを持たない日時を表します。
PlainDateTimeが適しているのは、タイムゾーンが暗黙的に決まっている、あるいは無関係な場面です。
たとえば「3月16日14:30に会議室Aで打ち合わせ」という予定は、参加者全員が同じオフィスにいるなら、タイムゾーンを意識する必要はありません。
壁掛け時計が14:30を指していれば、それが正しい時刻です。

こうした「ローカルな日時」を扱うのがPlainDateTimeの役割です。

PlainDateTimeの作成
// ISO 8601 文字列から
const dt = Temporal.PlainDateTime.from("2026-03-16T14:30:00")
console.log(dt.toString())
// → "2026-03-16T14:30:00"
 
// オブジェクトから
const dt2 = Temporal.PlainDateTime.from({
  year: 2026,
  month: 3,
  day: 16,
  hour: 14,
  minute: 30,
  second: 0,
})
console.log(dt2.toString())
// → "2026-03-16T14:30:00"
 
// PlainDate + PlainTime を合成
const date = Temporal.PlainDate.from("2026-03-16")
const time = Temporal.PlainTime.from("14:30")
const dt3 = date.toPlainDateTime(time)
// → "2026-03-16T14:30:00"

PlainDateTimeの読み取り専用メソッド

PlainDateTimeは日付側と時刻側の両方のメソッドを持ちます。
すべて読み取り専用メソッドです。

PlainDateTimeの読み取り専用メソッド
const dt = Temporal.PlainDateTime.from("2026-03-16T14:30:45.123")
 
// 日付フィールド
dt.year // → 2026
dt.month // → 3(1始まり!)
dt.day // → 16
dt.dayOfWeek // → 1(月曜日。ISO 8601準拠で1=月〜7=日)
dt.dayOfYear // → 75
dt.weekOfYear // → 12
dt.daysInMonth // → 31
dt.daysInYear // → 365
dt.inLeapYear // → false
 
// 時刻フィールド
dt.hour // → 14
dt.minute // → 30
dt.second // → 45
dt.millisecond // → 123
dt.microsecond // → 0
dt.nanosecond // → 0

PlainDateTimeの変更操作 withメソッド

withメソッドを使用することで変更できます。
Temporalはイミュータブルであるため、新しいオブジェクトを返します。

PlainDateTimeのwithメソッド
const dt = Temporal.PlainDateTime.from("2026-03-16T14:30")
 
// 時刻だけ変更
dt.with({ hour: 9, minute: 0 })
// → "2026-03-16T09:00:00"
 
// 日付だけ変更
dt.with({ month: 12, day: 25 })
// → "2026-12-25T14:30:00"
 
// 元のオブジェクトは変わらない
dt.toString() // → "2026-03-16T14:30:00"

withメソッドはオーバーフロー制御もあります。

withメソッドのオーバーフロー制御
// 4月31日は存在しない
Temporal.PlainDateTime.from("2026-03-31T10:00").with(
  { month: 4 },
  { overflow: "constrain" }, // デフォルト
)
// → "2026-04-30T10:00:00"(最も近い有効な日に丸められる)
 
Temporal.PlainDateTime.from("2026-03-31T10:00").with({ month: 4 }, { overflow: "reject" })
// → RangeError(エラーを投げる)

PlainDateTimeの加減算 add/subtractメソッド

add(加算)/subtract(減算)メソッドを使用することで加減算を行うことができます。

PlainDateTimeのadd/subtractメソッド
const dt = Temporal.PlainDateTime.from("2026-03-16T14:30")
 
dt.add({ hours: 3, minutes: 45 })
// → "2026-03-16T18:15:00"
 
dt.add({ days: 1, hours: 12 })
// → "2026-03-18T02:30:00"(日をまたぐ)
 
dt.subtract({ months: 2 })
// → "2026-01-16T14:30:00"

ここで重要なのは、PlainDateTimeにはタイムゾーンがないため、DST(夏時間)の影響を受けないことです。
「14:30の2時間後は常に16:30」になります。
実際のタイムゾーンではDSTの切り替えで1時間消えたり増えたりしますが、PlainDateTimeならそうした複雑さを考慮しなくて良いです。

PlainDateTimeの差分の計算 until/sinceメソッド

until(終了時刻)/since(開始時刻)メソッドを使用することで差分を計算できます。

PlainDateTimeのuntil/sinceメソッド
const start = Temporal.PlainDateTime.from("2026-03-16T09:00")
const end = Temporal.PlainDateTime.from("2026-03-16T17:30")
 
start.until(end)
// → PT8H30M(8時間30分)
 
start.until(end, { largestUnit: "minute" })
// → PT510M(510分)
 
// 日をまたぐ場合
const overnight = Temporal.PlainDateTime.from("2026-03-17T02:00")
start.until(overnight, { largestUnit: "hour" })
// → PT17H(17時間)

PlainDateTimeの繰り上げ/切り捨ての計算 roundメソッド

roundメソッドを使用することで繰り上げ/繰り下げを計算できます。

PlainDateTimeのroundメソッド
const dt = Temporal.PlainDateTime.from("2026-03-16T14:37:22")
 
dt.round("hour")
// → "2026-03-16T15:00:00"(繰り上げ)
 
dt.round({ smallestUnit: "minute", roundingIncrement: 15 })
// → "2026-03-16T14:30:00"(15分刻みで丸め)
 
dt.round({
  smallestUnit: "hour",
  roundingMode: "floor",
})
// → "2026-03-16T14:00:00"(切り捨て)

PlainDateTimeの他の型への変換

PlainDateTimeは他のTemporal型への変換に使用できます。

PlainDateTimeの型変換
const dt = Temporal.PlainDateTime.from("2026-03-16T14:30")
 
// 日付だけ取り出す
dt.toPlainDate() // → Temporal.PlainDate "2026-03-16"
 
// 時刻だけ取り出す
dt.toPlainTime() // → Temporal.PlainTime "14:30:00"
 
// タイムゾーンを付与して ZonedDateTime に変換
dt.toZonedDateTime("Asia/Tokyo")
// → "2026-03-16T14:30:00+09:00[Asia/Tokyo]"
 
dt.toZonedDateTime("America/New_York")
// → "2026-03-16T14:30:00-04:00[America/New_York]"
// ※同じ壁時計の時刻だが、指す「瞬間」は異なる

PlainDateTimeの比較

比較系のメソッドを使用することで比較できます。

PlainDateTimeの比較
const a = Temporal.PlainDateTime.from("2026-03-16T14:30")
const b = Temporal.PlainDateTime.from("2026-03-16T18:00")
 
Temporal.PlainDateTime.compare(a, b) // → -1(a < b)
a.equals(b) // → false
 
// ソートにも使える
const events = [b, a]
events.sort(Temporal.PlainDateTime.compare)
// → [a, b](時系列順)

PlainDateTimeのまとめ

PlainDateTimeはタイムゾーン情報を持たず「壁掛け時計が示す日時」を表現します。
「タイムゾーンを意識しなくてよい場面で使う軽量な型」であり、タイムゾーンが絡む瞬間にtoZonedDateTimeで明示的に文脈を与えるという設計になっています。
ですので、東京とニューヨークのメンバーが参加するオンライン会議のように、「全員が同じ瞬間に集まる」必要がある場合はZonedDateTimeを使います。

ZonedDateTimeについて

ZonedDateTimeの概要

ZonedDateTimeはタイムゾーンを持つ日時を表します。
日付、時刻、タイムゾーン、カレンダーのすべてを持ち、地球上の特定の「瞬間」を一意に表現します。
「全員が同じ瞬間を指している」ことが重要な場面で使います。

ZonedDateTimeの作成
// 文字列から(RFC 9557形式)
const zdt = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
console.log(zdt.toString())
// → "2026-03-16T14:30:00+09:00[Asia/Tokyo]"
 
// オブジェクトから
const zdt2 = Temporal.ZonedDateTime.from({
  year: 2026,
  month: 3,
  day: 16,
  hour: 14,
  minute: 30,
  timeZone: "Asia/Tokyo",
})
console.log(zdt2.toString())
// → "2026-03-16T14:30:00+09:00[Asia/Tokyo]"
 
// 現在時刻から(最もよく使うパターン)
const now = Temporal.Now.zonedDateTimeISO("Asia/Tokyo")

ZonedDateTimeの読み取り専用メソッド

PlainDateTimeのプロパティに加えて、タイムゾーン関連の情報を持ちます。

ZonedDateTimeの読み取り専用メソッド
const zdt = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
 
// 日付・時刻(PlainDateTimeと同じ)
zdt.year // → 2026
zdt.month // → 3
zdt.day // → 16
zdt.hour // → 14
zdt.minute // → 30
 
// タイムゾーン固有
zdt.timeZoneId // → "Asia/Tokyo"
zdt.offset // → "+09:00"
zdt.offsetNanoseconds // → 32400000000000
zdt.epochNanoseconds // → BigInt(Unix epoch からのナノ秒)
zdt.epochMilliseconds // → number(Date互換)
 
// その日のDST対応した時間を取得
zdt.hoursInDay // → 24(DSTの日は23や25になる)

hoursInDayはDSTの切り替え日には23や25を返します。
「今日は何時間あるか」を正確に知ることができます。

タイムゾーンの変換 withTimeZoneメソッド

タイムゾーンを変更するメソッドです。
同じ「瞬間」を別のタイムゾーンの壁時計で見たらどうなるか、を一行で表現できます。

withTimeZoneメソッド
const tokyo = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
 
const ny = tokyo.withTimeZone("America/New_York")
// → "2026-03-16T01:30:00-04:00[America/New_York]"
 
const london = tokyo.withTimeZone("Europe/London")
// → "2026-03-16T05:30:00+00:00[Europe/London]"
 
// 同じ瞬間であることを確認
tokyo.epochNanoseconds === ny.epochNanoseconds // → true
tokyo.epochNanoseconds === london.epochNanoseconds // → true

ZonedDateTimeの変更操作 withメソッド

withメソッドを使用することで変更できます。

ZonedDateTimeのwithメソッド
const zdt = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
 
// 時刻を変更(タイムゾーンは維持)
zdt.with({ hour: 9, minute: 0 })
// → "2026-03-16T09:00:00+09:00[Asia/Tokyo]"
 
// 日付を変更
zdt.with({ month: 12, day: 25 })
// → "2026-12-25T14:30:00+09:00[Asia/Tokyo]"

ZonedDateTimeの加減算 add/subtractメソッド

PlainDateTimeと異なり、ZonedDateTimeの加減算はDSTの影響を正しく反映します。

ZonedDateTimeのadd/subtractメソッド
// アメリカ東部:2026年3月8日に夏時間開始(時計が1時間進む)
const beforeDST = Temporal.ZonedDateTime.from("2026-03-07T12:00:00-05:00[America/New_York]")
 
beforeDST.add({ days: 1 })
// → "2026-03-08T12:00:00-04:00[America/New_York]"
// 壁時計上は同じ12:00だが、オフセットが -05:00 → -04:00 に変化
// 実際に経過した時間は23時間
 
beforeDST.add({ hours: 24 })
// → "2026-03-08T13:00:00-04:00[America/New_York]"
// 24時間後は壁時計上では13:00(DSTで1時間進んだため)

ZonedDateTimeの差分の計算 until/sinceメソッド

until(終了時刻)/since(開始時刻)メソッドを使用することで差分を計算できます。
タイムゾーンによる時差を計算できます。

ZonedDateTimeのuntil/sinceメソッド
const departure = Temporal.ZonedDateTime.from("2026-07-01T10:00:00+09:00[Asia/Tokyo]")
const arrival = Temporal.ZonedDateTime.from("2026-07-01T14:00:00-04:00[America/New_York]")
 
departure.until(arrival, { largestUnit: "hour" })
// → PT17H(実際のフライト時間は17時間)
// タイムゾーンの差を自動的に考慮

その日の始まり startOfDayメソッド

DSTの切り替え日では00:00が存在しない場合もあります。
startOfDayメソッドを使用することで、その日の始まりを正しく取得できます。

startOfDayメソッド
const zdt = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
 
zdt.startOfDay()
// → "2026-03-16T00:00:00+09:00[Asia/Tokyo]"
// DSTの切り替え日は 00:00 が存在しないことがある
// startOfDay はその場合でも正しい「その日の最初の瞬間」を返す

ZonedDateTimeの他の型への変換

ZonedDateTimeは他の型への変換に使用できます。

ZonedDateTimeの型変換
const zdt = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
 
// タイムゾーンを剥がす
zdt.toPlainDateTime() // → "2026-03-16T14:30:00"
zdt.toPlainDate() // → "2026-03-16"
zdt.toPlainTime() // → "14:30:00"
 
// 絶対時刻に変換
zdt.toInstant()
// → Instant(タイムゾーンなし、エポックナノ秒のみ)
 
// Dateオブジェクトとの相互変換
const legacyDate = new Date(zdt.epochMilliseconds)
// Date → ZonedDateTime
const fromLegacy = Temporal.Instant.fromEpochMilliseconds(legacyDate.getTime()).toZonedDateTimeISO("Asia/Tokyo")

ZonedDateTimeの比較

PlainDateTimeとは違い、ZonedDateTimeの場合はタイムゾーンを持つため結果が異なります。

ZonedDateTimeの比較
const a = Temporal.ZonedDateTime.from("2026-03-16T14:30:00+09:00[Asia/Tokyo]")
const b = Temporal.ZonedDateTime.from("2026-03-16T01:30:00-04:00[America/New_York]")
 
// compare は「瞬間」で比較する
Temporal.ZonedDateTime.compare(a, b) // → 0(同じ瞬間)
 
// equals は瞬間 + タイムゾーン + カレンダーすべてが一致する必要がある
a.equals(b) // → false(タイムゾーンが異なる)

ZonedDateTimeのまとめ

ZonedDateTimeTemporalの型体系の中心に位置する型です。
迷ったらまずZonedDateTimeから始め、タイムゾーンが不要だとわかった時点でtoPlainDateTime()などで軽量な型に変換する、というアプローチが安全になります。

Instantについて

Temporal.Instantはタイムゾーンやカレンダーを持たない、純粋な「時間軸上の一点」です。
Unixエポック(1970-01-01T00:00:00Z)からのナノ秒で表現されます。

データベースの作成・更新時刻の記録などに向いています。

また、toString()したときにUTC表記(末尾Z)で出力されるので「UTCが取れる」ように見えますが、実態はこういう構造です。

Instantの内部構造
Instant の中身:
  epochNanoseconds = 1773639000000000000n  ← これだけ
 
toString() すると:
  "2026-03-16T05:30:00Z"  ← UTC表記で「見せている」

Temporal.Nowについて

Temporal.Nowは現在時刻を取得するメソッドです。
Dateオブジェクトのnew Date()/Date.now()に該当します。

5つのメソッドが存在します。

Temporal.Nowのメソッド一覧
zonedDateTimeISO("Asia/Tokyo") // → 2026-03-16T04:16:43+09:00[Asia/Tokyo]
instant() // → 2026-03-15T19:16:43Z
plainDateTimeISO("Asia/Tokyo") // → 2026-03-16T04:16:43
plainDateISO("Asia/Tokyo") // → 2026-03-16
plainTimeISO("Asia/Tokyo") // → 04:16:43

instant()以外はすべてタイムゾーン引数を受け取ります。
省略するとシステムのタイムゾーンが使われます。

Dateオブジェクトの対応は下記になります。

DateオブジェクトとTemporal.Nowの対応
new Date() → Temporal.Now.zonedDateTimeISO()
Date.now() → Temporal.Now.instant().epochMilliseconds

Durationについて

Durationの概要

Temporal.Durationは時間の長さ(期間)を表す型です。

Durationの作成
// ISO 8601 文字列から
const d1 = Temporal.Duration.from("P1Y2M3DT4H5M6S")
// → 1年2ヶ月3日4時間5分6秒
 
// オブジェクトから
const d2 = Temporal.Duration.from({
  years: 1,
  months: 2,
  days: 3,
  hours: 4,
  minutes: 5,
  seconds: 6,
})
 
// 個別のフィールドだけでもOK
const d3 = Temporal.Duration.from({ hours: 2, minutes: 30 })
// → "PT2H30M0S"

ISO 8601P...T...形式は、Pが期間の開始を示し、Tが日付部分と時刻部分の区切りです。
P1Y2M3Dは「1年2ヶ月3日」、T4H5M6Sは「4時間5分6秒」を意味します。

Durationの読み取り専用メソッド

Durationは下記の10個の読み取り専用メソッドを持ちます。
years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds

Durationの読み取り専用メソッド
const d = Temporal.Duration.from({ hours: 2, minutes: 30 })
d.hours // → 2
d.minutes // → 30
d.days // → 0(未指定のフィールドは0)

他の型との連携

Durationの真価は他のTemporal型と組み合わせたときに発揮されます。

Durationと他の型との連携
const today = Temporal.PlainDate.from("2026-03-16")
 
// 加算
today.add({ months: 1, days: 5 })
// → "2026-04-21"
 
// 減算
today.subtract({ weeks: 2 })
// → "2026-03-02"
 
// 2つの日付の差を取得(Durationが返る)
const start = Temporal.PlainDate.from("2026-01-01")
const diff = start.until(today, { largestUnit: "month" })
// → P2M15D(2ヶ月15日)
 
// since は逆方向
today.since(start, { largestUnit: "month" })
// → P2M15D(同じ結果)

until/sinceで期間を取得するとき、largestUnitを指定しないとデフォルトでは最大の単位が型によって異なります。
明示的に指定することで結果の表現をコントロールできます。

largestUnitの指定
const a = Temporal.PlainDate.from("2025-01-01")
const b = Temporal.PlainDate.from("2026-03-16")
 
a.until(b) // → P440D(デフォルトはday単位)
 
a.until(b, { largestUnit: "year" })
// → P1Y2M15D(1年2ヶ月15日)
 
a.until(b, { largestUnit: "month" })
// → P14M15D(14ヶ月15日)

Durationの繰り上げ/切り捨ての計算 roundメソッド

Durationは繰り上げ/切り捨てを計算できます。
ただし、月や年を含む丸めにはカレンダー上の基準日(relativeTo)が必要です。

Durationのroundメソッド
const d = Temporal.Duration.from({ hours: 7, minutes: 43 })
 
d.round({ smallestUnit: "hour", roundingMode: "ceil" })
// → PT8H
 
d.round({ largestUnit: "day", smallestUnit: "hour" })
// → PT8H
 
// 月・年を含む場合は relativeTo が必須
const long = Temporal.Duration.from({ months: 15, days: 20 })
long.round({
  largestUnit: "year",
  relativeTo: Temporal.PlainDate.from("2026-01-01"),
})
// → P1Y4M(relativeToを起点に計算)

比較 compareメソッド

Durationは比較できます。

Durationのcompareメソッド
const d1 = Temporal.Duration.from({ months: 1 })
const d2 = Temporal.Duration.from({ days: 30 })
 
// 「1ヶ月」と「30日」、どちらが長い?
// → カレンダー上の起点によって変わる!
Temporal.Duration.compare(d1, d2, {
  relativeTo: Temporal.PlainDate.from("2026-02-01"),
})
// → -1(2月は28日なので、1ヶ月 < 30日)
 
Temporal.Duration.compare(d1, d2, {
  relativeTo: Temporal.PlainDate.from("2026-03-01"),
})
// → 1(3月は31日なので、1ヶ月 > 30日)

単位変換 totalメソッド

特定の単位に変換した数値が欲しいときはtotalを使います。

Durationのtotalメソッド
const d = Temporal.Duration.from({ hours: 1, minutes: 30 })
 
d.total("minutes") // → 90
d.total("seconds") // → 5400

符号 signメソッド

Durationは正または負のいずれかです。
すべてのフィールドが同じ符号を持つ必要があり、混在はできません。
signプロパティで確認できます。

Durationのsignメソッド
const pos = Temporal.Duration.from({ hours: 2 })
pos.sign // → 1
 
const neg = Temporal.Duration.from({ hours: -2 })
neg.sign // → -1
neg.negated() // → PT2H(正に反転)
neg.abs() // → PT2H(絶対値)

Durationのまとめ

Durationの設計で特に重要なのは、「1ヶ月」のような曖昧な単位をそのまま保持し、実際の計算時にrelativeToで文脈を与えるという考え方です。
Dateオブジェクトでは、こうした曖昧さが暗黙的に処理されてバグの原因になっていましたが、Temporalでは明示的に扱うことで安全性を確保しています。

さいごに

今回はTemporalについて紹介しました。
主要ブラウザの対応が完了したため、今後はTemporalを使用することが増えていくと思います。
以前までDateオブジェクト・ライブラリを使用していた箇所もJavaScriptの標準APIであるTemporalに書き換わっていくことでしょう。

この記事を書いた人

杉田侑祐
杉田侑祐

TOKOSのフロントエンドエンジニア兼UI/UXデザイナー。このブログではフロントエンドメインで投稿しています。HIPHOPとゲームが好きです✌️