JavaScriptでプレミアムフライデーやハッピーマンデーを調べる

月末の金曜日に決まった[プレミアムフライデー](https://premium-friday.go.jp/)は、あまりよい評判が聞こえません。[ハッピーマンデー](https://ja.wikipedia.org/wiki/ハッピーマンデー制度)は、月曜日に移された国民の祝日です。何年何月の何番目の何曜日という引数から、当てはまる日付が返るJavaScriptの関数(getNthDay())を定めてみましょう。

```
getNthDay(年, 月, 序数, 曜日)
```

# 01 月初と月末のDateオブジェクトを得る
たとえば、2017年3月の月初の`Date`オブジェクトは、つぎのJavaScriptコードで得られます。月は0からはじまる連番整数であることに注意しましょう。

```js
var date = new Date(2017, 2, 1);
```

では、その前の2月の月末はどうでしょう。実は、[`Date()`コンストラクタ](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date)の日に0を与えると、前月末のオブジェクトがつくられるのです(MDN「Date.prototype.setDate()」の「[説明](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate#Description)」参照)。

```js
var date = new Date(2017, 2, 0);
console.log(date);  // Tue Feb 28 2017 00:00:00 GMT+0900 (JST)
```

# 02 年・月・序数・曜日から日付を返す関数
年・月・序数・曜日の4つの引数から日付を返す関数(getNthDay())は、つぎのように定められます。何番目という序数は、曜日と同じく0からはじめます。3月のプレミアムフライデー(第5金曜日)は、年度末の31日でした。

```js
function getNthDay(year, month, ordinal, day) {
	var date;
	date = new Date(year, month - 1, 1);
	day = (day - date.getDay() + 7) % 7 + ordinal * 7;  // 日を導く
	date.setDate(date.getDate() + day);
	return date;
}
var date = getNthDay(2017, 3, 4, 5);
console.log(date.toString());  // Fri Mar 31 2017 00:00:00 GMT+0900 (JST)
```

関数の考え方はつぎのとおりです。[剰余演算子`%`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#剰余_())を用いた1行の式で、日を導いています。

1. 月初の曜日を調べる
2. その月の求めたい曜日のはじめ(第1)の日付を知る
3. 序数にもとづいて7の倍数を加える

# 03 序数を月末から数えられるようにする
プレミアムフライデーは月末です。すると、序数を月末からさかのぼって数えられるようにした方が便利でしょう。この場合、引数を負にします。月初と月末で、数えはじめと序数が負という違いはあるものの、前項と考え方は同じです。

```js
function getNthDay(year, month, ordinal, day) {
	var date;
	if (ordinal > -1) {
		date = new Date(year, month - 1, 1);
		day = (day - date.getDay() + 7) % 7 + ordinal * 7;
	} else {
		date = new Date(year, month, 0);
		day = (day - date.getDay() - 7) % 7 + (ordinal + 1) * 7;
	}
	date.setDate(date.getDate() + day);
	return date;
}
var date = getNthDay(2017, 3, -1, 5);
console.log(date.toString());  // Fri Mar 31 2017 00:00:00 GMT+0900 (JST)
```

# 04 月を超えないようにする
仕上げに、序数で月を超えないようにします。たとえば、3月に第6金曜日はありません。そのの場合には、つぎのコード001は`null`を返すこととしました。併せて、[TypeScriptの構文](http://www.typescriptlang.org/play/index.html#src=function%20getNthDay(year%3A%20number%2C%20month%3A%20number%2C%20ordinal%3A%20number%20%3D%200%2C%20day%3A%20number%20%3D%200)%3A%20Date%20%7B%0D%0A%09let%20date%3A%20Date%3B%0D%0A%09let%20thisMonth%3A%20number%3B%0D%0A%09if%20(ordinal%20%3E%20-1)%20%7B%0D%0A%09%09date%20%3D%20new%20Date(year%2C%20month%20-%201%2C%201)%3B%0D%0A%09%09thisMonth%20%3D%20date.getMonth()%3B%0D%0A%09%09day%20%3D%20(day%20-%20date.getDay()%20%2B%207)%20%25%207%20%2B%20ordinal%20*%207%3B%0D%0A%09%7D%20else%20%7B%0D%0A%09%09date%20%3D%20new%20Date(year%2C%20month%2C%200)%3B%0D%0A%09%09thisMonth%20%3D%20date.getMonth()%3B%0D%0A%09%09day%20%3D%20(day%20-%20date.getDay()%20-%207)%20%25%207%20%2B%20(ordinal%20%2B%201)%20*%207%3B%0D%0A%09%7D%0D%0A%09date.setDate(date.getDate()%20%2B%20day)%3B%0D%0A%09if%20(thisMonth%20!%3D%3D%20date.getMonth())%20%7B%0D%0A%09%09return%20null%3B%0D%0A%09%7D%0D%0A%09return%20date%3B%0D%0A%7D%0D%0Alet%20date%3A%20Date%20%3D%20getNthDay(2017%2C%203%2C%20-1%2C%205)%3B%0D%0Aconsole.log(date%20%26%26%20date.toString())%3B)も掲げておきましょう。

### コード001
```js
function getNthDay(year, month, ordinal, day) {
	var date;
	var thisMonth;
	if (ordinal > -1) {
		date = new Date(year, month - 1, 1);
		thisMonth = date.getMonth();
		day = (day - date.getDay() + 7) % 7 + ordinal * 7;
	} else {
		date = new Date(year, month, 0);
		thisMonth = date.getMonth();
		day = (day - date.getDay() - 7) % 7 + (ordinal + 1) * 7;
	}
	date.setDate(date.getDate() + day);
	if (thisMonth !== date.getMonth()) {
		return null;
	}
	return date;
}
```