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)に依存します。LANG や LC_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 仕様外のため、使用は避けてください。