4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-29

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

#概要
一か月後の定義は民法第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有)

#主な実装方法

画面イメージは実際に作ったデモページを参考にして下さい。
デモページ

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

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』と『月末の日付』を比較して、月末の日付を超えないように正しい日付を取得する。
月末は以下のようにして取得した。

    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から始まる事に注意。

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

#月末の処理に注意

ここで一つ問題です。

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日に上のコードを実行すると

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日に実行すると

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

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>
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)

4
6
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
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?