エンジニアを目指す初学者に向けて、わかりやすく解説したブログです。

【JavaScript】Dateのタイムゾーン挙動、文字列変換を完全に理解する

JavaScriptのDateは理解が難しく、タイムゾーン関連のバグを仕込みやすい。

この記事では、このDateとタイムゾーンの関係性を完全に理解することを目指す。

前提知識:ISO 8601

まずは前提知識としてISO 8601のフォーマットを軽く理解しておく。

YYYY-MM-DDThh:mm:ss±hh:mm

このフォーマットで表記される日時のことである。

ただし今回必要なポイントは2点のみ。

  1. UTCを示す場合、末尾が Zである
    1. 2026-03-27T17:30:00Z:UTCの時刻で「2026/03/27 17:30」を示す
  2. JSTを示す場合、末尾が +09:00である
    1. 2026-03-27T17:30:00+09:00:JST(+09:00)の時刻で「2026/03/27 17:30」を示す(UTCで「2026-03-27 08:30」のこと)
    2. 覚え方:「+09:00進んだ現地の時刻でその時間」

重要:Dateの実体はただの経過ミリ秒

まずタイムゾーンの変換などの理解に進む前に、Dateオブジェクトの実体について理解しておく必要がある。

JavaScriptのDateオブジェクトは、それ単体ではタイムゾーンは何も関係ない。

Dateオブジェクトは、単に基準時刻から何ミリ秒経過したかが格納されているだけである。

1970 年 1 月 1 日午前 0 時 (UTC)(元期)からのミリ秒を表す整数値をカプセル化しています。

参考:Icon in a page linkDate - JavaScript | MDN

実例:new Date()

これは new Date()を使ってみると理解ができる。

どんなタイムゾーン設定がされていても、 new Date()は基準時刻からの経過ミリ秒に過ぎない。

■5/9の14時頃の実行

どのタイムゾーンで実行しても、基準時刻からの経過時刻は同じ。

$ TZ=Asia/Tokyo node -e 'console.log(new Date())'
2026-05-09T05:19:27.007Z

$ TZ=UTC node -e 'console.log(new Date())'
2026-05-09T05:19:32.681Z

UTCで表示されているのは、nodeが「 console.log()でDateオブジェクトを引数に取ったときはUTC表記で出力される」という仕様のためである。

タイムゾーンに注意を払うポイントは2点

Dateオブジェクトというのは、中身はただの経過ミリ秒のため
実はタイムゾーンが絡んでくるのは「その経過時間をどうやって表現するか」に関わるときのみである。

つまり、タイムゾーンに注意を払わなければいけないタイミングは以下の2点のみ。

  1. Dateオブジェクトを文字列で表示するとき
  2. 任意の日時でDateオブジェクトを生成するとき

1. Dateオブジェクトを文字列で表示するとき(Date→文字列変換)

デバッグ出力などをして確認する場合、これを覚えておくと良い。

  1. toISOString() :タイムゾーン設定の影響を受けずに常にUTCで表示
  2. toString() :タイムゾーンの設定の影響を受けて表示

toISOString()

このメソッドはタイムゾーン設定の影響を受けない。

どんなタイムゾーン設定でも、末尾が ZのUTCで表示される。

$ TZ=Asia/Tokyo node -e 'console.log(new Date().toISOString())'
2026-05-09T05:42:32.136Z

$ TZ=UTC node -e 'console.log(new Date().toISOString())'
2026-05-09T05:42:42.823Z

toString()

これはタイムゾーン設定の影響を受ける。

日本時刻で5/9の14:40頃の実行した場合。

# JSTの14:40はUTCの5:40なので、「UTCの5:40」として表示される
$ TZ=UTC node -e 'console.log(new Date().toString())'
Sat May 09 2026 05:40:07 GMT+0000 (Coordinated Universal Time)

# JSTの時刻でそのまま表示される
$ TZ=Asia/Tokyo node -e 'console.log(new Date().toString())'
Sat May 09 2026 14:40:13 GMT+0900 (Japan Standard Time)

2. 任意の日時でDateオブジェクトを生成するとき

基本的に new Date()の引数に何か値を入れて生成することになるが
これには大きく分けて2通りの方法が存在する。

  1. 日時成分を入れて生成する
  2. 文字列を入れて生成する

他にも方法はあるが、よく使うのは上記の2つなので省略。

参考:Icon in a page linkDate() コンストラクター - JavaScript | MDN

①日時成分を入れて生成する

これはあまり難しく考えなくても良い。ポイントは2つである。

  1. 常にタイムゾーン設定の影響を受ける
  2. タイムゾーンを明示的に指定して生成できない

覚えておくべきことは
「コードに new Date(2026, 5, 9, 11, 15)などを書いた場合、実行環境(タイムゾーン設定)によって値が変わる」
ということである。

この引数の仕様上、タイムゾーンを受け付けることはないので
この方法で生成した日時は確実にタイムゾーンの影響を受けてしまう。

# JSTの2026/5/6 00:00(UTCの2026/5/8 15:00)として解釈される
$ TZ=Asia/Tokyo node -e 'console.log(new Date(2026, 5, 9))'
2026-06-08T15:00:00.000Z

