← 記事一覧

ライブラリなしで JavaScript のタイムゾーン正確な日付整形

JavaScript 組み込みの Intl.DateTimeFormat は、moment.js や date-fns なしで IANA タイムゾーンの整形を扱います。明示的な timeZone オプション、夏時間に安全な表示、formatToParts、壁時計変換の限界、Temporal やタイムゾーン ライブラリが今も役立つ場面を学びます。

Date になぜタイムゾーンのプロパティがないのか

JavaScript の Date は常に UTC ミリ秒のカウントです。オブジェクト内にタイムゾーンは保存されていません。オプションなしで .toString() や .toLocaleString() を呼ぶと、JavaScript は OS からのランタイムのローカルタイムゾーンを使います。同じコードでも、ニューヨークのサーバーと東京のノート PC では、基盤のタイムスタンプが同一でも異なる出力になります。

Intl.DateTimeFormat で整形する

Intl.DateTimeFormat は、ロケールとタイムゾーンを考慮した整形のための組み込み API です。IANA タイムゾーン識別子をサポートし、夏時間の切り替えを自動的に扱い、現代のブラウザと Node.js で利用できます。鍵は、ランタイムの既定に頼らず timeZone オプションを明示的に渡すことです。

  • new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
  • date.toLocaleString('en-GB', { timeZone: 'Europe/London', hour12: false })
  • date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
  • new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', hour12: false }).format(date)

formatToParts で個々の要素を取り出す

formatToParts() を使って、日付の各要素を {type, value} オブジェクトとして取得し、独自の日付文字列を組み立てます。これは、ローカライズされた日付文字列を分割するより優れています。句読点・順序・文字体系がロケールによって異なるからです。

  • const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date)
  • parts.find(p => p.type === 'year').value → '2023'
  • parts.find(p => p.type === 'month').value → '11'
  • Object.fromEntries(parts.map(p => [p.type, p.value])) → { year, month, day, hour, minute, second }

壁時計の時刻を UTC に変換する(難しい方向)

ある所定のタイムゾーンの壁時計の時刻から UTC へ向かうのは、従来の Date API では難しいです。ある瞬間を America/New_York で整形するのは簡単ですが、America/New_York における 2026-03-08 02:30 が表す瞬間を構築するのは簡単ではありません。そのローカル時刻は夏時間の切り替えで飛ばされたり繰り返されたりするからです。

  • Temporal は 2026 年に Stage 4 に到達したが、ブラウザのネイティブ対応はまだ普遍的ではない
  • 今日すべてのブラウザで本番利用するには、Temporal ポリフィルまたは date-fns-tz の toDate() が依然として実用的な選択肢
  • 手動の UTC オフセット算術は避ける — 夏時間の切り替えは年によって異なるローカル時刻に起こる
  • このサイトの zonedToEpochMs() は 1 回反復のオフセット補正を使う — src/timeUtils.ts を参照

正しいタイムゾーン識別子を選ぶ

America/New_York・Europe/London・Asia/Tokyo・UTC のような IANA タイムゾーン名を使ってください。ユーザー向けの時刻に UTC-5 のような固定オフセットを使うのは避けてください。オフセットは夏時間で変わるからです。America/New_York は 1 月に UTC-5、7 月に UTC-4 になりえます。IANA 名により、プラットフォームは選択された日付に対して正しい歴史的規則を適用できます。

  • 良い:America/Los_Angeles — 過去と将来の夏時間規則を含む
  • 良い:Europe/Berlin — 中央ヨーロッパ夏時間を自動的に扱う
  • 良い:Asia/Shanghai — 夏時間なしの安定した UTC+8 表示
  • ログ・API の保存・データベースのタイムスタンプ・リージョン間比較には UTC を使う
  • プロダクト UI での最終表示にはユーザーの IANA タイムゾーンを使う

タイムゾーン整形 FAQ

Two daylight-saving transitions a year break the assumption that "wall-clock time + timezone = a unique moment". Both are encoded in the IANA timezone database (still often called the Olson database) — the authoritative source of timezone rules used by every modern OS, browser, and language runtime.

  • DST gap: 02:00 to 02:59 local time does not exist on the spring-forward day
  • DST overlap: 01:00 to 01:59 local time occurs twice on the fall-back day
  • Ambiguous local time: in the overlap window you must pick "earlier" or "later" explicitly
  • Python uses a fold flag; JS Temporal uses a disambiguation option; java.time defaults to the earlier offset
  • Olson database / IANA tzdata / zoneinfo are three names for the same dataset, updated several times a year
  • Each IANA name (America/Los_Angeles, Europe/Berlin) maps to a list of (transition, offset, abbreviation) tuples
  • Stale tzdata on a device produces silently-wrong local times after any DST or zone change
  • Server best practice: do all date arithmetic in UTC, convert to local only at display

FAQ

Can JavaScript format a date in another timezone without a library?
Yes. Use Intl.DateTimeFormat or toLocaleString with a timeZone option, such as America/New_York or Asia/Tokyo.
Does Intl.DateTimeFormat handle daylight saving time?
Yes. When you use an IANA timezone identifier, the runtime applies the correct offset for that timezone and date.
Should I store timezone offsets or timezone names?
Store UTC for event instants. If you need the user's local context, store an IANA timezone name such as America/New_York, not only a numeric offset.
How do I get the user's timezone in JavaScript?
Use Intl.DateTimeFormat().resolvedOptions().timeZone, which returns an IANA name like America/New_York. Store that name, not a numeric offset, when you need to reconstruct the user's local time later.
What is the DST gap (spring-forward)?
The DST gap is the wall-clock hour that is skipped when daylight saving time begins. In most US zones, when the clock would tick from 02:00 to 02:59 it instead jumps to 03:00. Local times in that window do not exist on the transition day. Unix timestamps themselves never skip; only the wall-clock interpretation does.
What is the DST overlap (fall-back)?
The DST overlap is the wall-clock hour that occurs twice when daylight saving time ends. In most US zones, the clock ticks from 01:59 back to 01:00, so every minute from 01:00 to 01:59 happens twice. A wall-clock time in that window maps to two Unix timestamps; converters and schedulers must specify "earlier" or "later".