JavaScriptの日付(Date)で月の加算・減算が想定通りにならない

月だけ不自然な感じになってしまうというどちらかというと仕様の話




JavaScriptでDateの計算



年の計算をする場合日付の年+経過年数を年に設定する。
// +1年
var nowDate = new Date()
nowDate.setFullYear(nowDate.getFullYear() + 1)

月の計算をする場合日付の月+経過月数を月に設定する。
// +1ヶ月
var nowDate = new Date()
nowDate.setMonth(nowDate.getMonth() + 1)

日の計算をする場合日付の日+経過日数を日に設定する。
// +1日
var nowDate = new Date()
nowDate.setMonth(nowDate.getMonth() + 1)


上記でそこそこうまくいくのだが例えば、
2017/10/31 から-1ヶ月してみた場合2017/10/1
2018/01/31 から+1ヶ月してみた場合2018/3/3 になってしまう。
年月日を表示していれば仕様ですで押し切れそうな気もしないでもないが、月だけ表示で内部的にDate型で持っているケースなどはどうしても不自然な挙動になってしまう。


原因


そもそもJavaScriptに日付の計算というものはなくそれをsetXXXXというメソッドで代用しているのだがコレがどういう挙動をしているかというと
下記より
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth
1. 指定した値で月を置き換える。
2. 日付としてありえない値の場合は超過、不足分だけ補正する。
ということをやっている。

つまり上記の不具合は
2017/10/31 - 1ヶ月
→ 2017/09/31
→ 2017/09/30 + 1日
→ 2017/10/1

2018/01/31 + 1ヶ月
→ 2018/02/31
→ 2018/02/28 + 3日
→ 2018/03/03
という挙動になる。


対策


計算がおかしくなる場合、必ず日が前回の値と変わるので前回との値を見て補正する。

// 月の計算
function calcMonth(value) {
  var nowDate = new Date()
  // 計算前日付
  var preDate = nowDate.getDate()
  nowDate.setMonth(nowDate.getMonth() + value)
  
  if (nowDate.getDate() !== preDate) {
    // 計算後日付が一致しない場合、前月月末指定
    nowDate.setDate(0)
  }
  var domDate = document.getElementById("date")
  domDate.innerText = formatDate(nowDate)
}


動作サンプル




codepenがめっちゃ便利になってる

2017年12月6日水曜日