JavaScriptでn年後の同月の月末を求める処理が必要になり、Date
オブジェクトを使って日付の加減算を行ったのだが、思わぬ罠が潜んでいたので共有することにした。
Dateオブジェクトで日付の加減算
Date
オブジェクトにはgetMonth
やsetMonth
など、年月日の数字を取得/設定できるメソッドが用意されているため、これを使うのが一般的だろう。
var date = new Date("2020/4/11");
date.setMonth(date.getMonth() + 1);
console.log(date.toLocaleDateString()); // 2020/5/11
date.setFullYear(date.getFullYear() - 2);
console.log(date.toLocaleDateString()); // 2018/5/11
月末を求める場合はsetDate(0)
月末を求める場合は、Date
オブジェクトを求めたい月の翌月に設定し、setDate(0)
を実行する。
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate
- 例えば、dayValue に 0 を与えた場合、日付は前月の最終日に設定されます。
var date = new Date("2020/4/11");
date.setMonth(date.getMonth() + 1);
date.setDate(0);
console.log(date.toLocaleDateString()); // 2020/4/30
月末を考慮していないとハマる罠
たとえば、2020/1/31を基準に、Nヶ月後の月末日を求めたい時は以下のようにするだろう。
var date = new Date("2020/1/31");
var n = 1; //翌月の月末日(2020/2/29)を求めるために1を設定
date.setMonth(date.getMonth() + n + 1); // 2020/3/31 に設定する
date.setDate(0);
console.log(date.toLocaleDateString()); // 2020/2/29 になる
ところが、これを以下のように月計算を分割すると正しい結果が得られない。
var date = new Date("2020/1/31");
var n = 1; //翌月の月末日(2020/2/29)を求めるために1を設定
date.setMonth(date.getMonth() + n); // 2020/3/2に設定されてしまう
date.setMonth(date.getMonth() + 1); // 2020/4/2に設定されてしまう
date.setDate(0);
console.log(date.toLocaleDateString()); // 2020/3/31 になってしまう
しかも、この現象は基準日が2020/1/29以前であれば発生しないという厄介なものだ。
月末への対策
加減算を行う前に日付を1日に設定するのが簡単だろう。
var date = new Date("2020/1/31");
date.setDate(1); // 2020/1/1に設定する
date.setMonth(date.getMonth() + 1); // 2020/2/1に設定される
date.setMonth(date.getMonth() + 1); // 2020/3/1に設定される
date.setDate(0);
console.log(date.toLocaleDateString()); // 2020/2/29 になってくれる
さいごに
まとめてみると簡単なことだが、コードレビューでこれを発見するのは困難ではなかろうか。
特に、日付演算を不用意に関数化して分割しているとなおさらだろう。
テスト工程で見つける前提とするなら、観点として月末という境界値チェックが必要となることは覚えておきたい。