[GAS][Javascript]指定の年月の「第n d曜日」を配列で取得する

## はじめに
GASでのゴミ出しリマインダ用に「第n d曜日」を計算するアルゴリズムを作りました。
面白かったので、まとめておきます。

考え方としては、

- （１）指定した年月の「d曜日」をすべて配列で取得する
- （２）その配列から「第n d曜日」だけを抜き出して配列で取得する

という順番で実装していきます。
特にDate型の扱いと、配列メソッドの使い方が勉強になりました。
配列メソッドは、配列に要素として含まれているかどうかをブール値で返す`includes()`メソッドと、配列の要素のうち引数に指定した関数をみたすものだけを配列として取り出す`filter()`メソッドあたりの理解がミソになるかと思います。

このあたりの練習問題としても、やってみたい方は是非おすすめです！

## （１）指定した年月の「d曜日」をすべて配列で取得する

まずは、指定した年月の指定した曜日の一覧を配列で取得してみたいと思います。
例えば「今月の水曜日」と指定してやると、2020年12月の水曜日に該当する

```
第1水曜 2020/12/2
第2水曜 2020/12/9
第3水曜 2020/12/16
第4水曜 2020/12/23
第5水曜 2020/12/30
```

これらをDate型の配列という形で作ってみよう、という訳です

「`date`変数 = 今日の日付（Date型）」と「`day`変数 = 取得したい曜日（数値）」を指定して、目的の配列を作ってみたいと思います。

GAS（Javascript）のDate型では、0が日曜日、1が月曜日…、6が土曜日に対応しています。

`const WEEKDAYS = ['日', '月', '火', '水', '木', '金', '土'];`
今回は必要ありませんが、こんな定数を用意しておくと、今後なんか活用できる機会がありそうです。
「あれ、0は日曜だっけ？月曜だっけ？」という備忘のためにスクリプト内に記述しておいても良いかもしれません。

今回は「水曜日」が欲しいので、`day = 3`としてみます。

スクリプトは以下の通りです。

```javascript
function mainFunction() {
  const date = new Date(); //今日の日付
  const day = 3; //水曜日

  const year = date.getFullYear();
  const month = date.getMonth();
  
  const days = [];

  for (let i = 1; i <= 31; i++){
    const tmpDate = new Date(year, month, i);
    
    if (month !== tmpDate.getMonth()) break; //月代わりで処理終了
    if (tmpDate.getDay() !== day) continue; //引数に指定した曜日以外の時は何もしない
    days.push(tmpDate);
  }

  console.log(days);
}
```

上の`getDays()`関数を実行してログを確認してみると、「2020年12月の水曜日」を配列で得ることができます。
```
[ Wed Dec 02 2020 00:00:00 GMT+0900 (日本標準時),
  Wed Dec 09 2020 00:00:00 GMT+0900 (日本標準時),
  Wed Dec 16 2020 00:00:00 GMT+0900 (日本標準時),
  Wed Dec 23 2020 00:00:00 GMT+0900 (日本標準時),
  Wed Dec 30 2020 00:00:00 GMT+0900 (日本標準時) ]
```
<br>
  
