JavaScriptで日付が1日ずれる原因:タイムゾーン問題の対処法まとめ

スポンサーリンク

JavaScriptで日付が1日ずれる原因:タイムゾーン問題の対処法まとめ

JavaScript で日付を扱うとき「保存したはずの日付が1日前になっている」「API に送ったら前日の日付が届いた」という問題がよく起きます。原因はほぼ必ず UTC とローカル時刻の解釈の違い です。


なぜずれるのか:2種類の解釈

new Date() に文字列を渡すとき、書き方によって UTCローカル時刻 のどちらで解釈されるかが変わります。

書き方 解釈
new Date('2026-07-05') UTC として解釈(ISO 8601 仕様)
new Date('2026/07/05') ローカル時刻 として解釈(実装依存・非標準
new Date('2026-07-05T00:00:00') ローカル時刻 として解釈
new Date(2026, 6, 5) ローカル時刻 として解釈(月は0始まり)

日本(JST = UTC+9)の場合、UTC の 0時は JST の 9時です。ローカル時刻の 0時は UTC の前日 15時です。この9時間のズレが「1日ずれ」を引き起こします。

「ローカル時刻」は OS のタイムゾーン設定(TZ 環境変数や /etc/localtime)に依存します。LANGLC_ALL などのロケール設定は Date の解釈には影響しません。


パターン1:toLocaleDateString() で前日が表示される

UTC-X(アメリカ・ヨーロッパなど)の環境でよく起きます。

const date = new Date('2026-07-05') // UTC 00:00:00 として解釈
console.log(date.toLocaleDateString('ja-JP'))
// UTC-5 環境 → "2026/7/4" ← 前日になる!
// JST 環境  → "2026/7/5" ← ずれない

ハイフン形式は UTC midnight として解釈されるため、UTC より遅れたタイムゾーンでは toLocaleDateString() が前日を返します。


パターン2:toISOString() で取り出した日付が前日になる

JST 環境でよく起きます。「スラッシュにすれば大丈夫」と思って変換したら逆にずれるケースです。

// Date の作り方で内部の UTC 時刻が変わる
const a = new Date('2026-07-05')  // UTC 00:00:00 として解釈
const b = new Date('2026/07/05')  // ローカル midnight(= UTC 前日 15:00)として解釈 ※V8 実装依存・非標準

console.log(a.toISOString()) // "2026-07-05T00:00:00.000Z" ← UTC なので正確
console.log(b.toISOString()) // "2026-07-04T15:00:00.000Z" ← JST midnight を UTC 変換 → 前日!

// よくあるミス:toISOString() で日付部分だけ取り出す
console.log(a.toISOString().split('T')[0]) // "2026-07-05" ← 正確
console.log(b.toISOString().split('T')[0]) // "2026-07-04" ← 1日ずれる!

toISOString() は常に UTC で出力します。JST midnight(0時)は UTC では前日の 15時なので、スラッシュ形式で作った Date を toISOString() に通すと日付部分が前日になります。


対処法

対処法 1:UTC で統一する

日付文字列をハイフン形式で受け取り、UTC メソッドで操作・表示する。

const date = new Date('2026-07-05') // UTC 解釈

// NG:getDate() はローカル時刻。タイムゾーンによってずれる
console.log(date.getDate())

// OK:getUTCDate() で UTC の日付を取得
console.log(date.getUTCFullYear()) // 2026
console.log(date.getUTCMonth() + 1) // 7
console.log(date.getUTCDate())       // 5

// toISOString() も UTC なのでそのまま使える
console.log(date.toISOString().split('T')[0]) // "2026-07-05" ← 正確

対処法 2:数値で年月日を直接渡す

ローカル時刻として日付を扱う場合、数値で渡すのが仕様準拠の方法です。new Date(year, month - 1, day) はどのランタイムでも必ずローカル midnight を返します。

// フォームから "2026-07-05" を受け取った場合
const input = '2026-07-05'
const [year, month, day] = input.split('-').map(Number)
const date = new Date(year, month - 1, day) // 月は0始まり

// OK:getDate() はローカル時刻なので正確
console.log(date.getFullYear())  // 2026
console.log(date.getMonth() + 1) // 7
console.log(date.getDate())      // 5

// OK:ローカル日付文字列を自分で組み立てる
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
console.log(`${y}-${m}-${d}`) // "2026-07-05" ← 正確

⚠ スラッシュ形式(YYYY/MM/DD)は使わない

new Date('2026/07/05') のようなスラッシュ区切りは ECMAScript の仕様外です。V8(Node.js / Chrome)では現在ローカル時刻として解釈されますが、この動作は仕様で保証されていません。他のランタイムや将来のバージョンで挙動が変わる可能性があります。

// NG:非標準・実装依存(ランタイムによって挙動が異なる)
const date = new Date('2026/07/05')

// OK:数値で渡す(ECMAScript 仕様準拠)
const date2 = new Date(2026, 6, 5) // 月は 0 始まり(6 = 7月)

まとめ

目的 推奨する方法
表示のみ(ローカル) new Date(year, month - 1, day) + getDate()
API 送信・DB 保存(UTC 文字列) new Date('YYYY-MM-DD') + toISOString().split('T')[0]
フォーム値から生成 new Date(year, month - 1, day)
UTC メソッドで操作 getUTCFullYear() / getUTCMonth() / getUTCDate()

原則として「UTC か ローカルかを統一する」 ことが最も重要です。new Date('YYYY-MM-DD')(UTC 解釈)で作って getDate()(ローカルメソッド)で読むなど、解釈と出力を混在させると必ずずれが生じます。スラッシュ形式(YYYY/MM/DD)は ECMAScript 仕様外のため、使用は避けてください。