【GAS】手続き型関数をクラス化する~isWorkDay関数を例にして~

2022-04-20

当サイトではアフィリエイト広告を利用して商品を紹介しています。

元記事

[GAS]日付をincludesしたら意図した判定にならなくて困った。日付はいつもこれだよ。typeofしたり、そもそもクラスの理解が甘いし、つうかカレンダでどうにかするとかいろいろあって分からんくなっ|good-sun(a03)|note

経緯:ノンプロ研のアウトプットクイーンのa03さんのNoteより。

それクラス化したら楽だよー!という話をしたので、自分でもチャレンジとアウトプットをトライ

元のコード

/**

 * 参考
 * <https://dev.classmethod.jp/articles/202001-workday-only-gas/>
 */

// 指定された日が営業日か(営業日 = 「土日でない」「祝日カレンダーに予定がない」)
// 営業日 = true
function isWorkday(targetDate = new Date('2022/12/29')) {

  console.log({ targetDate });

  // targetDate の曜日を確認、週末は休む (false)
  const rest_or_work = ["REST", "mon", "tue", "wed", "thu", "fri", "REST"]; // 日〜土
  if (rest_or_work[targetDate.getDay()] == "REST") {
    console.log(`今日は 土日ですよ`);
    return false;
  };

  // 祝日カレンダーを確認する
  const calJpHolidayUrl = "ja.japanese#holiday@group.v.calendar.google.com";
  const calJpHoliday = CalendarApp.getCalendarById(calJpHolidayUrl);
  if (calJpHoliday.getEventsForDay(targetDate).length != 0) {
    // その日に予定がなにか入っている = 祝祭日 = 営業日じゃない (false)
    console.log(`今日は 祝日ですよ`);
    return false;
  };

  //会社指定の休日(上記の祝日カレンダには入っていない休日、休暇)スプレッドシートから取得
  const winterHoliday1 = dateMasterSheet.getRange('D31').getValue();   //12/29
  const winterHoliday2 = dateMasterSheet.getRange('D32').getValue();   //12/30
  const winterHoliday3 = dateMasterSheet.getRange('D33').getValue();   //12/31

  console.log({ winterHoliday1 });

  const winterHolidays = [winterHoliday1, winterHoliday2, winterHoliday3];

   console.log({ winterHolidays });

  //winterHolidays に targetDate が含まれていたら true
  console.log(winterHolidays.includes(targetDate));

  if (winterHolidays.includes(targetDate) == true) {
    //targetDate が冬季休暇日だったらfalse
    console.log(`今日は 冬季休日ですよ`);
    return false;
  }

  // 全て当てはまらなければ営業日 (True)
  console.log(`今日は 営業日ですよ`);
  return true;
}

とても長い。コードを読むとisWorkdayの判定するまでにはいくつもプロセスがあることがわかります。

コメントだけ取り出すと

  • // targetDate の曜日を確認、週末は休む (false)
  • // 祝日カレンダーを確認する
  • //会社指定の休日(上記の祝日カレンダには入っていない休日、休暇)スプレッドシートから取得
  • //winterHolidays に targetDate が含まれていたら true
  • // 全て当てはまらなければ営業日 (True)

この5つの処理がまとまってisWorkdayを判定していることがわかります。

慣れていればこのコードから一度にclass化して各処理をメソッドとして書き出すこともできますが、ここではステップを踏んでまず、処理を関数化して見ることにします。

関数として分けたのがこちら

関数化したコード

/**

 * 参考
 * <https://dev.classmethod.jp/articles/202001-workday-only-gas/>
 */

/**
 * 日付が営業日かどうかを判定します。(trueなら営業日)
 * @param {Date} Date - 
 * @return {boolean} boolean - 
 */
function isWorkdayV2(targetDate = new Date('2022/12/29')) {

  console.log({ targetDate });

  // targetDate の曜日を確認、週末は休む (true)
  if (isRestDay(targetDate) === true) return

  // 祝日カレンダーを確認する
  if (isJapaneseHoliday(targetDate) === true) return

  //会社指定の休日(上記の祝日カレンダには入っていない休日、休暇)スプレッドシートから取得
  if (isCompanyHoliday(targetDate) === true) return

  // 全て当てはまらなければ営業日 (True)
  console.log(`今日は 営業日ですよ`);
  return true;
}

