JavaScriptで日時を扱っていると、「15:00に設定したはずなのにtoISOString()を使うと06:00になってしまう」「DBやAPIから受け取った日時がちょうど9時間ずれて表示される」といったトラブルに遭遇することがよくあります。

この「9時間のズレ」は、JavaScriptのバグや計算ミスではなく、UTCとJSTの時差や、Dateオブジェクトの各メソッドがどのタイムゾーンを基準に出力しているかが原因になっているケースが多くあります。

この記事では、日時が9時間ずれて見える仕組みから、new Date()の文字列解釈の違い、+9時間の手動加算を避けるべき理由、Intl.DateTimeFormatを使った堅実なJST表示方法までを解説します。

JavaScriptで日時が9時間ずれる主な原因はUTCとJSTの違い

まず知っておくべき基本は、UTCとJSTの時差です。

  • UTC(協定世界時):世界共通の基準となる時刻
  • JST(日本標準時):UTCより9時間進んだ時刻(UTC+9)

例えば、「日本時間の2026年9月13日15:00」という瞬間は、UTC表記では「2026年9月13日06:00」になります。

【同じ「特定の瞬間」を表す表記の違い】
JST(日本時間): 2026-09-13 15:00
                    ↕ (時差 9時間)
UTC(協定世界時): 2026-09-13 06:00 (2026-09-13T06:00:00.000Z)

画面上で「15:00が06:00に変わってしまった」とき、日時の値そのものが狂ったのではなく、同じ瞬間をJSTではなくUTC基準で表示しているケースが大半です。

JavaScriptのDateは「JSTの日時」を保存しているわけではない

JavaScriptのDateインスタンスは、「日本時間の2026年9月13日15時」というローカルの日時文字列をそのまま保持しているわけではありません。

const date = new Date();
console.log(date.getTime());
// 1970-01-01T00:00:00Z からの経過ミリ秒

内部で保持しているのは、1970年1月1日00:00:00 UTCからの経過ミリ秒(タイムスタンプ)という単一の「絶対的な瞬間」です。

その瞬間を画面に出力するときに、「実行環境(ブラウザ等)のローカルタイムゾーン」として解釈するのか、「UTC」として解釈するのかによって、取り出される日時の文字列が変化します。

toISOString()を使うと9時間ずれて見える理由

最も多くの開発者がつまずくのが、toISOString()を呼び出した瞬間です。

// 日本時間(+09:00)の 15:00 として生成
const date = new Date('2026-09-13T15:00:00+09:00');

console.log(date.toISOString());
// 出力結果: "2026-09-13T06:00:00.000Z" (9時間引かれたように見える)

toISOString()は必ずUTCで返す

ECMAScriptの仕様上、toISOString()はタイムゾーンを常にUTCとして出力すると定められています。

末尾のZは、UTC(UTCオフセット+00:00)であることを示します。

そのため、toISOString()を実行して06:00:00.000Zになったとしても、「勝手に9時間引かれて値が壊れた」のではなく、「保持している瞬間を正確にUTCのISO形式で書き出した」という正しい挙動です。

ローカル時刻とUTCでの出力の違い

同じdateオブジェクトであっても、呼び出すメソッドによって参照するタイムゾーンが異なります。

const date = new Date('2026-09-13T15:00:00+09:00');

// 日本のブラウザ環境で実行した場合:
console.log(date.toString());    // "Sun Sep 13 2026 15:00:00 GMT+0900 (日本標準時)" → ローカル時刻
console.log(date.toUTCString()); // "Sun, 13 Sep 2026 06:00:00 GMT"                 → UTC
console.log(date.toISOString()); // "2026-09-13T06:00:00.000Z"                      → UTC

new Date()へ渡す文字列によってタイムゾーンの解釈が変わる

new Date()に文字列を渡してインスタンスを作る際、文字列のフォーマットによって「ローカル時刻として解釈されるか」「UTCとして解釈されるか」が異なるという仕様上の落とし穴があります。

1.タイムゾーンオフセット付きの日時文字列

new Date('2026-09-13T15:00:00+09:00');

明示的に+09:00やZが付いている場合、ブラウザはそのタイムゾーン情報に基づいて正確にその瞬間をパースします。

タイムゾーンを明示できるため、解釈が曖昧になりにくい形式です。

2.時刻を含み、タイムゾーン情報がない文字列

new Date('2026-09-13T15:00:00');

ISO形式で時刻(T15:00:00)が含まれ、末尾にZや+09:00がない場合、ECMAScript仕様ではローカル時刻(実行環境のタイムゾーン)として解釈されます。

日本の端末では「JSTの15:00」になります。

3.日付だけの文字列(YYYY-MM-DD)

const date = new Date('2026-09-13');

// 日本の環境で表示してみると...
console.log(date.toString());
// 出力結果: "Sun Sep 13 2026 09:00:00 GMT+0900 (日本標準時)"

日付のみの形式(YYYY-MM-DD)を渡すと、仕様上UTCの00:00:00として解釈されます。

その結果、「UTCの9月13日00:00」となり、日本のローカル時刻(+9時間)として取り出すと「9月13日の09:00」という意図しない時刻になってしまいます。

日付だけで初期化したい場合は特に注意が必要です。

日本時間として表示したいときの対策

画面上に日本時間(JST)として整形して表示したい場合の適切なアプローチです。

toLocaleString()でローカル時刻として表示する

閲覧者のブラウザ環境のタイムゾーンに合わせて表示する最も手軽な方法です。

const date = new Date('2026-09-13T06:00:00.000Z');

console.log(date.toLocaleString('ja-JP'));
// 日本の環境: "2026/9/13 15:00:00"

