[GAS] 具体事例から学ぶScriptAppによるトリガー生成コードサンプル（２）～毎月月末に実行したい場合（毎日の月末判定不要）

# はじめに
スプレッドシートを始めとしたGoogleのクラウドアプリは、**時間主導型トリガー**（以降、タイムトリガー）を使用して、定型処理を定期的に自動実行させることができます。

タイムトリガーはＵＩ画面から簡単に設定可能ですが、ＵＩにて設定できる項目が限られているため、要望する内容によってはGASの`ScriptApp`を使ってプログラム側からトリガーを生成する必要があります。

本稿では、ＵＩ画面からの設定では対応できない内容について、当方にご相談頂いた内容を元にした事例別の参考コードを不定期連載形式で紹介していきたいと思います。（ネタが溜まってきたら、随時公開予定です）

今回は比較的実装が容易なケースとして、**毎月月末に実行したい場合**のご紹介です。

::: note
**前提条件**
1. 仕様上、実行遅延（タイムラグ）がおよそ数分以内のレベルで発生します。そのため、厳密な時間管理が必要な処理には不向きです。（[公式](https://developers.google.com/apps-script/reference/script/clock-trigger-builder)にて一部メソッドにおいては±15分と明記されています）
2. 同様に、仕様上、エラー通知の頻度をスクリプト側で設定できないため、 **毎日通知を受け取る** がデフォルトとなります。
3. 拙記事ですが、[こちら](https://qiita.com/STSHISHO/items/2985c7b92c80b28f1018)を参考にタイムゾーンの設定が実行環境と合っているか確認してください。
4. サンプルコードでは１日あたりの総実行時間、１ユーザーあたり２０トリガーまで等、GASの制限事項は考慮していません。
:::

::: note
**設置時の共通事項**
1. トリガー生成ロジックは独立した関数として定義し、メイン処理内に組み込みます。組み込み位置は末尾を推奨しておきますが、好みや状況に合わせて適宜ご対応ください。
2. [GASの制限事項](https://developers.google.com/apps-script/guides/services/quotas#current_quotas)を考慮し、適宜メイン処理の軽量化、高速化をしましょう。また、実行回数に関係なく、実行済みトリガーの削除処理の実装を推奨します。
3. 初回のトリガー設定のみ手動で行いますが、設定画面経由でもエディタから当該関数の直接実行でもどちらでも結構です。
:::

# 比較的実装が容易なケース（２）
## 毎月月末に実行したい
要望としてはとてもシンプルなのですが、ひとくちに月末と言っても**小の月**、特に２月や閏年も考慮する必要があります。**月ベースのタイマー**で**３１日**を指定したら自動で調整してくれてもいいようなものですが、そうもいきません。

相談内容としましては、月末を締め日としているクライアント様が、当日の定型処理を自動化したい、というケースがほとんどです。

### ロジック
- Javascriptにて**翌月月末の指定日時**を設定する
- `ScriptApp`にて**特定の日時**としてトリガーを生成する

[前回](https://qiita.com/STSHISHO/items/00f388736fb475d3322e)と同様、当月実行時に**翌月の処理を実行予約する**イメージとなります。結果、これが毎月繰り返されることで、毎月末日の指定日時（仮に午後２３時５５分とします）に自動実行されます。

https://qiita.com/STSHISHO/items/00f388736fb475d3322e

### サンプルコード

```Javascript: 毎月月末の午後23:55にメイン処理を実行する
// 初期設定用オブジェクト
function init(){
  return {
    START_TIME: '23:55', // hh:mm形式で指定
    TRIGGER_HUNDLER: 'main' // 自動実行対象の関数名
  };
}

// メイン処理
function main() {
  /* メイン処理省略 */

  setTrigger();
}

// トリガー生成処理
function setTrigger() {
  const now = new Date();
  const INIT = init();
  const startTime = INIT.START_TIME.split(':');
  const targetDate = new Date(now.getFullYear(), now.getMonth()+2, 0, startTime[0]*1, startTime[1]*1);

  ScriptApp.newTrigger(INIT.TRIGGER_HUNDLER)
  .timeBased()
  .at(targetDate)
  .create();
}
```
ポイントは表題の通り、月末の取得方法になります。以下のような２ステップで月末設定をしています。

1. 翌月を指定するために現在時から`getMonth`したものに２を足す[^1]
2. 同様に、末日を指定するために第３引数（date）に `0` を設定する

[^1]: JavascriptのDateオブジェクトでは`getMonth`の戻り値は０スタートになります。

### 初回設定
初回はＵＩ画面より**特定の日時**で**当月**月末の指定時間を設定し、`main`を実行させます。以下は現在時が2022年6月23日の場合の設定例です。

- **実行する関数を選択**：main
- **時間ベースのトリガーのタイプを選択**：特定の日時
- **日時を入力**：2022-06-30 23:55

# 最後に
[前回](https://qiita.com/STSHISHO/items/00f388736fb475d3322e)に引き続き、比較的実装が容易なケースとして **毎月月末に実行したい（毎日の月末判定不要）場合** のご紹介をしましたが、もちろん最適解ではございません。

ネット上では毎日トリガーを実行させ、その都度当日が月末かどうかを判定する方法、要するに **月末判定で処理を分岐させる** 方法が散見されます。別途、毎日の定型処理があるなら、ついでの処理としては問題ないと思いますが、個人的には、 **月末判定のためだけに毎日トリガーを走らせるのは非効率** かと思います。

なお、[前回](https://qiita.com/STSHISHO/items/00f388736fb475d3322e)の内容を理解いただけているならすでにお気づきかもしれませんが、つまるところ、`targetDate` の生成ロジックがポイントとなります。**実行間隔が一定** であるか、**実行日を特定できる明確な法則（条件）** があれば、**特定の日時** を使用することで、ほぼほぼご要望内容は実現可能と思います。

誰かのお役に立てたなら幸いです。

# [参考・引用]
- [Class ClockTriggerBuilder  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference/script/clock-trigger-builder)
- [[GAS] 簡単になったGAS実行環境のタイムゾーン設定](https://qiita.com/STSHISHO/items/2985c7b92c80b28f1018)
- [Quotas for Google Services  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/guides/services/quotas#current_quotas)