Back

ブラウザで人間が読みやすい時刻を表示する

ブラウザで人間が読みやすい時刻を表示する

サーバーはタイムスタンプをUTCで保存します。ユーザーは数十のタイムゾーンに住んでいます。この2つの現実—生のISO文字列と「2時間前」—の間のギャップが、インターフェースがネイティブに感じられるか、それとも異質に感じられるかを決定します。

最近のブラウザは、サードパーティライブラリなしでJavaScriptで人間が読みやすい時刻を処理できるようになりました。この記事では、使用すべきネイティブAPIについて説明します:Intl.DateTimeFormatIntl.RelativeTimeFormatIntl.DurationFormat、そしてタイムゾーンを考慮したロジックのためのTemporal APIです。

重要なポイント

  • Intl.DateTimeFormatをロケールに対応した絶対タイムスタンプに使用し、IANAタイムゾーン識別子を直接渡すことで手動のオフセット計算を回避します。
  • Intl.RelativeTimeFormatを小さなヘルパー関数と組み合わせて使用し、「昨日」や「3時間前」のような自然なフレーズを生成します。
  • Intl.DurationFormatを経過時間やカウントダウンに使用しますが、ベースラインの利用可能性が2025年にようやく到達したため、ブラウザのサポート状況を確認してください。
  • Temporal APIは、エラーが発生しやすいDateオブジェクトを明示的で不変な型に置き換えます—新しいプロジェクトではこれを採用し、既存のコードベースではIntlフォーマッターに依存してください。

Intl.DateTimeFormatによる絶対タイムスタンプ

ユーザーが正確な日付と時刻を必要とする場合、Intl.DateTimeFormatは自動的にローカライゼーションを処理します。ユーザーのロケールを尊重し、地域の慣習に従って日付をフォーマットします。

const date = new Date('2026-03-15T14:30:00Z')

const formatter = new Intl.DateTimeFormat('en-US', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'America/New_York'
})

console.log(formatter.format(date)) // "Mar 15, 2026, 10:30 AM"

このAPIはIANAタイムゾーン識別子を直接受け入れるため、手動でのオフセット計算が不要になります。ユーザーのローカルフォーマットの場合は、タイムゾーンを自動的に検出します:

const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone

これは"Europe/London""Asia/Tokyo"のような値を返し、フォーマッターに直接渡すことができます。

Intl.RelativeTimeFormatによる相対時刻

ソーシャルフィード、通知、アクティビティログは相対タイムスタンプの恩恵を受けます。Intl.RelativeTimeFormatは、適切なローカライゼーションで「3日前」や「2時間後」のようなフレーズを生成します。

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })

rtf.format(-1, 'day')    // "yesterday"
rtf.format(-3, 'hour')   // "3 hours ago"
rtf.format(2, 'week')    // "in 2 weeks"

numeric: 'auto'オプションは、適切な場合に文字通りの数字(「1日前」)の代わりに自然言語(「昨日」)を生成します。

差分は自分で計算する必要があります—このAPIは日付ではなく値をフォーマットします。シンプルなヘルパーが機能します:

function getRelativeTime(date) {
  const now = Date.now()
  const diffInSeconds = Math.round((date - now) / 1000)
  const units = [
    { unit: 'year', seconds: 31536000 },
    { unit: 'month', seconds: 2592000 },
    { unit: 'day', seconds: 86400 },
    { unit: 'hour', seconds: 3600 },
    { unit: 'minute', seconds: 60 },
    { unit: 'second', seconds: 1 }
  ]

  for (const { unit, seconds } of units) {
    if (Math.abs(diffInSeconds) >= seconds) {
      const value = Math.round(diffInSeconds / seconds)
      return new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
        .format(value, unit)
    }
  }
  return 'just now'
}

上記の月と年の値は固定秒数の近似値を使用しています。月の長さの変動やうるう年をまたいだカレンダー精度の差分については、固定秒数定数の代わりにTemporalまたは専用の日付ライブラリを使用してください。

Math.roundは時折、値を次の単位に押し上げることがあります(例えば、89秒を60で割ると1に丸められ、「89秒前」ではなく「1分前」が生成されます)。より厳密な境界が必要な場合は、代わりにMath.truncを使用してください。

Intl.DurationFormatによる期間のフォーマット