ただし、これは実行環境のタイムゾーンに依存します。

海外からアクセスしているユーザーや、PCのタイムゾーン設定がUTCになっている環境では、日本の時刻ではなく現地の時刻で表示されます。

常に日本時間で表示したいならIntl.DateTimeFormatでtimeZoneを指定する

閲覧者の環境設定に関わらず「常に日本時間(JST)」で表示したい場合は、Intl.DateTimeFormatでtimeZone: 'Asia/Tokyo'を明示します。

const date = new Date('2026-09-13T06:00:00.000Z');

const formatter = new Intl.DateTimeFormat('ja-JP', {
  timeZone: 'Asia/Tokyo',
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
});

console.log(formatter.format(date));
// 出力例: "2026/09/13 15:00:00"(時刻はAsia/Tokyo基準。表示形式は環境により多少異なる場合があります)

この方法を使えば、元のdateオブジェクトの値を不正に書き換えることなく、安全にJST表記を得ることができます。

9時間を手動で足す・引く方法は基本的に避ける

日時のズレを直そうとして、以下のようにJavaScript上で手動で9時間(ミリ秒)を加減算するコードを書くのは避けるべきです。

// ✕ 避けるべきコード(手動で9時間を足す)
const jstDate = new Date(date.getTime() + 9 * 60 * 60 * 1000);
console.log(jstDate.toISOString()); // 無理やりJSTの数値をISO表記にするハック

このような手動補正を行うと、以下のような問題が発生します。

  • 別の「9時間後の瞬間」を表すDateになってしまう:JST表示へ変換したつもりでも、jstDateは元のdateとは別の「9時間後の瞬間」を表すDateになります。その値を保存・通信などへ流用すると、9時間進んだ誤った時刻として扱われる原因になります。
  • サマータイムや他地域対応で破綻する:将来的に他のタイムゾーンを扱う際、固定値の加算処理は修正が極めて困難になります。

「タイムゾーンの変換」はDateの数値を足し引きするのではなく、フォーマット・表示の段階でtimeZoneを指定して行うのが実務上の基本原則です。

APIやデータベースから受け取った日時がずれるときの確認点

APIやバックエンドから受け取った日時文字列がずれてしまう場合は、以下のチェックリストを確認してください。

  • 末尾にZが付いているか(例:2026-09-13T06:00:00Z)→ UTC時刻として正しくブラウザに解釈されます。
  • タイムゾーンオフセットが付いているか(例:2026-09-13T15:00:00+09:00)→ JSTの時刻として正しく解釈されます。
  • タイムゾーン情報のない文字列になっていないか(例:2026-09-13T15:00:00)→閲覧者のローカル時刻として解釈されるため、海外環境でズレが生じます。
  • 日付のみの形式(2026-09-13)をそのままパースしていないか→ UTCの00:00として扱われ、JSTで表示すると朝の09:00になります。
  • 表示段階でtoISOString()を使ってしまっていないか→ UTC出力になるため、画面表示にはIntl.DateTimeFormatなどを利用します。

UTCで保存して表示時にJSTへ変換する考え方

複数の地域やシステムをまたぐWeb開発では、以下のような役割分担を行うとトラブルを最小限に抑えられます。

  • 通信・保存(API / DB):特定の瞬間を表す値として、UTC形式(2026-09-13T06:00:00Z)やUNIXタイムスタンプでやり取りする。
  • 画面表示(フロントエンド):受け取ったDateをIntl.DateTimeFormatやtoLocaleString()で必要なタイムゾーン(Asia/Tokyoなど)へ整形して表示する。

この境界線を明確にしておくことで、「どこで9時間が足され、どこで引かれたのか分からない」という混乱を防ぎやすくなります。

UTC・JST・Date・各メソッドの役割の整理

日時処理に関わる主要な概念とメソッドの役割を整理すると以下のようになります。

項目/メソッド 種類/戻り値 主な意味・役割
UTC タイムゾーン基準 世界共通の基準時刻(協定世界時)
JST タイムゾーン基準 日本標準時(UTC+9時間)
Dateオブジェクト データ実体 1970年からの経過ミリ秒(絶対的な瞬間)を保持する
date.toString() 文字列出力 実行環境のローカルタイムゾーンで日時を文字列化
date.toUTCString() 文字列出力 UTC基準の日時として文字列化
date.toISOString() 文字列出力 UTC基準のISO 8601形式(…Z)で文字列化
date.toLocaleString() 文字列出力 実行環境の言語・ローカルタイムゾーンに合わせて表示
Intl.DateTimeFormat 整形フォーマッター timeZone: 'Asia/Tokyo'などを指定して表示形式を厳密に制御

まとめ

JavaScriptで日時が9時間ずれて見えるのは、Dateオブジェクトが壊れたわけではなく、UTCとJSTという異なる基準の時刻を混同していることが典型的な原因です。

  • 9時間ズレの正体:JST(UTC+9)とUTC(toISOString()やZ表記)の基準の違い
  • 日付文字列の仕様:YYYY-MM-DDはUTCの00:00として解釈され、JSTでは朝の09:00になる
  • 安全な表示方法:+9時間を手動で加算せず、Intl.DateTimeFormatのtimeZone: 'Asia/Tokyo'を使って表示時に変換する
  • システム設計の基本:特定の瞬間を扱うデータでは、通信・保存をUTCなどの明確な基準で扱い、表示時にローカル/JSTへ整形する設計がよく使われる

「保存されている絶対的な瞬間」と「表示するタイムゾーン」を切り分けて考えることで、日時のバグをすっきりと切り分けられるようになります。