こちらの記事を参考にしています。
[etau the non programmer coder  — GAS特定の月の特定の曜日を取得する](https://etauthenonprogrammercoder.tumblr.com/post/186638513489/gas%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9C%88%E3%81%AE%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9B%9C%E6%97%A5%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)


### 関数に切り分ける
こんな感じで`getDays()`関数に切り分けて、再利用しやすいようにしてみました。
`mainFunction()`内で日付と曜日を指定して実行すると、同様の結果が得られます。
 
```javascript
function mainFunction(){

  const date = new Date();
  const day = 3; //水曜日

  const days = getDays(date, day);
  console.log(days);
}


/*
*その年月のd曜日を取得する関数
* 
* @param {Date} date - 調べたい年月の日付（Date型）
* @param {number} day - 取得したい曜日を表す数値(0:日曜日〜6:土曜日)
* @return {days} ある年月のday曜日の日付の入った配列
*/
function getDays(date, day) {
  const year = date.getFullYear();
  const month = date.getMonth();
  
  const days = [];

  for (let i = 1; i <= 31; i++){
    const tmpDate = new Date(year, month, i);
    
    if (month !== tmpDate.getMonth()) break; //月代わりで処理終了
    if (tmpDate.getDay() !== day) continue; //引数に指定した曜日以外の時は何もしない
    days.push(tmpDate);
  }

  return days;
}
```


## （２）その配列から「第n d曜日」だけを抜き出して配列で取得する

いくつかアプローチが考えられます。

・先ほど作った「d曜日」を返す`getDays()`関数を加工して「第n d曜日」の配列を返す関数を作る
・メイン関数`mainFunction()`の中で配列`days`を加工して「第n d曜日」の配列を作る

後者のやり方でシンプルにできます。
新たに変数`weeks`を定義して、配列で欲しい週を指定してあげます。
（隔週に限らず、欲しい番号を指定してみましょう）

```javascript
function mainFunction2(){
  
  const date = new Date();
  const day = 3; //水曜日
  const weeks = [2, 4]; //第2週目、第4週目 を指定


  const days = getDays(date, day);
  const specifiedDays = days.filter((v, i) => weeks.includes(i+1));
  console.log(specifiedDays);
  
}
```

スクリプトの中身については、後ほど文法の説明とともに（ちゃんと説明できてるかどうかわからない）解説を載せておきます。
この関数を実行してログを表示すると、指定した第２・第４水曜日だけの配列ができています。やったね！

```
[ Wed Dec 09 2020 00:00:00 GMT+0900 (日本標準時),
  Wed Dec 23 2020 00:00:00 GMT+0900 (日本標準時) ]
```

### 備考
`weeks`に0, 10などのあり得ない数値を入れてやっても、特に不具合が出ることなく、該当する数値のみをとった配列が出来上がるようです。
これを利用すると、例えば`weeks = [1, 3, 5]`などと指定しておけば、5週目のあるなしに関わらず欲しい配列が手に入りますね。

また、この「第n d曜日」を配列にする処理も他の関数に切り分けて作ってはみましたが、あまりその関数を頻繁に使う機会が思い浮かびませんでした。。。
なので、今回作ってみたスクリプトとしてはここまで。

### 解説：配列のincludes()とfilter()メソッドの活用
GAS（Javascript）の文法について。知っていたら飛ばしてください。

ポイントはこの１行。

```javascript
const specifiedDays = days.filter((v, i) => weeks.includes(i+1));
```

ちょっとよくわかりませんね。

先に、今回使う配列のメソッドの最低限の使い方だけ整理しておきます。

|  メソッド  |  説明  |
| ---- | ---- | 
|  arr.filter(<関数>)  | 配列arrの要素のうち、引数に指定した<関数>をみたすものだけを抽出した配列として返す |
|  arr.includes(<要素>)  | 引数に指定した<要素>が配列arrに含まれているかどうかをブール値で返す |


さて...先ほどの１行は、全体の構文としては
`const specifiedDays = days.filter(なんかの関数);`

という構造になっています。
この構造だけみると実はシンプルで、`specifiedDays`という配列に、先ほど作った指定した曜日が全部入った配列`days`から`filter()`メソッドで「なんらかの処理」をして要素を抽出した配列を返していることになります。

この「なんらかの処理」というのが、`filter()`メソッドの引数に指定した関数です。

`(v, i) => weeks.includes(i+1)`

こいつは「アロー関数」という関数リテラルの書き方ですね。
矢印の左側が仮引数、右側が具体的な処理です。
（仮引数v, iはvalue, indexに相当します）

ざっくり「`weeks`で指定した数値がインデックス担っているものだけ、`days`から要素を抽出している」というような処理になっています。

今回の場合はこれにより第2、第4曜日だけを`days`から抽出して、`specifiedDays`を作っています。



## おわりに
実用的かつ気になっていた配列のメソッドを扱う、とても良い練習になりました。
メソッドの解説のあたりはだいぶ飛ばしてしまっているので、もう少しうまく説明できないかな…。思いついたら修正してみます。

### MEMO

参考記事
[etau the non programmer coder  — GAS特定の月の特定の曜日を取得する](https://etauthenonprogrammercoder.tumblr.com/post/186638513489/gas%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9C%88%E3%81%AE%E7%89%B9%E5%AE%9A%E3%81%AE%E6%9B%9C%E6%97%A5%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)

参考書籍（pp202-213　配列を取り扱う - Arrayオブジェクト）
[Amazon.co.jp： 詳解! Google Apps Script完全入門第2版 ~GoogleアプリケーションとGoogle Workspaceの最新プログラミングガイド: 高橋宣成: 本](https://www.amazon.co.jp/gp/product/4798063339/ref=dbs_a_def_rwt_hsch_vapi_taft_p1_i4)