Apexで日本の祝日を判定する

Salesforce（Apex + Visualforce）でカレンダーを作る際に、祝日判定の機能がなかったのでApexクラスでつくりました。
祝日は大きく分けて次のパターン

* 国民の休日
 * 固定祝日　ex. 元旦、建国記念の日、憲法記念日など
 * 春分の日
 * 秋分の日
 * ハッピーマンデー　ex. 成人の日、海の日、敬老の日、体育の日
* 振替休日

しかしここで気をつけなれけば行けないのが、当年だけに存在する固定祝日がある

* 新天皇即位日
* 即位礼正殿の儀

そのため、固定祝日を設定できるようオブジェクト化したほうがよいが、今回はApexクラス内で管理している。

春分の日と秋分の日
---
これは簡単、日付の計算方法が決まっているので、それをApexにするだけ

```
/**
 * 春分の日かどうか
 *
 * @params Date d 調べたい日付
 * @return boolean true:春分の日 / false:春分の日ではない
 **/
private static boolean isSpringHoliday(Date d) {
    return d.month() == 3 && d.day() == checkFourSeasons(20.8431, d.year());
}

/**
 * 秋分の日かどうか
 *
 * @params Date d 調べたい日付
 * @return boolean true:秋分の日 / false:秋分の日ではない
 **/
private static boolean isAutumnHoliday(Date d) {
    return d.month() == 9 && d.day() == checkFourSeasons(23.2488, d.year());
}

```

ハッピーマンデー
---
これも簡単、日付が固定されているのでそれにあわせるだけ

```
/**
 * ハッピーマンデーかどうか
 *
 * @params Date d 調べたい日付
 * @return boolean true:ハッピーマンデー / false:ハッピーマンデーではない
 **/
@TestVisible
private static boolean isHappyMonday(Date d) {
    Integer year = d.year();
    Integer month = d.month();
    Integer day = d.day();
    List<List<Integer> > holiday  = new List<List<Integer> > {
        //                             月 第N曜日 開始年 終了年
        new List<Integer> {1, 2, 2000, 9999}, // 成人の日
        new List<Integer> {7, 3, 2003, 9999}, // 海の日
        new List<Integer> {9, 3, 2003, 9999}, // 敬老の日
        new List<Integer> {10, 2, 2000, 9999} // 体育の日（スポーツの日）
    };

    Integer dow = getDayOfWeek(Date.newInstance(year, month, 1));
    Integer mondayOfWeek = 1;
    Integer happyDay = mondayOfWeek - dow < 0 ? 7 + mondayOfWeek - dow : mondayOfWeek - dow;
    happyDay++;

    for (List<Integer> variable : holiday) {
        if (month == variable[0]  && year >= variable[2] && year <= variable[3]) {
            happyDay = happyDay + 7 * (variable[1] - 1);
            if (happyDay == day) {
                return true;
            }
        }
    }

    return false;
}
```

固定祝日
---
固定もハッピーマンデーと似ており、処理を別にする必要は無いが、祝日分類のため別々のメソッドで管理している

```
/**
 * 固定祝日かどうか
 *
 * @params Date d 調べたい日付
 * @return boolean true:固定休日 / false:固定休日ではない
 **/
@TestVisible
private static boolean isFixedHoliday(Date d) {
    Integer year = d.year();
    Integer month = d.month();
    Integer day = d.day();
    List<List<Integer> > holiday  = new List<List<Integer> > {
        //                             月 日 開始年 終了年
        new List<Integer> {1, 1, 1949, 9999},    // 元旦
        new List<Integer> {2, 11, 1967, 9999},   // 建国記念の日
        new List<Integer> {2, 23, 2020, 9999},   // 天皇誕生日
        new List<Integer> {4, 29, 2007, 9999},   // 昭和の日
        new List<Integer> {5, 3, 1949, 9999},   // 憲法記念日
        new List<Integer> {5, 4, 2007, 9999},   // みどりの日
        new List<Integer> {5, 5, 1949, 9999},   // こどもの日
        new List<Integer> {8, 11, 2016, 9999},   // 山の日
        new List<Integer> {11, 3, 1948, 9999},   // 文化の日
        new List<Integer> {11, 23, 1948, 9999},   // 勤労感謝の日
        // 以下、指定した年月日のみの祝日
        new List<Integer> {5, 1, 2019, 2019},  // 新天皇即位日
        new List<Integer> {10, 22, 2019, 2019}  // 即位礼正殿の儀
    };

    for (List<Integer> variable : holiday) {
        if (month == variable[0] && day == variable[1] && year >= variable[2] && year <= variable[3]) {
            return true;
        }
    }

    return false;
}
```

振替休日と国民の休日？
---
ここがめんどくさい、いわゆる振替系

```
/**
 * 国民の休日かどうか
 *
 * @see https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
 * @params Date d 調べたい日付
 * @return boolean true:国民の休日 / false:国民の休日ではない
 **/
@TestVisible
private static boolean isNationalHoliday(Date d) {
    Date yesterday = d - 1;
    Date tomorrow = d + 1;

    return checkHoliday(yesterday) && checkHoliday(tomorrow) && !new List<Integer> {0, 6}.contains(getDayOfWeek(d));
}

/**
 * 振替休日かどうか
 *
 * @params Date d 調べたい日付
 * @return boolean true:振替休日 / false:振替休日ではない
 **/
@TestVisible
private static boolean isTransferHoliday(Date d) {
    // 当日が祝日または日曜日ならfalse
    if (checkHoliday(d) || getDayOfWeek(d) == 0) {
        return false;
    }

    Date yesterday = d - 1;
    for (Integer i = getDayOfWeek(d); i >= 0; i--) {
        if (!checkHoliday(yesterday)) {
            return false;
        } else if (getDayOfWeek(yesterday) == 0) {
            return true;
        }
        yesterday = yesterday - 1;
    }

    return false;
}
```
祝日前後が平日か日曜日で判定をおこなう


全てをまとめたクラスはこちら
---
https://gist.github.com/akiyoshisamukawa/4a02a95e09522a23925376c498fc4d98
もっと効率よく処理を作成できるはずですので、あくまで参考として...