Temporal API入門 — JavaScriptの日付操作が劇的に変わる
JavaScriptのDateオブジェクトは、開発者にとって長年の悩みの種でした。タイムゾーンの扱いにくさ、イミュータブルではない設計、直感的でないAPIなど、多くの問題を抱えています。Temporal APIは、これらの問題を根本から解決する新しい日付・時刻APIです。現在TC39 Stage 3で、主要ブラウザでの実装が進んでいます。
Date()の問題点
まず、従来のDateオブジェクトの問題点を確認しましょう。
// 問題1: ミュータブル
const date = new Date('2026-02-05');
date.setMonth(11); // dateが変更されてしまう
// 問題2: タイムゾーンの扱いが難しい
const date1 = new Date('2026-02-05T10:00:00');
console.log(date1.toISOString()); // ローカルタイムゾーンに依存
// 問題3: 月が0始まり
const date2 = new Date(2026, 1, 5); // 2月ではなく2月5日
// 問題4: APIが直感的でない
const diff = date2 - date1; // ミリ秒で返される
Temporal APIの基本概念
Temporal APIは、用途に応じて複数のオブジェクトを提供します。
- PlainDate - 日付のみ(2026-02-05)
- PlainTime - 時刻のみ(14:30:00)
- PlainDateTime - 日付と時刻(タイムゾーンなし)
- ZonedDateTime - タイムゾーン付き日時
- Instant - UTCタイムスタンプ
- Duration - 期間
- PlainYearMonth - 年月のみ
- PlainMonthDay - 月日のみ
PlainDateの使い方
日付のみを扱う場合はPlainDateを使います。
import { Temporal } from '@js-temporal/polyfill';
// 日付の作成
const today = Temporal.Now.plainDateISO();
const birthday = Temporal.PlainDate.from('2000-05-15');
const custom = new Temporal.PlainDate(2026, 2, 5); // 月は1始まり!
// 日付の操作(イミュータブル)
const tomorrow = today.add({ days: 1 });
const nextWeek = today.add({ weeks: 1 });
const nextMonth = today.add({ months: 1 });
// 差分の計算
const age = birthday.until(today, { largestUnit: 'years' });
console.log(`${age.years}歳`);
// 比較
if (birthday.equals(custom)) {
console.log('同じ日付です');
}
if (today.compare(tomorrow) < 0) {
console.log('todayの方が過去');
}
// フォーマット
console.log(today.toString()); // "2026-02-05"
console.log(today.toLocaleString('ja-JP', {
year: 'numeric',
month: 'long',
day: 'numeric',
})); // "2026年2月5日"
PlainTimeの使い方
時刻のみを扱う場合はPlainTimeを使います。
// 時刻の作成
const now = Temporal.Now.plainTimeISO();
const morning = Temporal.PlainTime.from('09:00:00');
const custom = new Temporal.PlainTime(14, 30, 0); // 14:30:00
// 時刻の操作
const inOneHour = now.add({ hours: 1 });
const inThirtyMinutes = now.add({ minutes: 30 });
// 差分の計算
const duration = morning.until(custom);
console.log(`${duration.hours}時間${duration.minutes}分`);
// 比較
if (morning.compare(custom) < 0) {
console.log('朝の方が早い');
}
// フォーマット
console.log(morning.toString()); // "09:00:00"
ZonedDateTimeの使い方
タイムゾーンを考慮した日時を扱う場合はZonedDateTimeを使います。
// 現在の日時(タイムゾーン付き)
const now = Temporal.Now.zonedDateTimeISO();
// 特定のタイムゾーンで日時を作成
const tokyo = Temporal.ZonedDateTime.from({
timeZone: 'Asia/Tokyo',
year: 2026,
month: 2,
day: 5,
hour: 14,
minute: 30,
});
const newYork = Temporal.ZonedDateTime.from({
timeZone: 'America/New_York',
year: 2026,
month: 2,
day: 5,
hour: 14,
minute: 30,
});
// タイムゾーンの変換
const tokyoToNY = tokyo.withTimeZone('America/New_York');
console.log(tokyoToNY.toString());
// 日時の操作
const tomorrow = tokyo.add({ days: 1 });
const nextHour = tokyo.add({ hours: 1 });
// 夏時間を考慮した計算
const summer = Temporal.ZonedDateTime.from({
timeZone: 'America/New_York',
year: 2026,
month: 7,
day: 1,
hour: 12,
});
// オフセットの取得
console.log(summer.offset); // "-04:00" (夏時間)
console.log(tokyo.offset); // "+09:00"
Durationの使い方
期間を表現する場合はDurationを使います。
// 期間の作成
const oneDay = Temporal.Duration.from({ days: 1 });
const twoHours = Temporal.Duration.from({ hours: 2, minutes: 30 });
// 期間の計算
const date1 = Temporal.PlainDate.from('2026-02-05');
const date2 = Temporal.PlainDate.from('2026-03-15');
const duration = date1.until(date2);
console.log(`${duration.days}日間`);
// 期間の操作
const doubled = duration.multiply(2);
const half = duration.divide(2);
// 期間の比較
const longer = Temporal.Duration.from({ days: 10 });
const shorter = Temporal.Duration.from({ days: 5 });
console.log(longer.compare(shorter)); // 1
// 人間が読める形式
console.log(duration.toString()); // "P38D"
console.log(twoHours.toString()); // "PT2H30M"
実用例
1. 営業日の計算
function addBusinessDays(startDate, daysToAdd) {
let current = Temporal.PlainDate.from(startDate);
let added = 0;
while (added < daysToAdd) {
current = current.add({ days: 1 });
const dayOfWeek = current.dayOfWeek;
// 土曜日(6)と日曜日(7)をスキップ
if (dayOfWeek !== 6 && dayOfWeek !== 7) {
added++;
}
}
return current;
}
const today = Temporal.Now.plainDateISO();
const deadline = addBusinessDays(today, 5);
console.log(`5営業日後: ${deadline.toString()}`);
2. タイムゾーンを跨ぐ会議時間の調整
function convertMeetingTime(dateTime, fromZone, toZone) {
const meeting = Temporal.ZonedDateTime.from({
timeZone: fromZone,
year: dateTime.year,
month: dateTime.month,
day: dateTime.day,
hour: dateTime.hour,
minute: dateTime.minute,
});
return meeting.withTimeZone(toZone);
}
// 東京時間で14:00の会議をニューヨーク時間に変換
const tokyoMeeting = {
year: 2026,
month: 2,
day: 5,
hour: 14,
minute: 0,
};
const nyTime = convertMeetingTime(
tokyoMeeting,
'Asia/Tokyo',
'America/New_York'
);
console.log(`東京: ${tokyoMeeting.hour}:00`);
console.log(`ニューヨーク: ${nyTime.hour}:${nyTime.minute.toString().padStart(2, '0')}`);
3. 相対的な日時の表示
function timeAgo(pastDate) {
const now = Temporal.Now.plainDateISO();
const past = Temporal.PlainDate.from(pastDate);
const duration = past.until(now, { largestUnit: 'days' });
if (duration.days === 0) return '今日';
if (duration.days === 1) return '昨日';
if (duration.days < 7) return `${duration.days}日前`;
if (duration.days < 30) return `${Math.floor(duration.days / 7)}週間前`;
if (duration.days < 365) return `${Math.floor(duration.days / 30)}ヶ月前`;
return `${Math.floor(duration.days / 365)}年前`;
}
console.log(timeAgo('2026-02-04')); // "昨日"
console.log(timeAgo('2026-01-29')); // "1週間前"
console.log(timeAgo('2025-12-05')); // "2ヶ月前"
4. 定期予定の計算
function getRecurringDates(startDate, endDate, interval) {
const dates = [];
let current = Temporal.PlainDate.from(startDate);
const end = Temporal.PlainDate.from(endDate);
while (current.compare(end) <= 0) {
dates.push(current);
current = current.add(interval);
}
return dates;
}
// 毎週月曜日を取得
const mondays = getRecurringDates(
'2026-02-02', // 月曜日
'2026-03-31',
{ weeks: 1 }
);
console.log('2月〜3月の月曜日:');
mondays.forEach(date => console.log(date.toString()));
Date()との比較
// Date()の場合
const date1 = new Date('2026-02-05T14:30:00');
const date2 = new Date(date1);
date2.setDate(date2.getDate() + 1);
console.log(date1.toISOString()); // 変更されてしまう可能性
// Temporal APIの場合(イミュータブル)
const temporal1 = Temporal.PlainDateTime.from('2026-02-05T14:30:00');
const temporal2 = temporal1.add({ days: 1 });
console.log(temporal1.toString()); // "2026-02-05T14:30:00"
console.log(temporal2.toString()); // "2026-02-06T14:30:00"
ブラウザサポートと使い方
現在Temporal APIは実装中です。本番で使用するにはpolyfillが必要です。
npm install @js-temporal/polyfill
import { Temporal } from '@js-temporal/polyfill';
const now = Temporal.Now.plainDateISO();
console.log(now.toString());
まとめ
Temporal APIは、JavaScriptの日付操作を根本から改善します。
主な利点:
- イミュータブルな設計
- 明確なタイムゾーン処理
- 直感的なAPI
- 型安全(TypeScript完全対応)
- 用途別の専用オブジェクト
現時点ではpolyfillが必要ですが、将来的にはネイティブサポートされる予定です。新しいプロジェクトでは、積極的にTemporal APIの導入を検討しましょう。