JavaScriptでNヶ月後やNヶ月前を取得しよう(月末の処理に注意！！)

#はじめに
少し前に業務で入力日付の1ヶ月後を取得する処理を作ったが、予想以上に苦戦してしまったため記事にしました。特にJavaScriptの日付の初期化については、他の記事でもかなり見落としがあったため注意して欲しいです。

#概要
一か月後の定義は[民法第143条第2項（暦による期間の計算）](https://www.minnpou-sousoku.com/commentary-on-civil-law/143_2/)を参照する。

具体例を示すと
2020年1月13日の一ヶ月後→2020年2月12日
2020年1月31日の1ヶ月後→2020年2月29日
2020年1月1日の1ヶ月後→2020年1月31日
となる。

一ヶ月前はこの矢印を逆にすればよい(なお2月29日の一ヶ月前は1月30日である事には注意)。


画面は以下の記事を参考にして作成した。
[UIのイケているdatepickerライブラリpickadate.jsが最高だった（demo有）](https://qiita.com/Yuta_Fujiwara/items/e80fb931af623bd8e73e)



#主な実装方法

画面イメージは実際に作ったデモページを参考にして下さい。
[デモページ](https://ainouiknow.github.io/calDatejs/)


画面からは「yyyy年MM月dd日」の形式で入力日付を持ってくる事を想定する。
その場合はsubstringを使用して年、月、日を分割させる。

```js
let year = date.substring(0,4); 
let month = date.substring(5,7);
let day = date.substring(8,10);
```


次は1月31日を純粋に一ヶ月増やした、「2月30日」という存在しない日付を取得してこない処理を実装する(正確には、この処理を実装しない場合は2月30日ではなく、3月3日が返ってくる)。

あらかじめ、一ヶ月先の月(この場合は2月)の月末を取得し、『入力日付の「日」-1』と『月末の日付』を比較して、月末の日付を超えないように正しい日付を取得する。
月末は以下のようにして取得した。

```js:
    let endOfMonth = (function (paraYear, paraMonth) {
		let tempEndDate = new Date(paraYear, paraMonth, 0);
		return tempEndDate.getDate();
	})(year,Number(month) + Number(monthTerm));
```

入力された○ヶ月後(ヶ月前)の数字を、最初に取得した月の変数に加算する。
その後、年はsetFullYearを、月はsetMonthを、日はsetDateを使って算出する日付を作成する。
JavaScriptでは、Monthは0から始まる事に注意。

```js:
setMonth(0)   // 1月を取得
setMonth(11)  // 12月を取得
```

#月末の処理に注意

ここで一つ問題です。

```js:
let date = new Date();
date.setFullYear(2020);
date.setMonth(3);
date.setDate(1);
console.log(date);
```
このコードはいつの日付を返すでしょうか。

ここで2020年4月1日と直ぐに答えた人は要注意です。
正解は本日の日付が1日〜30日の場合は2020年4月1日で、31日の場合は2020年5月1日となります。

##どうしてか
JavaScriptにてnew Date()をした際は、現在日付が取得されます。
そのため、1月10日に上のコードを実行すると

```js:1月10日に実行
let date = new Date();        // dateは2020/1/10
date.setFullYear(2020);       // dateは2020/1/10
date.setMonth(3);             // dateは2020/4/10
date.setDate(1);              // dateは2020/4/1
console.log(date);            // 2020/4/1を返す
```
となりますが、1月31日に実行すると

```js:1月31日に実行
let date = new Date();        // dateは2020/1/31
date.setFullYear(2020);       // dateは2020/1/31
date.setMonth(3);             // dateは2020/4/31→4/31は存在しないため、繰り上がって5/1となる
date.setDate(1);              // dateは2020/5/1
console.log(date);            // 2020/5/1を返す
```

となります。
知っておくと問題のですが、知らない場合は嵌ってしまうので注意が必要です。

#コード

以下が○ヶ月後、○ヶ月前を取得するコードになります。
ライブラリーは各自で取得するか、下のGithubのコードを取得してください。
[Github](https://github.com/AinouIKnow/calDatejs)

```html:index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>日付計算</title>

  <link rel="stylesheet" href="css/default.css">
  <link rel="stylesheet" href="css/default.date.css">

  <script src="Scripts/jquery-3.4.1.min.js"></script>
  <script src="Scripts/picker.js"></script>
  <script src="Scripts/picker.date.js"></script>
  <script src="Scripts/main.js"></script>
  <script src="Scripts/lang-ja.js"></script> <!-- 日本語化 -->

</head>

<body>
  <section class="section">
    <div class="section__block section__block--scoped">
      <h3>日付計算</h3>
      <fieldset class="fieldset fieldset--demo">
        <div class="fieldset__wrapper">
          <label>日付</label></br>
          <input id='inputDate' class="fieldset__input js__datepicker" type="text" placeholder="Try me&hellip;"></br></br>
          <label>求めたい未来(過去)</label></br>
          <input type="number" id="inputMonth" min="0"/>
          <form name="form1">
            <input name="manth" id="next" type="radio" checked> ヶ月後
            <input name="manth" id="pre" type="radio"> ヶ月前
          </form>
          <button id="submit" class="fieldset__button button button--small" onclick="onClickSubmit();">算出する</button>
        </div>
        <div id="result"></div> <!-- ボタン押下の結果を表示する -->
      </fieldset>

    </div>
  </section>
</body>

</html>
```


```js:main.js


$(function() {
    //datepicker表示イベント
    $('#inputDate').pickadate();
});

//算出ボタン押下処理
function onClickSubmit(){

	let date = $('#inputDate').val();

	// 日付が入力されていない場合は終了
	if(date==''){
		return;
	}

	// 結果出力部分を空白に
	$('#submit_result').remove();
    
	// 年、月、日をそれぞれ算出
    let year = date.substring(0,4); 
    let month = date.substring(5,7);
    let day = date.substring(8,10);
	
    let checkNext =  document.form1.next.checked;	// ヶ月後の方にチェックが入っているか
	let monthTerm = 0								// 入力したMonthの期間


	// ヶ月後を選択の際はその値を、ヶ月前を選択の場合は-1をかけたものを月の期間にセットする
    if(checkNext){
		monthTerm = $('#inputMonth').val();
    } else {
		monthTerm = $('#inputMonth').val() * -1;
	}

	// 算出する日付の月末の日にちを取得
	let endOfMonth = (function (paraYear, paraMonth) {
		let tempEndDate = new Date(paraYear, paraMonth, 0);
		return tempEndDate.getDate();
	})(year,Number(month) + Number(monthTerm));

	if (day > endOfMonth) {
		day = endOfMonth;
	} else {
		if (checkNext) {
			day = day - 1;
		} else {
			day = Number(day) + 1;
		}
	}

	// 日付の初期化
	let initDate = function () {
		let date = new Date();
		date.setDate(1);
		return date;
	};

	let newDate = initDate();

	newDate.setFullYear(year);
	newDate.setMonth(Number(month) + Number(monthTerm) - 1);
	newDate.setDate(day);
	let reultYear = newDate.getFullYear();
	let reultMonth = ('00' + (newDate.getMonth() + 1)).slice(-2);
	let reultDay = ('00' + newDate.getDate()).slice(-2);

	let result = reultYear + '年' + reultMonth + '月' + reultDay + '日';

    //メッセージ表示
	$('#result').after('<div id="submit_result" class="section__block section__block--notification"><p><strong>'
	+ result
	+ '</strong></p></div>'); 

}
```
#おわりに
意外にもネット上には、この形式の1ヶ月後を取得するサンプルコードがなかったため、自分で作成しました(あくまでも当時の話で、現在はあるのかもしれません)。
間違い、疑問点等がありましたらコメントにてお願いします。

#参考資料
[月末近くの日付でsetMonth()を使うとバグる？ - tukihatuの技術日記]
(https://tukihatu.hatenablog.com/entry/20120531/1338458469)