はじめに
スプレッドシートを始めとしたGoogleのクラウドアプリは、時間主導型トリガー(以降、タイムトリガー)を使用して、定型処理を定期的に自動実行させることができます。
タイムトリガーはUI画面から簡単に設定可能ですが、UIにて設定できる項目が限られているため、要望する内容によってはGASのScriptApp
を使ってプログラム側からトリガーを生成する必要があります。
本稿では、UI画面からの設定では対応できない内容について、当方にご相談頂いた内容を元にした事例別の参考コードを不定期連載形式で紹介していきたいと思います。(ネタが溜まってきたら、随時公開予定です)
今回は前回の応用編として、指定休業日以外の営業日ごとに実行したい場合 のご紹介です。
設置時の共通事項
- トリガー生成ロジックは独立した関数として定義し、メイン処理内に組み込みます。組み込み位置は末尾を推奨しておきますが、好みや状況に合わせて適宜ご対応ください。
- GASの制限事項を考慮し、適宜メイン処理の軽量化、高速化をしましょう。また、実行回数に関係なく、実行済みトリガーの削除処理の実装を推奨します。
- 初回のトリガー設定のみ手動で行いますが、設定画面経由でもエディタから当該関数の直接実行でもどちらでも結構です。
指定休業日以外の営業日ごとに実行したい場合
休業日についての要件をまとめる
主にサービス業の方からのご相談が多いケースですが、本件のような内容では、指定休業日が不定休なのか一部例外を除き一定の法則があるのかに関わらず、翌営業日を特定すること がポイントになります。
なお、ベースとなるロジックは 前回 の応用編になりますので、当日実行時に翌営業日を特定し、指定の日時で実行予約をする 、というロジックをベースとします。以下が雛形となるコードになります。
// 初期設定用オブジェクト
function init(){
return {
/* 必要に応じてその他プロパティ設定 */
START_TIME: '00:00', // hh:mm形式で指定
TRIGGER_HUNDLER: 'main' // 自動実行対象の関数名
};
}
// メイン処理
function main() {
/* メイン処理省略 */
setTrigger();
}
// トリガー生成処理
function setTrigger() {
const now = new Date();
const INIT = init();
const startTime = INIT.START_TIME.split(':');
// 翌営業日を特定する処理
const getNBD = date => {
/* 以降、ケースに応じて構築 */
};
const NBD = getNBD(now);
const targetDate = new Date(NBD.getFullYear(), NBD.getMonth(), NBD,getDate(), startTime[0]*1, startTime[1]*1);
ScriptApp.newTrigger(INIT.TRIGGER_HUNDLER)
.timeBased()
.at(targetDate)
.create();
}
つまり、今回の主目的は、個別のご要望に応じた getNBD()
の構築ということになり、合わせてその他付随する処理を追加もしくは修正して対応することになります。以下、具体的な事例を元に対応内容を考えてみましょう。
- 事例A:元日以外は原則無休、月内に1日店休日を設けることもあり、当月中に翌月の店休日を決定する(連休になることはない)
- 事例B:土日祝は原則として休業、家族や個人的な理由で急遽1日から数日間休業することがあり、原則前日までには決定する
- 事例C:毎週火曜日が定休日、祝日の場合は翌水曜日に振替える(水曜日も祝日の場合はその週は無休)
※事例Cは年末年始、GW、お盆期間に不定期の休業があり、期間は年によって変わる
また、今回は以下の内容も前提として追加しておきます。
- 営業日の指定時間に1度だけ実行する
- スプレッドシートがメインUI
ロジック
事例ごとの要件に合わせ、翌営業日を取得する getNBD()
を構築します。いずれも別途休業日登録用のカレンダーかテーブルを用意し、参照、休業日判定という流れとなります。事例B、Cは、一定の法則があるため、加えて、例外時の判定方法を考えればよいでしょう。
事例A
基本的に毎日実行することになりますので、その際に翌日が営業日かどうかを判定し、実行予約をするという方針で良いと思います。この事例の場合は祝日カレンダーを読み込む必要がありませんので、別途カレンダー参照ではなく、別シートに休業日設定用のテーブルを設置し、名前付き範囲で参照するほうが良いでしょう。
スプレッドシートを参照するため、init()
に Spreadsheet
を追加しておきます。
// 初期設定用オブジェクト
function init(){
return {
SS: SpreadsheetApp.getActive(),
START_TIME: '00:00', // hh:mm形式で指定
TRIGGER_HUNDLER: 'main' // 自動実行対象の関数名
};
}
要件より、元日が店休日確定のため、ピンポイントの元日判定を行います。また、Utilities.formatDate()
にて日付を文字列としてフォーマットし、単純比較を行います。最後に翌日が店休日だった場合には更にその翌日を返すようにします。
const getNBD = date => {
const tomorrow = new Date(date);
tomorrow.setDate(date.getDate()+1);
// 名前付き範囲から次回休業日を取得
const nextClosedDay = INIT.SS.getRangeByName('**********').getValue();
// 翌日が元日かどうか
const isNewYear = nextDay.getMonth() === 0 && nextDay.getDate() === 1;
// 翌営業日の判定
const formatedNCD = Utilities.formatDate(nextClosedDay, 'JST', 'yyyyMMdd');
const formatedNxt = Utilities.formatDate(tomorrow, 'JST', 'yyyyMMdd');
const isClosedDay = formatedNCD === formatedNxt;
return (isNewYear || isClosedDay)? tomorrow.setDate(tomorrow.getDate()+1): tomorrow;
};
事例B
祝日カレンダーとは別に 臨時休業カレンダーを用意する という方針で良いと思います。事例Aと同様に定義用のテーブルを用意しても良いですが、判定方法の統一という面から、カレンダーによる管理を推奨します。その際、カレンダーはお店や会社用のアカウントで作成し、個人用とは混同させない ようにしたほうが良いでしょう。
init()
に祝日カレンダーと臨時休業カレンダーを追加します。休業日は終日イベントとして事前にカレンダーに登録されていることが前提となります。
// 初期設定用オブジェクト
function init(){
return {
HOLIDAY_CAL: CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com'),
CLOSED_CAL: CalendarApp.getCalendarById('********************'), // 臨時休業カレンダーID
START_TIME: '00:00', // hh:mm形式で指定
TRIGGER_HUNDLER: 'main' // 自動実行対象の関数名
};
}
祝日判定は 前回 のものを修正して利用します。INITオブジェクト
へアクセスするため、setTrigger()
内に定義します。
// 祝日の判定
const isHoliday = date => {
const events = INIT.HOLIDAY_CAL.getEventsForDay(date);
return events.length > 0;
}
メインとなる翌営業日の取得です。先に臨時休業判定用の処理 isClosedDay()
を定義しておきます。
/* 祝日の判定処理割愛 */
// 臨時休業日の判定
const isClosedDay = date => {
const events = INIT.CLOSED_CAL.getEventsForDay(date);
return events.length > 0;
};
const getNBD = date => {
const tomorrow = new Date(date);
tomorrow.setDate(date.getDate()+1);
// 土曜日なら日曜日をスキップし月曜日の判定
if(tomorrow.getDay() === 6) return getNBD(new Date(tomorrow.setDate(tomorrow.getDate()+1)));
// 祝日なら翌日の判定
if(isHoliday(tomorrow)) return getNBD(tomorrow);
// 臨時休業日なら翌日の判定
if(isClosedDay(tomorrow)) return getNBD(tomorrow);
return tomorrow;
};
事例C
基本的には事例Bと同様、GW、年末年始、お盆期間などの 休業日カレンダーを用意する という方針で良いと思います。この事例では 振替処理の構築が主なポイント となります。定休日が祝日であった場合、翌日を休業日として自動登録することにします。
また、休業日判定のタイミングも重要 です。祝日が営業日扱いになるため処理分岐時の条件設定に注意しましょう。特に振替休業日については混乱してしまいますので、以下に分岐内容をまとめておきます。
- 定休日が祝日かつ翌日が平日:休業日カレンダーへイベント登録
- 定休日が祝日かつ翌日が祝日:何もしない
- 定休日が平日:翌日の休業日判定へ
なお、休業日の判定は事例Bで使用した isClosedDay()
をそのまま使用します。もちろん、 isHoliday()
もそのまま使用し、init()
には休業日カレンダーと定休日の設定として、REGULAR_CLOSED_DAY: 2
を追加しておきます。
// 初期設定用オブジェクト
function init(){
return {
HOLIDAY_CAL: CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com'),
CLOSED_CAL: CalendarApp.getCalendarById('********************'), // 休業日カレンダーID
REGULAR_CLOSED_DAY: 2,
START_TIME: '00:00', // hh:mm形式で指定
TRIGGER_HUNDLER: 'main' // 自動実行対象の関数名
};
}
先に休業日判定を行うことで、事前に登録されているGW等の休業期間と後から自動で登録される振替休業日が重なっても、正常に判定されようにします。
/* 祝日の判定処理割愛 */
/* 休業日の判定処理割愛 */
const getNBD = date => {
const tomorrow = new Date(date);
tomorrow.setDate(date.getDate()+1);
// 休業日の判定(GW、年末年始、お盆、振替休業日)
if(isClosedDay(tomorrow)) return getNBD(tomorrow);
// 振替休業日の設定
const isRCD = tomorrow.getDay() === INIT.REGULAR_CLOSED_DAY;
if(isRCD){
const slideDay = new Date(tomorrow);
slideDay.setDate(tomorrow.getDate()+1);
// 定休日が祝日かつ翌日が平日なら振替え、平日なら翌日の判定
if(isHoliday(tomorrow) && !isHoliday(slideDay)) INIT.CLOSED_CAL.createAllDayEvent('振替休業日', slideDay);
if(!isHoliday(tomorrow)) return getNBD(tomorrow);
}
return tomorrow;
};
初回設定(共通)
初回はUI画面より特定の日時で翌日の指定時間を設定し、main()
を実行させます。以下は現在時が2022年6月23日、時間は午前9時の場合の設定例です。
- 実行する関数を選択:main
- 時間ベースのトリガーのタイプを選択:特定の日時
- 日時を入力:2022-06-24 09:00
最後に
サンプルということもあり、開発効率優先のテンプレートに当てはめる形式で構築しました。事例Aは時間指定がなければ 日付ベースのタイマー で毎日定時間帯に実行し、休業日だけ処理スキップする仕様でも良いと思います。その他、事例B、Cについてももっと効率的な方法があると思います。
誰かのお役に立てたなら幸いです。