こんにちは、Ryohei(@ityryohei)です!

JavaScriptにおける日付と時刻の処理は、常に開発者にとって最もトリッキーな課題の一つでした。標準のDateオブジェクトは、タイムゾーンの扱いが難しく、複雑な計算や国際化(i18n)が必要な場面で多くのバグの原因となってきました。

この課題を解決するため、ブラウザ標準の強力な機能としてIntl.DateTimeFormatが提供されており、さらに未来の標準としてTemporal APIの導入が進んでいます。

本記事では、既存のDateオブジェクトの限界を具体的なバグ例とともに理解しつつ、国際化された正確な日付表示を実現するIntl.DateTimeFormatの応用的な使い方、そして今後の開発で主流となるTemporal APIの詳細なクラス構造と実務的な計算方法までを徹底的に解説します。

サイトに日付を表示したいんだけど、ユーザーの国や言語に合わせて曜日や時刻を正確に出すにはどうすればいいんだろう、しかもDateオブジェクトでの計算ってどうしてこんなに扱いづらいんだろう?

上記の疑問にお答えします。

では、解説していきます。

Dateオブジェクトの根本的な問題点とバグ例

従来のDateオブジェクトは、内部的にUTC(協定世界時)のミリ秒として時間をカウントしていますが、その処理と表示に一貫性がないため、深刻なバグを引き起こします。

1. 深刻なバグの温床となる「ミュータブル性」

Dateオブジェクトはミュータブル(可変)です。つまり、一度作成した後、値を変更するメソッド(setHours()setDate()など)を使うと、元のインスタンスが直接変更されます

JavaScript

const originalDate = new Date(2025, 10, 10); // 2025年11月10日
const newDate = originalDate; // 参照がコピーされる(同じオブジェクトを指す)

// newDateを変更
newDate.setDate(15); 

// 予期せぬ変更:originalDate も変更されている!
console.log(originalDate.getDate()); // -> 15 (10のままであってほしい)

複雑な日付計算の連鎖で、どこかの処理が意図せずoriginalDateを変更してしまい、デバッグが極めて困難になる副作用を生み出します。

2. 厄介なタイムゾーンとサマータイム(DST)

Dateのメソッドは、ローカルタイムゾーンに基づいて動作するため、サマータイム(DST)が絡むと予期せぬ結果になります。

JavaScript

// 米国/ロサンゼルスの2025年3月のDST移行日(午前2時が午前3時にスキップする日)を想定

// 3月9日 午前1時30分
const bugDate = new Date('2025-03-09T01:30:00-08:00'); 

// 3月9日 午前2時30分に設定しようとする
bugDate.setHours(2, 30); 

// 実際には DST移行により午前3時30分に設定される!
// 時刻が「スキップ」されたため、指定した2:30が存在しない。
console.log(bugDate.getHours()); // -> 3 

このように、Dateオブジェクトは、あくまで時間軸上の「特定の瞬間」を指すには使えますが、「カレンダー上の日付時刻」や「正確な時間間隔」の操作には根本的に不向きです。

突き詰めると、Dateの抱える問題は「複雑な計算」と「煩雑な表示」の二点に集約されます。

複雑な計算とタイムゾーンの問題は、未来の標準であるTemporal APIがその構造から解決します。一方、Temporalが導き出した正確なデータを、ユーザーの言語や地域に合わせて適切に整形し、表示する(国際化する)役割は、今すぐ利用できるIntl APIが担います。この二つの強力なAPIを適切に使い分けることこそが、バグのない完璧な日付処理を実現する鍵となるのです。

まずは、即座に表示の問題を解決できるIntl APIの活用法から見ていきましょう。

ステップ1:Intl APIの応用:表示だけでなく計算もサポート

Intl.DateTimeFormatによる基本的な日付・時刻のローカライズに加え、Intl APIには、ユーザー体験を劇的に向上させるための応用機能が多数含まれています。

1. 相対的な時刻の表示(Intl.RelativeTimeFormat)

ニュース記事の投稿日時やコメントの時間など、現在時刻を基準とした相対的な時間を国際化対応して表示します。

JavaScript

// 3日前の表示
const rtf = new Intl.RelativeTimeFormat('ja', { numeric: 'auto' });

console.log(rtf.format(-3, 'day'));    // -> 3日前
console.log(rtf.format(5, 'hour'));    // -> 5時間後

// 英語ロケールの場合
const rtfEn = new Intl.RelativeTimeFormat('en', { style: 'long' });

console.log(rtfEn.format(-1, 'day'));  // -> 1 day ago
console.log(rtfEn.format(2, 'week'));  // -> in 2 weeks

