javascriptでありえない日付をselectで選択させない制御

#はじめに
セレクトボックスで年月日を選択させるときに、2月31日とかありえない日が選べると
受け取るサーバー側では非常に都合悪いです。

そこでjavascriptを使ってありえない日付を選択できないように制御します。

#使い方
まず、月ごとに日付を返す関数を用意します。
うるう年も計算する必要があります。

```js
function monthday(year,month){
	var lastday = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){
		lastday[2] = 29;
	}
	return lastday[month];
}
```

形で下記のsetDay()という関数を用意します。
この関数の中では以前説明した[ゼロパディングの関数](http://qiita.com/terrym/items/6257f6507ca19f00cdf3)も呼ばれています。

```js
function setDay(){
	var year 	= $('#year').val();
	var month	= $('#month').val();
	var day		= $('#day').val();
	var lastday	= monthday(year, month);
	var option = '';
	for (var i = 1; i <= lastday; i++) {
		if (i == day){
			option += '<option value="' + zeroPadding(i,2) + '" selected="selected">' + zeroPadding(i,2) + '</option>\n';
		}else{
			option += '<option value="' + zeroPadding(i,2) + '">' + zeroPadding(i,2) + '</option>\n';
		}
	}
	$('#day').html(option);
}
```

そして、htmlではselect boxに”year”,”month”,”day”というIDを各々つけます。

```html
<select id="year" style="width: 80px;" name="year"></select>
<span style="margin-left: 3px; margin-right: 5px; display: inline-block;">年</span>

<select id="month" style="width: 50px;" name="month"></select>
<span style="margin-left: 3px; margin-right: 5px; display: inline-block;">月</span>

<select id="day" style="width: 50px;" name="day"></select>
<span style="margin-left: 3px; margin-right: 5px; display: inline-block;">日</span>

<input type="hidden" name="yyyymmdd" id="yyyymmdd" />
```

そしてこの関数 setDay() が、年と月が変わったタイミングで呼ばれるようにすればOKです。

```js

$('#year').change(function(){
	setDay();
});
$('#month').change(function(){
	setDay();
});
```

#応用編でselectの中身もjsで作る
ちなみに上のhtmlを見るとわかるんですが、selectタグの中身がありません。

よくやるのはこのようにselectの中身は空にして、jsで要素を作って入れてます。

このときには setDate()という関数を作って、何年から現在までのoptionを選択できるかを決めてます。
※　引数なしで呼び出された場合は、現在から30年前まで選択可能

この関数をカスタマイズすれば、何年から何年までを選択肢とさせるなどいろいろなことができます。
※　下記の例では現在から1950年まで

```js

setDate(1950); // 関数の呼び出し

function setDate(startyear, endyear){
	var date = new Date();
	var nowyear = date.getFullYear();
	var month = 1;
	var day	= 1;
	var yearOptions = '';
	var monthOptions = '';
	var dayOptions = '';
	if(!startyear || startyear == ""){
		startyear = nowyear - 30;
	}
	if(!endyear || endyear == ""){
		endyear = nowyear;
	}
	if(startyear < endyear){
		return;
	}
	for (var i=startyear; i <= endyear; i++){
		if(i == nowyear){
			yearOptions += '<option value="' + i + '" selected="selected">' + i + '</option>';
		}else{
			yearOptions += '<option value="' + i +'">' + i + '</option>';
		}
	}
	for (i=1; i<=12; i++){
		if(month == i){
			monthOptions += '<option value="' + zeroPadding(i,2) + '" selected="selected">' + zeroPadding(i,2) + '</option>';
		}else{
			monthOptions += '<option value="' + zeroPadding(i,2) +'">' + zeroPadding(i,2) + '</option>';
		}
	}
	for (i=1; i<=31; i++){
		if(day == i){
			dayOptions += '<option value="' + zeroPadding(i,2) + '" selected="selected">' + zeroPadding(i,2) + '</option>';
		}else{
			dayOptions += '<option value="' + zeroPadding(i,2) +'">' + zeroPadding(i,2) + '</option>';
		}
	}
	$('#year').html(yearOptions);
	$('#month').html(monthOptions);
	$('#day').html(dayOptions);
}
```

これで、システム的にはご法度の、ありえない日付はサーバー側に送られなくなります。




#さらに応用編でselectの中身もjsで作り、すでにsetされている場合はselectedにする。



うちの会社の意識高い若者から質問があったので追記します。

仮に誕生日が1990年1月1日のユーザーがいるとしましょう。
それで、このユーザーのプロフィール変更画面等で使うことを想定します。

```html:sample.html
<form id="birthdayForm" method="post" accept-charset="utf-8">
	<div class="form-group">
		<label for="" class="col-sm-2 control-label">生年月日</label>
		<div class="form-content col-sm-10">
			<select class="form-control setDay num4" id="year"></select> 年 
			<select class="form-control setDay num2" id="month"></select> 月 
			<select class="form-control setDay num2" id="day"></select> 日 
			<input type="hidden" id="birthday" value="1990-01-01" />
		</div>
	</div>
	<button type="submit" class="btn btn-default" id="submitBtn">生年月日変更</button>
</form>
```

```js:sample.js

/***************************
 * main flow
 ***************************/
$(function(){
	var birthday = $('#birthday').val();
	if(birthday == '' || !birthday){ 
		birthday = '1980-01-01'; // 誕生日が入っていない場合はデフォルト値をセット
	}
	// 基本的にシステムでセットされるけど値チェック
	if(birthday.length != 10 || !birthday.match(/^\d{4}-\d{2}-\d{2}/){
		alert('生年月日が不正です。');
	}else{
		var date = new Date();
		var nowyear = date.getFullYear();
		var startyear = 1900;
		var endyear = nowyear - 18; // 本サービスは18歳未満は登録禁止としたらこう書く
		setDate(startyear, endyear, birthday); // 日付のセット
	}
	
	/***************************
	 * trigger
	 ***************************/
	$('#year','#month').change(function(){
		setDay();
	});
	$('#submitBtn').click(function(e){
		e.preventDefault();
		var year = $('#year').val();
		var month = $('#month').val();
		var day	= $('#day').val();
		var birthday = year + '-' + month + '-' + day;
		if(birthday.length != 10 || !birthday.match(/^\d{4}-\d{2}-\d{2}/){
			alert('生年月日が不正です。');
			return;
		}
		$('#birthday').val(birthday);
		$('#birthdayForm').submit();
	});

});

/***************************
 * function
 ***************************/

function setDay(){
	var year 	= $('#year').val();
	var month	= $('#month').val();
	var day		= $('#day').val();
	var lastday	= monthday(year, month);
	var option = '';
	for (var i = 1; i <= lastday; i++) {
		if (i == day){
			option += '<option value="' + zeroPadding(i,2) + '" selected="selected">' + zeroPadding(i,2) + '</option>\n';
		}else{
			option += '<option value="' + zeroPadding(i,2) + '">' + zeroPadding(i,2) + '</option>\n';
		}
	}
	$('#day').html(option);
}

function monthday(year,month){
	var lastday = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){
		lastday[2] = 29;
	}
	return lastday[month];
}

function zeroPadding(num,length){
	return ('0000000000' + num).slice(-length);
}

function setDate(startyear, endyear, date){
	var dateArray = date.split('-');
	var selectedYear = dateArray[0];
	var selectedMonth = dateArray[1];
	var selectedDay = dateArray[2];
	var date = new Date();
	var nowyear = date.getFullYear();
	var yearOptions = '';
	var monthOptions = '';
	var dayOptions = '';
	if(!startyear || startyear == ""){
		startyear = nowyear - 30;
	}
	if(!endyear || endyear == ""){
		endyear = nowyear;
	}
	if(startyear < endyear){
		return;
	}
	for (var i = startyear; i <= endyear; i++){
		if(i == parseInt(selectedYear)){ // 0でのパディングを数値に変換
			yearOptions += '<option value="' + i + '" selected="selected">' + i + '</option>';
		}else{
			yearOptions += '<option value="' + i +'">' + i + '</option>';
		}
	}
	for (var j=1; j <= 12; j++){
		if(j == parseInt(selectedMonth)){ // 0でのパディングを数値に変換
			monthOptions += '<option value="' + zeroPadding(j,2) + '" selected="selected">' + zeroPadding(j,2) + '</option>';
		}else{
			monthOptions += '<option value="' + zeroPadding(j,2) +'">' + zeroPadding(j,2) + '</option>';
		}
	}
	for (var k = 1; k <= 31; k++){
		if(k == parseInt(selectedDay)){ // 0でのパディングを数値に変換
			dayOptions += '<option value="' + zeroPadding(k,2) + '" selected="selected">' + zeroPadding(k,2) + '</option>';
		}else{
			dayOptions += '<option value="' + zeroPadding(k,2) +'">' + zeroPadding(k,2) + '</option>';
		}
	}
	$('#year').html(yearOptions);
	$('#month').html(monthOptions);
	$('#day').html(dayOptions);
}

```

ご参考まで。

#変更履歴

2015/02/19 意識高い若者に対応