Google Apps Scriptを正確なスケジュールで実行する
課題
- GASで「毎日 HH:MM に実行」といった感じのタスクスケジューリングをしたい
- トリガー「時間主導型/日タイマー」だと「午前8時〜9時」のような、ざっくりとした指定しかできない
- 「時間主導型/特定の日時」は
YYYY-MM-DD HH:MM
で指定ができ、指定した時間に正確に実行してくれるが、日時のピンポイント指定のみで条件指定ができないため、1実行につき1件のトリガーを作成しなければならず、運用するのは非現実的
ソリューション
「特定の日時」トリガーを動的に生成するタスクを「日タイマー」トリガーで実行する
サンプルコード
- 下の例では
createTriggers()
で、土日祝を除く毎日、12:00、13:00、14:00にmain()
を実行するためのトリガーを生成する - 生成された
main()
のトリガーは実行が完了した後も残り続けるため、ScriptApp.deleteTrigger()
で掃除する- 削除する際に、
createTriggers()
のトリガーを一緒に削除しないよう、trigger.getHandlerFunction() === 'main’
のものに絞って削除を行う
- 削除する際に、
createTriggers()
を実行するためのトリガーを「時間主導型/日タイマー/午前10時〜11時」で作成する
function isHoliday(date) { if (date.getDay() === 0 || date.getDay() === 6) { return true } // 日本の祝日カレンダーに終日予定があれば祝日とする var calendar = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com') var events = calendar.getEventsForDay(date) return events.length > 0 } function createTriggers() { console.log('createTriggers') var now = new Date() // 残っているトリガーを掃除する var triggers = ScriptApp.getProjectTriggers() if (Array.isArray(triggers)) { triggers.forEach(function(trigger) { // mainのトリガーのみを削除する if(trigger.getHandlerFunction() === 'main') { ScriptApp.deleteTrigger(trigger); } }) } // 土日祝はスケジュールしない if (isHoliday(now)) { console.log('there are no schedules today') return } var hours = [12, 13, 14] hours.forEach(function(hour) { var date = new Date() date.setHours(hour) date.setMinutes(0) if (now.valueOf() < date.valueOf()) { // main() のトリガーを指定した日時で作成 ScriptApp.newTrigger("main").timeBased().at(date).create() } }) } function main() { console.log('hello.') }
実行結果
createTriggers()
が日タイマーで実行された結果、11:00、12:00、13:00 のトリガーが生成されている。
(なぜか並び順は滅茶苦茶だが...)
各トリガーの実行結果。
秒はまちまちだが、時/分までは指定した時刻に実行されているのがわかる。
(GASで console.log()
した内容はStackdriver Loggingで確認できる)
まとめ
- GASを正確に定期実行したい場合、デフォルトで用意されている時間主導型トリガーのタイマーではなく、
ScriptApp.newTrigger()
を使用する - 生成したトリガーは実行された後も残るので、次のトリガー生成のタイミングにリセットが必要