日本で動かしたときだけ、JavaScript の日付が1日ずれた ― 同じ仕様、違う答え⑤

Go
B!

3月決算で、2020年4月に設立した会社があります。2025年6月15日は第何期で、その期はいつからいつまででしょうか。

答えは 第6期(2025-04-01 〜 2026-03-31) です。PHP・Python・Go は正解しました。JavaScript は、期の数こそ正しいものの、日付を 2025-03-31 〜 2026-03-30 と、すべて1日前にずらして返しました。

toISOString は、UTC に直してから文字列にする

JavaScript では、日付を「2025-04-01」の形にするのに、次の書き方がよく使われます。

const fmt = (d) => d.toISOString().slice(0, 10);

new Date(2025, 3, 1) は、実行している環境のタイムゾーンでの 2025年4月1日 0:00 を表します。一方、toISOString() は、それを UTC(世界標準時)に直してから文字列にします。

日本時間の 4月1日 0:00 は、UTC では 3月31日 15:00 です。日付の部分だけを切り出すと、3月31日になります。

// 日本(Asia/Tokyo)で実行
new Date(2025, 3, 1).toISOString().slice(0, 10)  // "2025-03-31"

// ロサンゼルス(America/Los_Angeles)で実行
new Date(2025, 3, 1).toISOString().slice(0, 10)  // "2025-04-01"

同じコードが、動かす場所で通ったり落ちたりする

同じ JavaScript のコードを、タイムゾーンを変えて動かしました。

実行環境のタイムゾーン正解と一致したケース
日本(Asia/Tokyo・UTC+9)17件中1件
UTC17件中15件
ロサンゼルス(America/Los_Angeles・UTC−8)17件中15件

ずれるのは、UTC より東のタイムゾーンだけです。ロサンゼルスの 0:00 は、UTC では同じ日の朝 8:00 なので、日付が変わりません。

クラウドのサーバーや CI は、UTC で動いていることがよくあります。テストはそこで通るのに、日本の利用者の手元でだけずれる、という形で表に出ます。

この検証は毎朝、日本時間(Asia/Tokyo)に設定した環境で動かしています。実行環境のタイムゾーンは、結果と一緒に記録しています。

夏時間のある地域では、日数も1日ずれる

タイムゾーンの影響は、もう1つありました。勤続年数の仕様で、JavaScript は通算日数を次のように求めています。

Math.floor((base - join) / (24 * 60 * 60 * 1000))

日本では問題ありません。ところがロサンゼルス時間で動かすと、1985年4月1日入社・2026年10月10日(c015)の通算日数が 15,166日(正しくは15,167日)になりました。

1985年4月1日は標準時、2026年10月10日は夏時間です。2つの日付の差が1時間足りなくなり、切り捨てで1日減りました。

どう直したか ― 日付を Date に入れない

年・月・日の数のまま計算し、文字列も自分で組み立てました。月末の日だけは、タイムゾーンの影響を受けない Date.UTC で求めています。

const daysInMonth = (y, m) => new Date(Date.UTC(y, m, 0)).getUTCDate();

daysInMonth(2025, 2)  // 28(2025年2月)

日数を数えるときも、Date.UTC で作った値どうしの差にしました。UTC には夏時間がないので、1日はいつも同じ長さです。

直した後は、日本・UTC・ロサンゼルス・UTC+14 のどのタイムゾーンで動かしても、17件中17件が正解と一致しました。

まとめ

  • toISOString() は UTC に直してから文字列にする。日本のような UTC より東のタイムゾーンでは、日付が1日前にずれる
  • UTC で動くサーバーや CI では通り、日本の利用者の手元でだけずれる。テストはタイムゾーンを変えて回す
  • ミリ秒の差を1日の長さで割る数え方は、夏時間で1時間ずれて1日減ることがある
  • 日付だけを扱うなら、年・月・日の数のまま計算する。どうしても Date を使うなら UTC で作る

この記事のケースの正解表・4言語の結果・直す前と後のコードの違いは、次のページで見られます。

次回は最終回です。言語ではなく、「1か月」の決め方で答えが割れたケースを取り上げます。

B!
← 一覧へ戻る