# UTCの2026/5/6 00:00として解釈される
$ TZ=UTC node -e 'console.log(new Date(2026, 5, 9))'
2026-06-09T00:00:00.000Z
# JSTの2026/5/9 11:15:16(UTCの2026/5/9 2:15:16)として解釈される
$ TZ=Asia/Tokyo node -e 'console.log(new Date(2026, 5, 9, 11, 15, 16))'
2026-06-09T02:15:16.000Z

# UTCの2026/5/9 11:15:16として解釈される
$ TZ=UTC node -e 'console.log(new Date(2026, 5, 9, 11, 15, 16))'
2026-06-09T11:15:16.000Z

②文字列を入れて生成する

new Date()の引数には、日時を表す文字列を入れて生成することもできる。

書式

ISO 8601のフォーマットを使うことができ、基本的な書式は以下の通り。

YYYY-MM-DDTHH:mm:ss.sssZ

そして、 YYYY以外は全て省略可能である。

■例

  • 2011-10-10(日付のみ)
  • 2011-10-10T14:48:00(日時)
  • 2011-10-10T14:48:00.000+09:00(日時+タイムゾーン)

タイムゾーンが省略して書けてしまうので、2通りに分けて考えると良い。

  1. タイムゾーンを指定する場合
  2. タイムゾーンを指定しない場合

①タイムゾーンを指定する場合

指定する場合は簡単である。

タイムゾーンが指定されているということは基準時刻からの経過ミリ秒が一意に定まるため、意図通りに生成される。

■JSTで2026/05/09 20:15:16.115(UTCで2026/05/09 11:15:16.115)を指定した場合。

# UTCで11:15なので、その通りに生成される
$ TZ=UTC node -e 'console.log(new Date("2026-05-09T11:15:16.115Z"))'
2026-05-09T11:15:16.115Z

# JSTの11:15(UTCで2:15)として生成される
$  TZ=UTC node -e 'console.log(new Date("2026-05-09T11:15:16.115+09:00"))'
2026-05-09T02:15:16.115Z

# タイムゾーンがJSTでも、UTCの時刻を指定しているのでUTCの11:15として生成される
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026-05-09T11:15:16.115Z"))'
2026-05-09T11:15:16.115Z

# JSTの11:15(UTCの2:15)で生成される
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026-05-09T11:15:16.115+09:00"))'
2026-05-09T02:15:16.115Z

前にも説明したが、UTCで表示されるのはただ単にnodeのconsole.log()の仕様である。

③タイムゾーンを指定しない場合

このパターンは要注意である。

「日付のみ」「時刻を含む」で挙動が異なる点がかなりの初見殺し。

公式ドキュメントに以下のような記載がある。歴史的経緯でそうなってしまった模様。

タイムゾーンオフセットがない場合、日付のみの形式は UTC 時刻として解釈され、日時形式はローカル時刻として解釈されます。 UTC 時刻として解釈するのは、過去には ISO 8601 に適合しない仕様があったためですが、ウェブの互換性のために変更できませんでした。Broken Parser - A Web Reality Issue を参照してください。

参考:Icon in a page linkDate - JavaScript | MDN

■時刻を含む場合:タイムゾーン設定を基準に判断される

# タイムゾーン設定がUTCの場合、UTCの11:15として時刻が生成される
$ TZ=UTC node -e 'console.log(new Date("2026-05-09T11:15:16.115"))'
2026-05-09T11:15:16.115Z

# タイムゾーン設定がJSTの場合、JSTの11:15(UTCの2:15)として時刻が生成される
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026-05-09T11:15:16.115"))'
2026-05-09T02:15:16.115Z

■時刻を含まない場合:UTCとして判断される

実務では「時刻を含まない日付のみのデータ」を扱いたいときが結構あるが
そのときはこのパターンを踏んでしまう可能性があるので注意したほうが良い。

# 日付のみなので、システムのタイムゾーン関係なくUTCとして5/9の00:00とみなされる
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026-05-09"))'
2026-05-09T00:00:00.000Z

# 日付のみなので、システムのタイムゾーン関係なくUTCとして5/9の00:00とみなされる
$ TZ=UTC node -e 'console.log(new Date("2026-05-09"))'
2026-05-09T00:00:00.000Z

月まで、年だけも同様。

# 月まで
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026-05"))'
2026-05-01T00:00:00.000Z

# 年だけ
$ TZ=Asia/Tokyo node -e 'console.log(new Date("2026"))'
2026-01-01T00:00:00.000Z

まとめ

  • Dateオブジェクトは基準時刻からの経過時間が格納されており、これ単体ではタイムゾーンは関係ない
  • タイムゾーンに注意を払わなければいけないタイミングは2つ
    • Dateオブジェクトを文字列で表示するとき
    • 任意の時刻でDateオブジェクトを生成するとき
  • Date→文字列変換するときは、基準時刻からの経過ミリ秒をどうやって表記するかに過ぎない
  • new Date(2026, 5, 9)のように任意の日時を生成するときは、タイムゾーン設定の影響を受けて解釈される
  • 文字列→Date変換において、タイムゾーンが記載された文字列は素直に考えて良い
  • 文字列→Date変換において、タイムゾーンを記載しない場合は要注意
    • 時刻を含む場合:タイムゾーン設定の影響を受けて解釈される
    • 時刻を含まない場合:UTCとして解釈される