/**
 *  targetDate の曜日を確認、週末は休む (true)
 * @param {Date} Date - 
 * @return {boolean} boolean - 
 */
function isRestDay(targetDate) {
  const rest_or_work = ["REST", "mon", "tue", "wed", "thu", "fri", "REST"]; // 日〜土
  if (rest_or_work[targetDate.getDay()] == "REST") {
    console.log(`今日は 土日ですよ`);
    return true;
  } else {
    return false;
  }
}

/**
 * 対象の日付が祝日カレンダーを確認して祝日かどうかを判定します。(祝日ならtrue)
 * ※Googleが公開しているカレンダーに依存
 * ja.japanese#holiday@group.v.calendar.google.com
 * 
 * @param {Date} targetDate - 
 * @return {boolean} boolean - 
 */
function isJapaneseHoliday(targetDate) {
  const calJpHolidayUrl = "ja.japanese#holiday@group.v.calendar.google.com";
  const calJpHoliday = CalendarApp.getCalendarById(calJpHolidayUrl);
  if (calJpHoliday.getEventsForDay(targetDate).length != 0) {
    // その日に予定がなにか入っている = 祝祭日 = 営業日じゃない (false)
    console.log(`今日は 祝日ですよ`);
    return true;
  } else {
    return false;
  }
}

/**
 * 会社独自の休みかを判定します。(休みならtrue)
 * ※日付の判定が正しく動作しませんがここでは触れません。
 * @param {Date} targetDate - 
 * @return {boolean} boolean - 
 */
function isCompanyHoliday(targetDate,dateMasterSheet = SpreadsheetApp.getActiveSpreadsheet()) {

  const winterHolidays = getCompanyHoliday_(dateMasterSheet)

  console.log({ winterHolidays });

  //winterHolidays に targetDate が含まれていたら true
  console.log(winterHolidays.includes(targetDate));

  if (winterHolidays.includes(targetDate) == true) {
    //targetDate が冬季休暇日だったらfalse
    console.log(`今日は 冬季休日ですよ`);
    return true;
  }

}

/**
 * 会社独自の休み一覧を取得します。
 * 
 * @return {array.<Date>} winterHolidays - 
 */
function getCompanyHoliday_(dateMasterSheet = SpreadsheetApp.getActiveSpreadsheet()) {

  const winterHoliday1 = dateMasterSheet.getRange('D31').getValue();   //12/29
  const winterHoliday2 = dateMasterSheet.getRange('D32').getValue();   //12/30
  const winterHoliday3 = dateMasterSheet.getRange('D33').getValue();   //12/31

  console.log({ winterHoliday1 });

  const winterHolidays = [winterHoliday1, winterHoliday2, winterHoliday3];
  return winterHolidays

}

全体としてはコードの量が多くなりましたが、全体が何をやっているのかがわかりやすくなり、修正が必要なときにどこを修正すれば良いのかがわかりやすくなったかと思います。

メインのコードで言えば下記のようになりスッキリしたのがわかります。

/**
 * 日付が営業日かどうかを判定します。(trueなら営業日)
 * @param {Date} Date - 
 * @return {boolean} boolean - 
 */
function isWorkdayV2(targetDate = new Date('2022/12/29')) {

  console.log({ targetDate });

  // targetDate の曜日を確認、週末は休む (true)
  if (isRestDay(targetDate) === true) return

  // 祝日カレンダーを確認する
  if (isJapaneseHoliday(targetDate) === true) return

  //会社指定の休日(上記の祝日カレンダには入っていない休日、休暇)スプレッドシートから取得
  if (isCompanyHoliday(targetDate) === true) return

  // 全て当てはまらなければ営業日 (True)
  console.log(`今日は 営業日ですよ`);
  return true;
}

ではこれを更にclassにしてみましょう。

class全体のコード

/**
 * 日付に関するクラス
 */
class DayClass {
  /**
   * @param {Date} targetDate - 日付
   * @param {object} dateMasterSheet - シート
   */
  constructor(targetDate = new Date(),dateMasterSheet = SpreadsheetApp.getActiveSpreadsheet()) {
    this.targetDate = targetDate
    this.dateMasterSheet = dateMasterSheet
  }
  /**
   * 日付が営業日かどうかを判定します。(trueなら営業日)
   * @param {Date} Date - 
   * @return {boolean} boolean - 
   */
  isWorkday(targetDate = this.targetDate) {

    console.log({ targetDate });

    // targetDate の曜日を確認、週末は休む (true)
    if (this.isRestDay(targetDate) === true) return

    // 祝日カレンダーを確認する
    if (this.isJapaneseHoliday(targetDate) === true) return

    //会社指定の休日(上記の祝日カレンダには入っていない休日、休暇)スプレッドシートから取得
    if (this.isCompanyHoliday(targetDate) === true) return

    // 全て当てはまらなければ営業日 (True)
    console.log(`今日は 営業日ですよ`);
    return true;
  }

