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件 |
| UTC | 17件中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言語の結果・直す前と後のコードの違いは、次のページで見られます。
- 会計年度(第何期か)の計算
- 勤続年数(◯年◯か月◯日)(c015)
次回は最終回です。言語ではなく、「1か月」の決め方で答えが割れたケースを取り上げます。
- 第1回:「四捨五入」と書いたのに、4言語で答えが割れた
- 第2回:1,100 ÷ 1.1 が 1,000 にならない
- 第3回:負の数を入れたら、Go は止まり、JavaScript は NaN を返した
- 第4回:大きな整数で、PHP は黙って上限に張りつき、JavaScript は桁が化けた
- 第5回:日本で動かしたときだけ、JavaScript の日付が1日ずれた(この記事)
- 第6回:1月31日の「1か月後」はいつか
Analyzegear


