日本の祝日を判定するBashスクリプトを書いた件

業務アプリケーション寄りのプログラマであれば一度は書いたであろう営業日判定のプログラム。
今回は**Bash**で書いてみた。わりと汎用的に使えると思うので共有する。

# 開発動機
休日は実行しなくても良い夜間バッチがあった（休日に実行しても影響は無い）。
土日は実行しないように`cron`でスケジュールしていたが、サーバのリソースを節約したいので、できれば祝日は実行させたくなかった。

# 日本の祝日情報をどこから貰うか
**Google Calendar API** を利用しようと思ったが、調べてみると、内閣府ホームページで祝日情報CSVファイルを公開しているようなので、それを使うことにする。

```:取得方法
curl -sL https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f cp932
```

URLの中に shukujitsu（ヘボン式）とsyukujitsu（訓令式）が混在しているが気にしないことにする。
将来URLが移転するかもしれないので、`L`オプションでリダイレクトを有効にしておく。
`iconv`で指定する文字コードは sjis よりも cp932 の方が無難である。

```:実行結果
国民の祝日・休日月日,国民の祝日・休日名称
1955/1/1,元日
1955/1/15,成人の日
1955/3/21,春分の日
1955/4/29,天皇誕生日
     :
2020/7/24,スポーツの日
2020/8/10,山の日
2020/9/21,敬老の日
2020/9/22,秋分の日
2020/11/3,文化の日
2020/11/23,勤労感謝の日
```

日付の桁数も揃っていないが気にしないことにする。（`2020/1/1`は`2020/01/01`にならないのかという意味）
1955年から2020年までのすべての祝日・振替休日が登録されている。毎年2月になると官報に翌年の暦要項が掲載されるので、2021年のデータは2020年2月頃に追加されるのだろうか？

なお、このCSVファイルは、そのままExcelの**WORKDAY関数**に使えるので覚えておくと良い。

ちなみに・・・「スポーツの日」とは「体育の日」の名称が変わったものだが、2020年だけはオリンピック開会式の7月24日に移動するらしい。本年は令和対応で来年はオリンピックと特例が続く。

# Bashスクリプト
祝日情報を、内閣府のサーバに毎回問い合わせるのも忍びないので、ローカルにキャッシュする。キャッシュが古くなれば自動更新。

確実に祝日と言える場合のみ、終了ステータスに 0 がセットされる。

```bash:check_holiday.sh
#!/bin/bash
###############################################################
#   本邦休日判定スクリプト
#   @author  MindWood
#   @param   チェック日付を yyyymmdd で指定。省略すると今日を仮定
#   @return  0       ... 確実に祝日
#            1       ... おそらく平日
#            上記以外 ... エラー
#   @usage   check_holiday.sh || ”平日に必ず実行させるジョブ”
###############################################################

# 引数チェック
if   [ $# -eq 0 ]; then
    CHECK_DATE=$(date +%s)
elif [ $# -eq 1 ]; then
    CHECK_DATE=$(date +%s --date $1) || exit 254
else
    echo 'Invalid argument'
    exit 255
fi

CACHE_PATH=/tmp                          # 内閣府提供の祝日ファイルをキャッシュするディレクトリ
HOLIDAY_FILE=$CACHE_PATH/holiday.csv     # 祝日登録ファイル名
LIMIT=$(date +%s --date '3 months ago')  # ３ヶ月以上前は古い祝日登録ファイルとする

# 祝日登録ファイルが無い、もしくは祝日登録ファイルの更新日が古くなった場合、再取得する
if [ ! -f $HOLIDAY_FILE ] || [ $LIMIT -gt $(date +%s -r $HOLIDAY_FILE) ]; then
    curl -sL https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv | iconv -f cp932 > $HOLIDAY_FILE || exit 250
fi

# 祝日として登録されていれば 0 を返却して終了
grep ^$(date -d @$CHECK_DATE +%Y/%-m/%-d), $HOLIDAY_FILE > /dev/null 2>&1 && exit 0

# 土日なら 0 を返却して終了
DAYOFWEEK=$(date -d @$CHECK_DATE +%u)
[ $DAYOFWEEK -eq 6 ] || [ $DAYOFWEEK -eq 7 ] && exit 0

# 年末年始（12月31日～1月3日）なら 0 を返却して終了
MMDD=$(date -d @$CHECK_DATE +%m%d)
[ $MMDD -eq 1231 ] || [ $MMDD -le 0103 ] && exit 0

# 上記いずれでもなければ平日として終了
exit 1
```

年末年始を休みとするかは会社によって異なるので、適宜ソースコードを修正して欲しい。
2019年12月26日から2020年1月16日までの平日を表示させた結果を示す。

```bash
for (( DATE=20191226; $DATE < 20200116; DATE=$(date -d "$DATE 1 day" +%Y%m%d) )); do
    ./check_holiday.sh $DATE || echo "$DATE は恐らく平日です"
done
20191226 は恐らく平日です
20191227 は恐らく平日です
20191230 は恐らく平日です
20200106 は恐らく平日です
20200107 は恐らく平日です
20200108 は恐らく平日です
20200109 は恐らく平日です
20200110 は恐らく平日です
20200114 は恐らく平日です
20200115 は恐らく平日です
```

平日なのに祝日と誤判定することは無いが、祝日なのに平日と誤判定することは有り得るため、「恐らく」と自信なげに書いている。
例えば、内閣府のサーバに接続できなければエラー終了となって `||` の後ろが実行されてしまうが、平日に必ず実行させるジョブがある場合は、むしろ安全で都合が良い。

# cron定義例
業務開始前に実行させるジョブが、次のように定義されていたとすると、

```:crontab
00 7 * * * python3 /usr/local/bin/app.py
```

次のように追記するだけで、休日は実行しないようにできる。

```:crontab
00 7 * * * /usr/local/bin/check_holiday.sh || python3 /usr/local/bin/app.py
```

# さいごに
日本の行政機関が提供する **Web API** に、祝日情報も加えていただきたいと思う。