  /**
   *  targetDate の曜日を確認、週末は休む (true)
   * @param {Date} Date - 
   * @return {boolean} boolean - 
   */
  isRestDay(targetDate = this.targetDate) {
    const rest_or_work = ["REST", "mon", "tue", "wed", "thu", "fri", "REST"]; // 日〜土
    if (rest_or_work[targetDate.getDay()] == "REST") {
      console.log(`今日は 土日ですよ`);
      return true;
    } else {
      return false;
    }
  }

  /**
   * 対象の日付が祝日カレンダーを確認して祝日かどうかを判定します。(祝日ならtrue)
   * ※Googleが公開しているカレンダーに依存
   * ja.japanese#holiday@group.v.calendar.google.com
   * 
   * @param {Date} targetDate - 
   * @return {boolean} boolean - 
   */
  isJapaneseHoliday(targetDate = this.targetDate) {
    const calJpHolidayUrl = "ja.japanese#holiday@group.v.calendar.google.com";
    const calJpHoliday = CalendarApp.getCalendarById(calJpHolidayUrl);
    if (calJpHoliday.getEventsForDay(targetDate).length != 0) {
      // その日に予定がなにか入っている = 祝祭日 = 営業日じゃない (false)
      console.log(`今日は 祝日ですよ`);
      return true;
    } else {
      return false;
    }
  }

  /**
   * 会社独自の休みかを判定します。(休みならtrue)
   * ※日付の判定が正しく動作しませんがここでは触れません。
   * @param {Date} targetDate - 
   * @return {boolean} boolean - 
   */
  isCompanyHoliday(targetDate = this.targetDate,dateMasterSheet = this.dateMasterSheet) {

    const winterHolidays = this.getCompanyHoliday_(dateMasterSheet)

    console.log({ winterHolidays });

    //winterHolidays に targetDate が含まれていたら true
    console.log(winterHolidays.includes(targetDate));

    if (winterHolidays.includes(targetDate) == true) {
      //targetDate が冬季休暇日だったらfalse
      console.log(`今日は 冬季休日ですよ`);
      return true;
    }

  }

  /**
   * 会社独自の休み一覧を取得します。
   * 
   * @return {array.<Date>} winterHolidays - 
   */
  getCompanyHoliday_(dateMasterSheet = this.dateMasterSheet) {

    const winterHoliday1 = dateMasterSheet.getRange('D31').getValue();   //12/29
    const winterHoliday2 = dateMasterSheet.getRange('D32').getValue();   //12/30
    const winterHoliday3 = dateMasterSheet.getRange('D33').getValue();   //12/31

    console.log({ winterHoliday1 });

    const winterHolidays = [winterHoliday1, winterHoliday2, winterHoliday3];
    return winterHolidays

  }

}





クラスを呼び出して使うコード

/**
 * 日付が営業日かどうかを判定します
 */
function isWorkdayV3(){
  const targetDate = new Date('2022/12/29')
  const day = new DayClass(targetDate)
  day.isWorkday()
  
}

各関数をクラスの中のメソッドとしてもたせてクラスを作成しました。

呼び出して使うコードはわずか3行になりました。

機能を拡張する時はclassにメソッドを追加すれば良いだけです。

また別の案件時に、例えば会社の独自の休みを知りたい!

というときには

これが、一番はじめのコードを再利用しようと思うと、すべてのコードを読んで必要な箇所を抜き出す作業が必要になります。

この例だと元のコードが50行程度ですが、機能を追加していって大きなコードになるとその作業だけでも大変です。

ですがクラス化してあればisCompanyHolidayメソッドを呼び出すだけでよく、細かいパーツに分かれているため使い回しがし易いです。

なお、GASでよく使うクラスはだいたいetauさんのGitHubにまとまっているので、適宜使うのが開発が早くなって◎

https://github.com/etau/gas-classes

Classを理解するにはまずは使ってみるのが良いと思います。