プログラムを営業日にのみ稼働させる方法の俺的決着①

# 悩んだ休日判定
Pythonでは`jpholiday`などのライブラリがあり、簡単に祝日判定を返してくれる。
土日は`weekday`のナンバーで、祝日判定はライブラリに頼れるとして、じゃあ、ゴールデンウイーク前後で会社が休み、年末年始など祝日じゃないけど世間的に休みの時は？？
いつ休みにするか、関係各所との調整でぎりぎりに決まるなんてことは珍しくないし。。。

# 毎年々々、その都度確認＆コード修正なんて面倒くさい
- ディベロパーが手作業で対応するなんてナンセンス。
- 休日追加してコード動かないようにする、休日だけど任意のタイミングで上層部はほしい（レポーティングを吐き出すスクリプトなど）、なんて事務員さんや本人たちにやってもらいたい。
- そもそももう放っておくモードのコード触りたくない

と怠け者の俺は思った。
そしてなんとか考えた結果、この方法にたどり着いた。

# 結論
### ①スプレッドシートの`=workday()`で営業日を算出
### ②スクリプトの稼働はカレンダー予定の開始日時にすることで追加/削除を簡易的にする
### ③特別な休日の追加はGoogleフォームで事務員さんにやってもらう
# 解説
スプシの何がいいかというと、
1. 日付が変わると`=today()`を勝手に再計算してくれる(エクセルでは一回開いて上書き保存が必要)。これがファンダメンタル。これは深夜0:00amにこの目で確認した。
2. GASがあるので、内閣府の祝日データを定期的に読み込むスクリプトをサクッと作れる。
3. 祝日データと、人手で追加するデータの合算があれば、それで社内的な休日が割り出せる。

(本記事で触れる内容はここまで)

さらに、IFTTT([5 appletまで無料](https://ifttt.com/plans))のIF(トリガー)には**カレンダーイベントの作成や開始時**、That(アクション)には**Webhookポスト**があるので、これを混ぜて**Googleカレンダーをジョブスケジューラ化**できる。
Googleカレンダーのジョブスケジューラ化とはつまり、Googleカレンダーイベントの追加や削除、時間移動で、プログラムの稼働タイミングをコントロールできるということ。
急な休みでプログラムをオフしたり、追加したりがこれで自在になる。稼働スケジュールの可視化にもなる。
(ここら辺は次回記事)

これらを使って、以下の施策を施した。

### セットアップ

#### ① GASで定期的に[内閣府のHP](https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html)の公開CSVから祝日データを取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1984110/8d65a34f-0a4f-1f97-2f80-3438cb556dc8.png)
※天皇陛下の代替わりで一年に一回では祝日が必ずしも確定していないことを味わったので、月一回くらいの頻度でこのスクリプトをトリガーさせるようにした。

```javascript:getHolidays.gs
const url = 'https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv';
const sheetName ='HolidayDataFromGovernment'
const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);

function getHolidays() {
  let text = UrlFetchApp.fetch(url).getBlob().getDataAsString("Shift_JIS");
  let csv = Utilities.parseCsv(text);
  // はりつけ前にクリア
  sheet.clearContents()
  // はりつける
  sheet.getRange(1, 1, csv.length, csv[0].length).setValues(csv);
}
```
シート名は`HolidayDataFromGovernment`。

#### ② 手入力で休日を入れられるシートを用意。
シート名 = `manual Input`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1984110/92d180da-4276-7c4e-9bad-477d2f24e495.png)

#### ③ そこに日付を追加するフォームを社内に共有。
Googleフォームでよし。ネイティブにフォームとスプシを連携させてもよし。フォームにコンテナバインドのスクリプトを追加して、`onSubmit`で稼働させて日付を追加するのもよし。
みんなにシートを公開してしまうと、誤っての削除、日付フォーマットの乱れなどあるかもしれないので手入力は許さない。
まあそこまでしなくとも、保護などかけつつ限定共有するでもいいかもしれない、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1984110/5ef19f52-d3ea-eea6-acac-3b5adce8f78f.png)

#### ④ `HolidayDataFromGovernment`と`manual Input`を合算したシートを用意
大分前なのでどこを参考にしたのか忘れてしまったし関数の詳細は自分でも忘れてしまったが、`=filter()`を使って`HolidayDataFromGovernment`の**空欄じゃない、ついでに今より12か月前以後のもの**というフィルターをかけたものと、と`manual Input`の空欄じゃないものを配列を構成できる`{;}`を使って合算して`Aggregated`というシートの`A2`以下に持ってきている。
```
={FILTER(INDIRECT("'HolidayDataFromGovernment'!A2:A"&Match(1,ArrayFormula(1/(HolidayDataFromGovernment!A2:A<>"")),1)),INDIRECT("'HolidayDataFromGovernment'!A2:A"&Match(1,ArrayFormula(1/(HolidayDataFromGovernment!A2:A<>"")),1))>EDATE(TODAY(),-12));INDIRECT("'manual input'!A1:A"&Match(1,ArrayFormula(1/('manual Input'!A1:A<>"")),1))}
```
これで`=WORKDAY(開始日, 日数, [休日])`の休日参照元ができあがった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1984110/b6a6f2ca-954d-ca6e-511f-5d71901fb104.png)

### 必要な判定はGoogleシートにやらせておく
#### 「今日が営業日か？」
という判定は、
**workdayで算出する `明日-1営業日` が`today()`と一緒か**のBooleanでできる。
※`INDIRECT("Aggregated!A2:A"&COUNTA(Aggregated!A:A))`とすることで最終行までを動的に参照。単純に`Aggregated!A2:A`でもいいかも。
```
=WORKDAY(TODAY()+1,-1,INDIRECT("Aggregated!A2:A"&COUNTA(Aggregated!A:A)))=TODAY()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1984110/2f7b73eb-95a1-d608-be69-8329cc44f2d9.png)
これでプログラム起動時に単一セルのBooleanを読むだけで今日が営業日か判明する。
FALSEの場合にexitというガードステートメントをコードに書いておけば、もうここまででもOK。
ただし、ここまでだと休日の追加は簡単にできても、稼働タイミングの追加はできない。


次回記事で、 このスプシを使って**社内の特定の営業日を算出するやり方**や、スプシで算出された日付を元に予定を作ることと、IFTTTを使うことで、**Googleカレンダーをスクリプト実行するスケジューラーにする方法**を紹介する内容を執筆予定。
スプシを頻繁に読み込むとエラーになるのが怖い、という場合もあるかもしれないし。

執筆したら次回記事のリンクをつけて記事更新＆通知するので、気になる方はストックしてください。