【JavaScript】祝日取得クラス

[先日作成したPHP版祝日取得クラス](https://qiita.com/akebi_mh/items/82cf4dbe70ac9913beb1) のJavaScript版です。
PHP版をJavaScriptに置き換えただけですので、使い方もほぼ同じです。
メソッドやプロパティについてはPHP版のほうを参照してください。

~~~js:holiday_class.js
class Holiday {

    constructor(year = 0) {
        // 祝日定義
        // ('国民の祝日に関する法律'が公布・施行された1948年7月20日以降のもののみ)
        this.holidayDefinitions = {
            1 : {
                '1949:1'    : '元日',
                '1949:15, 2000:2_1'
                            : '成人の日',
            },
            2 : {
                '1967:11'   : '建国記念の日',
                '2020:23'   : '天皇誕生日',
                '1989:24, 1990:0'
                            : '大喪の礼',
            },
            3 : {
                '1949:s'    : '春分の日',
            },
            4 : {
                '1959:10, 1960:0'
                            : '結婚の儀',
                '1949:29'   : '天皇誕生日, 1989:みどりの日, 2007:昭和の日',
            },
            5 : {
                '2019:1, 2020:0'
                            : '皇太子殿下即位・改元',
                '1949:3'    : '憲法記念日',
                '2007:4'    : 'みどりの日',
                '1949:5'    : 'こどもの日',
            },
            6 : {
                '1993:9, 1994:0'
                            : '結婚の儀',
            },
            7 : {
                '1996:20, 2003:3_1, 2020:23, 2021:22, 2022:3_1'
                            : '海の日',
                '2020:24, 2021:23, 2022:0'
                            : 'スポーツの日',
            },
            8 : {
                '2016:11, 2020:10, 2021:8, 2022:11'
                            : '山の日',
            },
            9 : {
                '1966:15, 2003:3_1'
                            : '敬老の日',
                '1948:a'    : '秋分の日',
            },
            10 : {
                '1966:10, 2000:2_1, 2020:0, 2022:2_1'
                            : '体育の日, 2020:スポーツの日',
                '2019:22, 2020:0'
                            : '即位礼正殿の儀',
            },
            11 : {
                '1948:3'    : '文化の日',
                '1990:12, 1991:0'
                            : '即位礼正殿の儀',
                '1948:23'   : '勤労感謝の日',
            },
            12 : {
                '1989:23, 2019:0'
                            : '天皇誕生日',
            },
        };

        this.dateTime = new Date();
        this.dateTime.setHours(0, 0, 0);

        if(year < 1) year = this.dateTime.getFullYear();
        this.year = year;
        this.month = this.dateTime.getMonth() + 1;

        this.result = {};
        this.useIndefiniteHoliday = 1;
        this.resultType = 0;
    }

    /**
     *  1年分のリストを返す
     */
    getHolidayOfYear(year = 0) {
        if(year < 1) year = this.year;

        if(this.result[year] === undefined) {
            // 該当年の祝日オブジェクトに変換
            let holiday = {};
            const equinox = .242194 * (year - 1980) - Math.floor((year - 1980) / 4);
            for(const month in this.holidayDefinitions) {
                holiday[month] = {};
                const currentMonthData = this.holidayDefinitions[month];
                for(const days in currentMonthData) {
                    const names = currentMonthData[days];
                    // 対象年・日取得
                    const _days = days.split(',');
                    const arrTmp = {};

                    for(const i in _days) {
                        const tmp = _days[i].split(':');
                        arrTmp[Number(tmp[0])] = tmp[1].trim();
                    }
                    let yearTmp = 0;
                    for(const tmp in arrTmp)
                        if(year >= tmp && tmp >= yearTmp) yearTmp = tmp;

                    if(yearTmp === 0) continue;

                    // 日を記述形式ごとに取得
                    let day;
                    if(arrTmp[yearTmp] === 's')
                        day = Math.floor(20.8431 + equinox);
                    else if(arrTmp[yearTmp] === 'a')
                        day = Math.floor(23.2488 + equinox);
                    else if(arrTmp[yearTmp].indexOf('_') >= 0) {
                        const [num, w] = arrTmp[yearTmp].split('_');
                        day = this.getDayOfNumWeek(year, month, num, w);
                    }
                    else
                        day = Number(arrTmp[yearTmp]);

                    if(day < 1) continue;

                    // 名称取得
                    const _names = names.split(',');
                    const arrTmpName = {};
                    for(const i in _names) {
                        const tmp = _names[i].split(':');
                        if(tmp.length === 1)
                            arrTmpName[0] = tmp[0].trim();
                        else
                            arrTmpName[Number(tmp[0])] = tmp[1].trim();
                    }

                    yearTmp = 0;
                    for(const tmp in arrTmpName)
                        if(year >= tmp && tmp >= yearTmp) yearTmp = tmp;

                    holiday[month][day] = (holiday[month][day] === undefined) ?
                        arrTmpName[yearTmp] : holiday[month][day] + ', ' + arrTmpName[yearTmp];
                }
            }

            // 国民の休日・振替休日
            if(this.useIndefiniteHoliday)
                holiday = this.indefiniteHoliday(holiday, year);

            this.result[year] = holiday;
        }

        return this.resultType ?
            this.convertLinear(year, this.result[year]):
            this.result[year];
    }

    /**
     *  国民の休日・振替休日
     */
    indefiniteHoliday(holiday, year) {
        for(let month = 1; month <= 12; month++) {
            // 月末日
            this.dateTime.setFullYear(year, month, 0);
            const lastDay = this.dateTime.getDate();

            for(let day = 1; day <= lastDay; day++) {
                // 前日の月日
                this.dateTime.setFullYear(year, month - 1, day - 1);
                const prevMonth = this.dateTime.getMonth() + 1;
                const prevDay = this.dateTime.getDate();

                // 翌日の月日
                this.dateTime.setFullYear(year, month - 1, day + 1);
                const nextMonth = this.dateTime.getMonth() + 1;
                const nextDay = this.dateTime.getDate();

                // 祝日に挟まれた平日を国民の休日に変更(1986年以降)
                this.dateTime.setFullYear(year, month - 1, day);
                if( year >= 1986 && // 1986年以降
                    holiday[prevMonth][prevDay] !== undefined && // 前日が祝日
                    holiday[nextMonth][nextDay] !== undefined && // 翌日が祝日
                    holiday[month][day] === undefined && // 当日が祝日ではない
                    this.dateTime.getDay() !== 0 // 当日が日曜ではない
                ) {
                    holiday[month][day] = '国民の休日';
                }

                // 振替休日(1973年4月以降)
                if( (year > 1973 || (year == 1973 && month >= 4)) && // 1973年4月以降
                    holiday[month][day] !== undefined && // 祝日
                    this.dateTime.getDay() === 0 // 日曜
                ) {
                    // その日以降の直近の平日を振替休日に
                    for(let i = 1; i < 7; i++) {
                        this.dateTime.setFullYear(year, month - 1, day + i);
                        const m = this.dateTime.getMonth() + 1;
                        const d = this.dateTime.getDate();
                        if(holiday[m][d] === undefined) {
                            holiday[m][d] = '振替休日';
                            break;
                        }
                    }
                }
            }
        }
        return holiday;
    }

    /**
     *  year年 month月 第num w曜日に該当する日を返す
     */
    getDayOfNumWeek(year, month, num, w) {
        this.dateTime.setFullYear(year, month - 1, 1);
        const firstDayWeek = this.dateTime.getDay();
        return 1 + (num - 1) * 7 + (7 + w - firstDayWeek) % 7;
    }

    /**
     *  YYYY-MM-DDをキーとしたオブジェクトに変換
     */
    convertLinear(year, array) {
        const arrTmp = {};
        for(const month in array) {
            for(const day in array[month]) {
                const names = array[month][day];
                arrTmp[
                    String(year).padStart(4, '0') + '-' +
                    String(month).padStart(2, '0') + '-' +
                    String(day).padStart(2, '0')
                ] = names;
            }
        }
        return arrTmp;
    }

    /**
     *  1か月分のリストを返す
     */
    getHolidayOfMonth(month = 0) {
        if(month < 1 || month > 12) month = this.month;

        const year = this.year;

        // 該当年の結果が未取得であれば取得
        if(this.result[year] === undefined)
            this.getHolidayOfYear();

        if(this.resultType) {
            const arrTmp = {};
            for(const day in this.result[year][month]) {
                arrTmp[
                    String(year).padStart(4, '0') + '-' +
                    String(month).padStart(2, '0') + '-' +
                    String(day).padStart(2, '0')
                ] = this.result[year][month][day];
            }
            return arrTmp;
        } else {
            return this.result[year][month];
        }
    }

    /**
     *  国民の休日・振替休日 使用フラグ変更
     */
    setUseIndefiniteHoliday(flg = 1) {
        this.useIndefiniteHoliday = flg == 1 ? 1 : 0;

        // 取得済みの結果をリセット
        this.result = {};
    }

    /**
     *  戻り値形式変更
     */
    setResultType(flg = 0) {
        this.resultType = flg == 0 ? 0 : 1;
    }

    /**
     *  年変更
     */
    setYear(year = 0) {
        if(year < 1) year = new Date().getFullYear();
        this.year = year;
    }
}
~~~

####使用例####

~~~js
const holiday = new Holiday;
// 年も同時に指定
//const holiday = new Holiday(2019);

// 国民の休日及び振替休日の使用指定(0:非使用 1:使用(デフォルト))
holiday.setUseIndefiniteHoliday(1);

// 戻り値形式変更メソッド
// 0: 月、日をキーとしたオブジェクト(デフォルト)
// 1: YYYY-MM-DDをキーとしたオブジェクト
holiday.setResultType(0);

// 1年分のリスト取得
// 引数は年、引数省略時は年プロパティの値を参照します
console.log(holiday.getHolidayOfYear());

// 1か月分のリスト取得
// 引数は月、引数省略時は実行時の月で取得します
// 年は年プロパティの値を参照します
console.log(holiday.getHolidayOfMonth());

// 戻り値をYYYY-MM-DD形式に変更
holiday.setResultType(1);

// 年プロパティ変更(引数省略時は現在の年で設定)
holiday.setYear(2019);

console.log(holiday.getHolidayOfYear());
console.log(holiday.getHolidayOfMonth(5));
~~~

####結果####
![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618708/83401e25-dfab-6164-35ac-86465dc7c102.jpeg)

年、月をキーにして取得したものは、こんな使い方をする場合に楽かと思います。

~~~js
const year  = 2020;
const month = 7;
const day   = 24;
const holidayName = new Holiday(year).getHolidayOfMonth(month)[day];
console.log(
    `${year}年${month}月${day}日は` +
    (holidayName !== undefined ? `${holidayName}です` : '祝日ではありません')
);
~~~

####結果####

~~~plain
2020年7月24日はスポーツの日です
~~~