1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1月31日の「1か月後」が3月3日になる — Date の月の繰り上がりと月末の扱い

1
Posted at

「契約日から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)の設計までセットで含みます。

※ 中上級者には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?