numeric: 'auto'オプションを使うと、「昨日」「明日」といった自然な表現に自動で変換されます。

2. リストの国際化(Intl.ListFormat

複数のアイテムを列挙する際の区切り文字や接続詞(例: 「AとB」「A、B、およびC」)を、言語のルールに合わせて適切に処理します。

JavaScript

const listJa = new Intl.ListFormat('ja', { style: 'long', type: 'conjunction' });
const listEn = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });

const items = ['りんご', 'バナナ', 'みかん'];

console.log(listJa.format(items)); // -> りんご、バナナ、みかん
console.log(listEn.format(items)); // -> apple, banana, and orange

この機能は、特に動的なリスト表示や、多言語対応が必要なフォームの検証メッセージなどで役立ちます。

ステップ2:Temporal APIのクラス構造と実務応用

Temporal APIは、Dateオブジェクトの課題を解決するため、日付・時刻の概念を以下の3つの主要なクラスに厳密に分離しています。

1. 時間軸上の絶対的な時点:Temporal.Instant

Instantは、タイムゾーン情報を持たない、時間軸上の単なる一点(UTCエポックからのナノ秒数)を表します。これは、ログ記録やデータ転送など、タイムゾーンの影響を受けずに時刻を比較・記録したい場合に最適です。

  • 用途:データベースへの記録、ネットワーク間のデータ交換。

2. カレンダー上の日時:Temporal.PlainDateTime

PlainDateTimeは、タイムゾーン情報を持たない、カレンダー上の日時(例: 2025年11月10日 15:00)を表します。これは、特定の場所がどこであるかを気にせず、「会議は明日の10時」といったローカルな日時を表現するのに使います。

  • 用途:カレンダーの予定入力、アラーム設定、誕生日の記録。

3. 特定の場所での日時:Temporal.ZonedDateTime

ZonedDateTimeは、PlainDateTimeに特定のタイムゾーン情報が付与されたオブジェクトです。これにより、「2025年11月10日 15:00 日本標準時 (JST)」のように、いつ、どこで、何時か、を正確に表現できます。サマータイムの自動調整もこのクラスが担当します。

  • 用途:ユーザーインターフェースでの表示、タイムゾーンをまたぐイベントの調整。

4. 正確な期間の計算:Temporal.Duration

Durationクラスは、特定の時間間隔(例: 3時間、15分、2日)を表し、日付時刻オブジェクトとの間で正確な加算・減算を可能にします。

JavaScript

const duration = Temporal.Duration.from({ days: 1, hours: 2 });

const todayJST = Temporal.ZonedDateTime.from({
    timeZone: 'Asia/Tokyo',
    year: 2025, month: 10, day: 27, hour: 10
});

// ZonedDateTimeにDurationを加算 (不変性によりtodayJSTは変化しない)
const tomorrowJST = todayJST.add(duration);

console.log(tomorrowJST.toString());
// -> 2025-10-28T12:00:00[Asia/Tokyo] (1日と2時間後)

このDurationを使うことで、Dateで必須だったミリ秒単位での手動計算や、うるう秒・サマータイムを考慮する必要がなくなります。

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal

最後に

JavaScriptの日付時刻処理は、新標準Temporal APIの登場により、根本的に変わろうとしています。

  • 即戦力としてのIntl
    今すぐ多言語対応を実現するには、Intl.DateTimeFormatIntl.RelativeTimeFormatIntl.ListFormatを積極的に使いましょう。
  • 未来の標準Temporal
    Dateオブジェクトの最大の弱点であったミュータブル性タイムゾーンの曖昧さを、不変なクラス構造明確な概念分離で解決します。ZonedDateTimePlainDateTimeDurationを使いこなすことが、これからのJavaScript開発者の必須スキルとなるでしょう。

これらの高度なAPIをマスターすることで、あなたは日付時刻処理に関するバグを根絶し、国際的なユーザーにも対応した高品質なWebアプリケーションを自信を持って構築できるようになります。

以上、JavaScriptの新標準Temporal APIとIntlで正確な日付時刻をマスター!Dateの悩みを完全に解決についてのご紹介でした!

この記事を書いた人

Ryohei

Webエンジニア / ブロガー

福岡のWeb制作会社に務めるWebエンジニアです。エンジニア歴は10年程で、好きな言語はPHPとJavaScriptです。本サイトは私がインプットしたWebに関する知識を整理し、共有することを目的に2015年から運営しています。Webに関するご相談があれば気軽にお問い合わせください。