経過時間、カウントダウン、またはビデオの長さについては、Intl.DurationFormatがロケール間で一貫した出力を提供します:

const duration = { hours: 2, minutes: 45, seconds: 30 }

const df = new Intl.DurationFormat('en', { style: 'long' })
console.log(df.format(duration)) // "2 hours, 45 minutes, 30 seconds"

const dfShort = new Intl.DurationFormat('en', { style: 'digital' })
console.log(dfShort.format(duration)) // "2:45:30"

このAPIは2025年にクロスエンジンのベースラインサポートに到達し、最新のブラウザで利用可能ですが、特定のサポートマトリックスとの互換性を確認する必要があります。

より安全な日付処理のためのTemporal API

Temporal APIは、JavaScriptのDateオブジェクトの長年の問題—可変性、タイムゾーンの混乱、パース時の不整合—に対処します。Temporalは2026年時点で最新のChromiumとFirefoxリリースでサポートされていますが、すべてのブラウザで普遍的にサポートされているわけではないため、機能検出は依然として不可欠です。

if (typeof Temporal !== 'undefined') {
  const now = Temporal.Now.zonedDateTimeISO('America/Los_Angeles')
  console.log(now.toString())
}

Temporalは異なる概念に対して明確な型を提供します:Temporal.PlainDateはカレンダー日付用、Temporal.PlainTimeは壁時計時刻用、Temporal.ZonedDateTimeはタイムゾーンを考慮した瞬間用です。この明示性は、Dateベースのコードに蔓延するバグを防ぎます。

新しいプロジェクトでは、Temporalが最もクリーンな基盤を提供します。既存のコードベースでは、IntlフォーマッターはレガシーのDateオブジェクトとシームレスに動作します。

実用的な推奨事項

タイムスタンプをUTC ISO 8601文字列として保存します。Intl APIを使用してクライアント側でフォーマットします。非ISO日付文字列のパースは避けてください—ブラウザ間で一貫性のない動作をします。

複数のタイムスタンプをフォーマットする場合は、フォーマッターインスタンスを再利用してください。フォーマッターを一度作成してformat()を繰り返し呼び出す方が、呼び出しごとに再作成するよりも大幅に高速です。

アクセシビリティのためにセマンティックHTMLを使用します:

<time datetime="2026-03-15T14:30:00Z">March 15, 2026</time>

まとめ

ネイティブプラットフォームは、かつてMoment.jsや類似のライブラリを必要としていたものを処理できるようになりました。Intl.DateTimeFormatは絶対タイムスタンプをカバーし、Intl.RelativeTimeFormatは相対的なフレーズをカバーし、Intl.DurationFormatは経過時間をカバーし、Temporal APIはタイムゾーンを考慮した日付ロジックの健全な基盤を提供します。ブラウザで人間が読みやすい時刻を表示するには、これらの組み込みツールがあれば十分です。

よくある質問

いいえ。このAPIは数値と単位をローカライズされた文字列にフォーマットするだけです。2つの日付間の差分を自分で計算し、formatメソッドに渡す前に適切な単位を選択する必要があります。この記事で示されているヘルパー関数は、その計算の一般的なパターンです。

Temporalは2026年時点で最新のChromiumとFirefoxリリースで利用可能ですが、すべてのブラウザで普遍的にサポートされているわけではありません。Temporalメソッドを呼び出す前に常に機能検出を使用し、広範な互換性が必要な場合はポリフィルを検討してください。フォーマットだけの場合、Intl APIはすでに広くサポートされており、レガシーのDateオブジェクトで問題なく動作します。

Intlフォーマッターの構築には、ロケールデータ、タイムゾーンルール、フォーマットオプションの解決が含まれ、測定可能なコストがかかります。1つのインスタンスを作成してそのformatメソッドを繰り返し呼び出す方が、呼び出しごとにフォーマッターを再構築するよりも大幅に高速です。特に長いタイムスタンプのリストをレンダリングする場合は顕著です。

デフォルトではい。timeZoneオプションを省略すると、フォーマッターはランタイム環境のタイムゾーンを使用します。ブラウザではユーザーのシステム設定と一致します。この値はIntl.DateTimeFormat().resolvedOptions().timeZoneを介して明示的に取得でき、他のフォーマッターに渡したり、サーバーに送信したりできます。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay