もう迷わない!JavaScript日付とタイムゾーンの扱い方 #2 - new Date() の挙動を正しく理解する

はじめに

こんにちは!

今回は new Date() の挙動についてまとめていきます。 JavaScriptは日付の扱いに癖があると言われますが、その原因の一端はこのnew Date()にあると思います。

new Date()の挙動をしっかり理解して、うまく付き合っていきましょう!

日付オブジェクトの生成 (new Date)

  • 日付オブジェクトを生成するには new Date() を用いる
  • new Date は内部的には UTC しか扱えない
    • 1970-01-01T00:00:00Z(UNIX時間)からの経過ミリ秒を保持する
    • タイムゾーンは保持しない(内部的にタイムゾーンの概念はない)
    • タイムゾーンを扱えるように見える理由は次の通り
      • 引数として +09:00 のような ISO 8601 形式の文字列を解釈して、UTC時間に換算できる
      • 出力時( toString() など)に実行環境のローカルタイムゾーンで表示できる

new Date()の挙動

パターン①:引数なし(現在時刻を取得)

  • 現在のローカルタイムゾーンを基準に生成 → 内部的にはUTC のミリ秒値として保持
const date = new Date(); 

console.log(date.toISOString());
// 実行環境が JST (UTC+09:00) で2025-05-18 00:00:00 に実行した場合
// -> 2025-05-17T15:00:00.000Z
// 実行環境が NY (UTC-04:00) で2025-05-18 00:00:00 に実行した場合
// -> 2025-05-18T04:00:00.000Z

パターン②:YYYY-MM-DD(日付だけ)

  • UTC の 2025-05-18T00:00:00Z として解釈され、UTCのミリ秒値として保持(例外的に UTC 扱い
const date = new Date("2025-05-18");

console.log(date.toISOString()); 
// 実行環境に関わらず、2025-05-18T00:00:00.000Z

パターン③:YYYY-MM-DDTHH:mm:ss(日時)

  • ローカルタイムとして解釈し、UTCのミリ秒値に換算して保持
const date = new Date("2025-05-18T00:00:00"); 

console.log(date.toISOString()); 
// 実行環境が JST (UTC+09:00) の場合 → 2025-05-17T15:00:00.000Z
// 実行環境が NY (UTC-04:00) の場合 → 2025-05-18T04:00:00.000Z

パターン④:YYYY-MM-DDTHH:mm:ss+09:00タイムゾーン付き)

  • +09:00 という 指定タイムゾーンのローカルタイムとして解釈し、UTCのミリ秒値 に換算して内部に保持
const date = new Date("2025-05-18T00:00:00+09:00");

console.log(date.toISOString()); 
// 実行環境に関わらず、2025-05-17T15:00:00.000Z

なぜ toISOString() を使って比較するのか?

  • toISOString()UTC基準のISO 8601形式 で出力する仕様
  • console.log()は実装によって挙動が異なる
    • ChromeなどだとtoString()を呼び出しているのと一緒(のはず)
    • toString()はローカルタイムゾーンが関わってくるので、より比較しやすいtoISOString()で比較する
  • toISOString()は必ずUTC基準の時刻で出力=Z(Zulu = UTC)が付いて表示される
    • +9:00のような表記になることもない
  • そのため、比較に有用なので今後のシリーズではtoISOString()で基本的に比較する

YYYY-MM-DDUTC として扱われる理由(例外的仕様)

  • これは他の引数パターンと整合性がとれておらず、開発者が混乱する原因
  • 本来のECMAScriptの仕様上は、日付だけの引数もローカルタイムとして解釈するはずだったが、過去の実装など歴史的な経緯からUTCとして解釈される

    タイムゾーンオフセットがない場合、日付のみの形式は UTC 時刻として解釈され、日時形式はローカル時刻として解釈されます。 これは過去には ISO 8601 に適合しない仕様があったためですが、ウェブの互換性のために変更できませんでした。 Date - JavaScript | MDN

まとめ:new Date(...) に与える引数とその挙動の違い

入力形式の種類 解釈タイムゾーン 内部処理の挙動
引数なし (new Date()) ローカルタイム 現在のローカルタイムを UTC に換算してミリ秒で保持
"YYYY-MM-DD"(日付のみの文字列) UTC(例外的) 与えられた日付の 00:00:00 を UTC として解釈し、ミリ秒で保持
"YYYY-MM-DDTHH:mm:ss" ローカルタイム ローカルタイムとして解釈し、UTC に換算してミリ秒で保持
"YYYY-MM-DDTHH:mm:ss+09:00" など 明示されたタイムゾーン 指定されたタイムゾーンでの時刻を UTC に換算してミリ秒で保持

おわりに

今回はnew Date() の挙動についてまとめました。個人的には、「タイムゾーンを保持しない」、「内部でUNIXのミリ秒を保持」という2点を押さえたことで、かなり理解が深まりました。

次回は、生成した Date を「どう文字列として出力するか?」について、toString()toISOString()toLocaleString() の違いと落とし穴を整理していきます。お楽しみに!