2020-12-12に更新

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

はじめに

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

考え方としては、

  • (1)指定した年月の「d曜日」をすべて配列で取得する
  • (2)その配列から「第n d曜日」だけを抜き出して配列で取得する

という順番で実装していきます。

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

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

(1)指定した年月の「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としてみます。

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

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 (日本標準時) ]

こちらの記事を全力でパクらせていただきましたw
etau the non programmer coder — GAS特定の月の特定の曜日を取得する

関数に切り分ける

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

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;
}

(2)その配列から「第n d曜日」だけを抜き出して配列で取得する

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

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

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

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

}

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

[ 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)の文法について。知っていたら飛ばしてください。

ポイントはこの1行。

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

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

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

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

さて...先ほどの1行は、全体の構文としては
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特定の月の特定の曜日を取得する

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

ツイッターでシェア
みんなに共有、忘れないようにメモ

Massa

北海道でアプリ制作に取り組んでるノンプログラマな農夫。仕事や日常生活で感じる小さな不便を解消すべく趣味と実益を兼ねて遊んでます ■Python・GAS + LINE bot

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント