月の最終営業日にGASを実行する

以前チャットにGASからメッセージを送信する方法をご紹介させて頂いた時に、弊社では月末になると月末処理(経費精算など)の依頼を社員にチャットで連絡していると書きました。普段使っているチャットから毎月チャットで連絡が通知されることで、処理漏れがなくなりました。

トリガーの選択肢に「月末」がない

GASのトリガーで月末にメッセージを送る場合、「月末」という選択肢はないため、日付で選択することになりますが、31日を選択しても、必ずしもその月に31日があるとは限りません

時間主導型の月ベースのタイマーでトリガーを設定する画面

また、例えその月が31日まであったとしても、土日祝日だったら、通知しても意味がありません

月曜日から金曜日かどうかをチェックする

では、そうしたらその月の最終営業日を判定できるのかを説明したいと思います。
まずは、渡された日付が土日祝日ではないか?をチェックする関数を作成します。

function checkWeekday(date) {
  let week = date.getDay()
  // 曜日が土日ではないか確認
  if((week > 0) && (week < 6)){
    //祝日ではないか確認
    let today = Utilities.formatDate(date,"JST", "yyyy-MM-dd")
    let events = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEvents(new Date(today+' 00:00:00'),new Date(today+' 23:59:59'))
    if(events.length == 0){
      return true;
    }
  }
  return false;
}

上記のcheckweekdayは、土日祝日ではない平日の場合に、trueを返し、土日祝日の場合はfalseを返す関数です。
checkWeekdayの引数のdateには、Dateオブジェクトが入ります。

const today = new Date()
checkWeekday(today)

例えば、上記のようにnew Date()とすると、今日の日付がtoday変数に格納されます。
この時、today変数に格納された値は、たとえば、実行した日時が2021年10月16日15時57分12秒だった場合、『Sat Oct 16 2021 15:57:12 GMT+0900 (Japan Standard Time)』という値が入ります。
それをcheckWeekday関数の引数に入れてあげることで、その日が平日かそうでないかが判定できます。

Dateオブジェクトが、 『Sat Oct 16 2021 15:57:12 GMT+0900 (Japan Standard Time)』 という値だとプログラムの中で扱うのが難しいため、そこから年、月、日や曜日などを取得するメソッドがあります。

getFullYear() 年を返す。2021年の場合は、「2021」を返します。
getMonth()月を返す。10月の場合は、「10」を返します。
getDate()日を返す。16日の場合は、「16」を返します。
getDay()曜日を返します。数値で返すので注意が必要です。
「0」が日曜日で、「1」が月曜日・・・「6」が土曜日となります。
getHours()時間を24時間表記で返します。16時の場合は「16」を返します。
getMinutes()分を返します。16分の場合、「16」を返します。
getSeconds()秒を返します。15秒の場合は、「15」を返します。

まずは、土日かどうかをチェックするために、getDay()メソッドを使っています。0より大きく6より小さい数値の場合、月曜日から金曜日だということになりますので、下記のif文で判定しています。

if((week > 0) && (week < 6)){

祝日じゃないかどうかをチェックする

祝日かどうかをチェックするために、Googleカレンダーの「日本の祝日」というカレンダーを読み込みます。
まずは自分のGoogleカレンダーに「日本の祝日」カレンダーを追加してみましょう。
「設定」->「カレンダーを追加」->「関心のあるカレンダーを探す」のメニューから設定できます。

「日本の祝日」を自分のGoogleカレンダーに追加する画面

「日本の祝日」を追加したら、カレンダー画面に戻り、左メニューの「他のカレンダー」に日本の祝日が追加されています。カーソルを日本の祝日という名前の上に移動すると、点が3つ縦に並んだメニューが表示されますので、クリックしてください。

メニューから「設定」を開くと、「カレンダー ID」が表示されています。
日本の祝日のカレンダーIDは、checkWeekday関数の中にも記載されている「ja.japanese#holiday@group.v.calendar.google.com」になります。

CalendarAppクラスのgetCalendarById()メソッドを使うことで、指定したカレンダーIDのカレンダーオブジェクトを取得することができます。

let events = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com').getEvents(new Date(today+' 00:00:00'),new Date(today+' 23:59:59'))

getCalendarById()メソッドの後ろに、getEvents()メソッドを付けています。
これは、取得したカレンダーオブジェクトから指定期間の予定を取得するためのメソッドです。
1行に繋げずに、下記のように2行で書いても問題ありません。

const calendar = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com')
let events = calendar.getEvents(new Date(today+' 00:00:00'),new Date(today+' 23:59:59'))

getEvents()メソッドに指定したtoday変数には、その前の行で指定したフォーマットの日付を格納しています。

let today = Utilities.formatDate(date,"JST", "yyyy-MM-dd")

Dateオブジェクトを”yyyy-MM-dd”というフォーマットに指定しています。
フォーマットの指定ルールについては、下記のページを参照ください。
https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

getEvents()メソッドで、例えば渡された日付が2021年10月16日だった場合、2021-10-16 00:00:00のDateオブジェクトと、 2021-10-16 23:59:59のDateオブジェクトを渡すことで、その日のイベント(予定)名を返してくれます。

もし何も返してこなければ、その日は祝日ではないということになりますので、下記のように指定し、events変数が空だったら、trueを返しています。

if(events.length == 0){
  return true;
}

ここまでが、 渡された日付が土日祝日ではなく平日かどうかをチェックする関数の説明になります。

月末の日を取得する

次に最初に実行される関数checkEndofweekday()を作成します。

function checkEndofweekday() {
  const today = new Date()
  const getumatu = new Date(today.getFullYear(), today.getMonth() + 1, 0)
  //月末の日を取得
  const d_getumatu = getumatu.getDate()
  //今日の日を取得
  const d_today = today.getDate()
  let result = checkWeekday(getumatu)
  if(result){
    //月末が平日=>最終営業日
    if(d_getumatu == d_today){
      //今日が最終営業日 メッセージ配信
      chatworkSend()
    }
  }else{
    //月末は営業日ではない
    if(d_getumatu > d_today){
      //今日が月末ではない
      for(let i = d_getumatu; i >= d_today; i--) {
        let checkdate = new Date(today.getFullYear(), today.getMonth(), i)
        let result = checkWeekday(checkdate)
        if(result){
          //平日
          if(i == d_today){
            //今日が最終営業日 メッセージ配信
            chatworkSend()
          }
          break
        }
      }
    }
  }
}

処理の流れとして、

  1. 今日の日付と、月末の日付を取得する。
  2. 月末が平日かどうかチェック =>月末が平日で、今日が月末ならメッセージを配信
  3. 月末が祝日で、今日が月末ならもう最終営業日を過ぎているので、プログラムは終了
  4. 月末が祝日で、今日が月末ではない場合、月末から今日までを1日ずつ遡って平日かどうかチェックし、最初に平日と判定された日が最終営業日となる。=>判定された最終営業日が今日だった場合は、メッセージを配信

このようになります。

今日のDateオブジェクトを取得するのは、先ほども説明したとおり、下記のように書きます。

 const today = new Date()

では、月末のDateオブジェクトはどのように取得したら良いでしょうか?

const getumatu = new Date(today.getFullYear(), today.getMonth() + 1, 0)

new Date()の引数に年,月,日を入れると、指定した日付のDateオブジェクトを取得することができます。
月末を指定するには、翌月の1日の一日前の日付をしてすれば良いことになります。
まず、翌月を指定するため、『today.getMonth() + 1』のように今日の月に+1を足しています
日の方は、1日の一日前ということで、「0」を指定することで、前の月の月末の日を取得できます

月末のDateオブジェクトが取得できたら、最初に作成したcheckWeekday()関数を使って、平日かどうか調べましょう。

メッセージを配信する関数chatworkSend()は、今回は紹介しません。
過去に紹介したチャットワークにGASからメッセージを送る記事か、SlackにGASからメッセージを送る記事を参考に作成してください。

for(let i = d_getumatu; i >= d_today; i--) {
  let checkdate = new Date(today.getFullYear(), today.getMonth(), i)
  let result = checkWeekday(checkdate)
  if(result){
     //平日
     if(i == d_today){
       //今日が最終営業日 メッセージ配信
       chatworkSend()
     }
     break
   }
}

上記の部分は、処理の流れの④にあたる月末から今日までの日付をチェックする部分です。
たとえば、月末が31日で、今日が28日だったとします。
forの繰り返し処理で、変数iに月末の「31」と代入し、繰り返し条件をiが今日の「28」以下であると指定し、iは、一回処理するごとに-1されるようにfor条件を書いています。
28日が最終営業日で、29日(祝)、30日(土)、31日(日)だった場合、31、30、29、28と処理されて、iが28の時にメッセージが配信されます。
28日が平日で、29日が最終営業日、30日(土)、31日(日)だった場合、31、30、29と処理されて、iが29の時にメッセージが配信されますが、breakを記述することでforの繰り返し処理は止まり、28は処理されません。

トリガーを設定する

作成したGASを実行するトリガーを設定します。
日付ベースのタイマーで、時刻を設定することで、毎日指定した時刻に実行されます。

最終営業日の1日だけ実行させるプログラムを毎日動かすのは如何なものだろうか?と思う方は、月ベースのタイマーで、26日~31日を指定して実行されると良いと思います。月ベースのタイマーの場合は、1日単位で指定するため、6個のトリガーを登録する必要があります。

トリガーで実行させる関数は、checkWeekday()ではなく、 checkEndofweekday()ですので、注意してください。

トリガーの設定方法についてわからない方は、チャットワークにGASからメッセージを送る記事でも紹介していますので、ご確認ください。

以上が月の最終営業日にGASを処理するプログラムの紹介でした。今回ご紹介したやり方ですと、土日祝日は判定できますが、会社独自の休業日が判定されません。会社独自の休業日を指定したカレンダーからGoogleカレンダーに作成して、読み込ませることで対応できますので、ぜひやってみてください。

ソースダウンロード

下記のフォームからソースコードを無料でダウンロードして頂けます。
ダウンロード版のソースには、「月初めの営業日にメッセージを送る」ための関数も追記しています。月初めの営業日と最終営業日の両方にメッセージを送るためには、oshirase_timer()という関数をトリガーに設定してください。

ダウンロードしたテキストファイルの中身をコード.gsにコピー&ペーストして利用してください。