「契約日から1か月後」「来月の同じ日」を setMonth(getMonth() + 1) で計算すると、月末に近い日付(29〜31日)だけおかしな結果になります。2026年1月31日の1か月後は、2月28日ではなく3月3日になります。
この記事では、JavaScript の Date で月をまたぐときに起きる繰り上がりの仕組みと、月末に揃える計算の書き方を整理します。実行結果は Node.js v24.14.1 で確認し、日付文字列の解釈だけはブラウザ(macOS の WebKit)でも比べました。
タイムゾーンによるずれ(
new Date("2026-08-16")が日本時間の9時になる話など)は今回は扱わず、「月の計算」に絞ります。
1か月後が3月3日になる
const d = new Date(2026, 0, 31); // 2026年1月31日
d.setMonth(d.getMonth() + 1);
console.log(d.toDateString()); // Tue Mar 03 2026
先に押さえておきたいのは、new Date(年, 月, 日) の月が0始まりなことです。0 が1月、1 が2月です。
そのうえで、setMonth(1) は「2月31日」を作ろうとします。2026年の2月は28日までしかないので、はみ出した3日ぶんが翌月に繰り上がり、3月3日になります。
はみ出した日付は、エラーにならず繰り上がる
年・月・日を数値で渡したとき、Date は範囲外の月や日でもエラーにせず、前後の月へ繰り上げ・繰り下げます(コード中のコメントは各式の戻り値です)。
const f = (d) => d.toLocaleDateString("ja-JP");
f(new Date(2026, 1, 30)); // "2026/3/2" (2月30日 → 3月2日)
f(new Date(2026, 12, 1)); // "2027/1/1" (月に12=13番目の月 → 翌年1月)
f(new Date(2026, 2, 0)); // "2026/2/28" (3月0日 → 2月の最終日)
f(new Date(2026, 0, 0)); // "2025/12/31" (1月0日 → 前年の大みそか)
同じ仕組みで、ほかの月末でも起きます。
| 元の日付 | 1か月足した結果(setMonth(getMonth() + 1)) |
月末に揃えるなら |
|---|---|---|
| 2026年1月31日 | 2026年3月3日 | 2月28日 |
| 2028年1月31日(うるう年) | 2028年3月2日 | 2月29日 |
| 2026年3月31日 | 2026年5月1日 | 4月30日 |
| 2026年8月31日 | 2026年10月1日 | 9月30日 |
引き算でも同じです。2026年3月31日の1か月前を setMonth(getMonth() - 1) で計算すると、「2月31日」が繰り上がって3月3日になり、月が戻りません。
「0日」で月の最終日が取れる
上の例の new Date(2026, 2, 0) のように、日に 0 を渡すと「前の月の最終日」になります。これを使うと、うるう年も含めて、ある月の日数を簡単に求められます。
月の引数は0始まりなので、1〜12の月の番号をそのまま渡すと「その翌月」を指します。その0日、つまり指定した月の最終日になる、という仕組みです。
// month は 1〜12 の月の番号
const lastDay = (year, month) => new Date(year, month, 0).getDate();
lastDay(2026, 2); // 28(2026年2月)
lastDay(2028, 2); // 29(2028年2月・うるう年)
月末に揃える書き方
「1か月後の同じ日。その日がなければ月末」にしたいときは、移動先の月の最終日を求めて、日付をそこまでに抑えます。
function addMonths(date, n) {
const y = date.getFullYear();
const m = date.getMonth() + n;
const last = new Date(y, m + 1, 0).getDate(); // 移動先の月の最終日
return new Date(y, m, Math.min(date.getDate(), last));
}
日付だけを扱う前提の関数です(返ってくる日付の時刻は0時0分になります)。
| 元の日付 | n | 結果 |
|---|---|---|
| 2026年1月31日 | 1 | 2026年2月28日 |
| 2028年1月31日 | 1 | 2028年2月29日 |
| 2026年3月31日 | 1 | 2026年4月30日 |
| 2026年3月31日 | -1 | 2026年2月28日 |
| 2026年11月30日 | 3 | 2027年2月28日 |
| 2026年12月31日 | 2 | 2027年2月28日 |
| 2026年1月15日 | 1 | 2026年2月15日 |
new Date(y, m, ...) の月に12以上や負の数が入っても、年をまたいで繰り上がるので、年の計算を自分で書く必要はありません。
「前回の結果から1か月ずつ」足すと日付がずれていく
毎月の更新日を、前回の結果に1か月ずつ足して作ると、一度月末に揃えた日付が元に戻りません。
// 前回の結果に足していく
let d = new Date(2026, 0, 31);
for (let i = 0; i < 4; i++) d = addMonths(d, 1);
// 2/28 → 3/28 → 4/28 → 5/28(28日に固定されてしまう)
// 最初の日付から n か月後として計算する
const base = new Date(2026, 0, 31);
[1, 2, 3, 4].map((n) => addMonths(base, n));
// 2/28 → 3/31 → 4/30 → 5/31
毎月の更新日や締め日は、「最初の日付から n か月後」として毎回計算すると、月末の日付を保てます。
「2026-02-30」は環境によって扱いが違う
入力された日付が正しいかを new Date() で確かめるときは、もう1つ注意があります。存在しない日付の文字列を渡したときの結果が、実行環境で違いました(日本時間で表示した結果。Chrome と Firefox では試していません。Chrome は Node.js と同じ V8 エンジンなので、Node.js と同じ結果になる可能性があります)。
| 入力 | Node.js v24.14.1 | WebKit(macOS) |
|---|---|---|
new Date("2026-02-30") |
2026年3月2日 | Invalid Date |
new Date("2026-02-29") |
2026年3月1日 | Invalid Date |
new Date("2026/02/30") |
2026年3月2日 | 2026年3月2日 |
new Date("2026-13-01") |
Invalid Date | Invalid Date |
Node.js では、2月30日のような日付でも繰り上がった日付として作られてしまい、「Invalid Date かどうか」だけでは2月30日の入力を弾けません。年・月・日を数値で取り出し、new Date(年, 月 - 1, 日)(ここの月は1〜12)で作った日付の年・月・日が、入力と同じかを比べると、繰り上がった日付を見分けられます。
function isValidDate(y, m, d) {
const date = new Date(y, m - 1, d);
return date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d;
}
// 文字列から取り出すときは、数値に変えてから渡す(文字列のままだと === で一致しない)
const [y, m, d] = "2026-02-30".split("-").map(Number);
isValidDate(y, m, d); // false
isValidDate(2028, 2, 29); // true
AI が書いた日付計算を見るときの確認ポイント
AI に「1か月後の日付を出して」と頼むと、setMonth(getMonth() + 1) の形で返ってくることがあります。月の途中の日付で動作確認すると正しく見えるので、次の点を見ておくと安心です。
- 月末(29〜31日)の日付で試したか。1月31日・3月31日・8月31日あたりが分かりやすい
- 毎月の日付を、前回の結果に足して作っていないか
- 入力チェックを
isNaN(new Date(文字列))だけで済ませていないか
まとめ
- 年・月・日を数値で渡したとき、
Dateははみ出した日付をエラーにせず繰り上げる。2026年1月31日にsetMonth(getMonth() + 1)すると3月3日になる - 日に
0を渡すと前の月の最終日になる。1〜12の月 m の日数はnew Date(年, m, 0).getDate()で求められる - 「同じ日、なければ月末」は、移動先の月の最終日で日付を抑える
- 毎月の日付は、最初の日付から n か月後として計算する
- 存在しない日付の文字列の扱いは環境で違う。入力チェックは作った日付と入力を比べる
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。