ちょっと話題の記事

正確でカスタマイズ可能な日本の祝日・休日判定処理を求めて

日本の休日・祝日の判定を正確に行う手法を検討しました。お客様の事業者休業日の反映も考慮しています。
2019.03.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

業務システムで、曜日の判定はできても、祝日・休日の判定が難しいと思うことないですか。今年は特に、平成最後の年ということで祝日がイレギュラーですよね。

以前開発していたAlexaスキルで、祝日・休日の判定が必要な場面がありました。"JavaScript 祝日判定"などのキーワードで検索すると、『国民の祝日に関する法律』を基に祝日の判定処理をハードコードしたnpmパッケージやサンプルプログラムがたくさん見つかりますが、以下の点で問題がありました。

  • 今年のような祝日の例外に対応できない
  • 事業所休業日の反映など、お客様のご要望に合わせたカスタマイズができない
  • 継続的な運用(情報の更新)が難しい

これらを満たせる祝日・休日判定処理の実装と運用方法を考えてみました。

祝日データの取得

まずは、今年のような例外にも対応した、正確な祝日のデータが必要です。

祝日に関する事務を所掌するのは内閣府大臣官房総務課とのことで、内閣府の「国民の祝日」についてのページに情報が記載されています。

このページには、例えば今年の5月1日と10月22日が祝日扱いの休日となることなどが記載されています。

このページで取得したいのは、ページの下の方にあるCSVファイルです。執筆時点では「平成31年(2019年)から平成32年(2020年)国民の祝日(csv形式:2KB)」というテキストで https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv に対してリンクが張られています。

CSVファイルは文字コードがShift-JIS、改行コードがCR+LFです。内容をみてみましょう。

国民の祝日・休日月日,国民の祝日・休日名称
2019/1/1,元日
2019/1/14,成人の日
2019/2/11,建国記念の日
2019/3/21,春分の日
2019/4/29,昭和の日
2019/4/30,休日
2019/5/1,休日(祝日扱い)
2019/5/2,休日
...

1行目が見出し(ラベル)行、2行目以降がデータ行であり、データ行は1カラム目がYYYY/MM/DD形式の日付、2カラム目が祝日の名前となっています。

方針

前掲のCSVファイルのURLは、ずっと維持される保証がないこと、また休日データの追加などのカスタマイズができないことから、プログラムで内閣府サイトから直接ダウンロードすることは避けたいと思います。そのため、いったんS3に保存して利用することにします。

このS3のCSVファイルを、別途CyberduckAWS Transfer for SFTPを経由してSFTPソフトにて編集すれば、プログラムを修正することなく、データの更新運用が可能になります。

また、このS3に保存したCSVファイルにお客様事業所の休業日(創立記念日など)を追記することで、同時に休日も判定することができます。

実装例

TypeScriptで実装例をご紹介しています。エラー処理などは省略しています。

まずpackage.jsonです。実行時にはS3からファイルを読み込むためのaws-sdkとCSVファイルの解析を行うcsv-parseを利用します。

{
  "devDependencies": {
    "typescript": "^3.3.3333",
    "@types/node": "^11.9.5",
    "@types/csv-parse": "^1.1.12"
  },
  "dependencies": {
    "aws-sdk": "^2.409.0",
    "csv-parse": "^4.3.3"
  }
}

実際のコードです。

import * as AWS from 'aws-sdk';
import parseSync = require('csv-parse/lib/sync');

/** S3からファイルの読み込み */
/** バケット名埋め込みの部分は適宜改変を */
async function s3get(key:string):Promise<any> {
    const s3 = new AWS.S3();
    const bucket = 's3-bucket-name';
    if ( key ) {
        return s3.getObject({
        Bucket: bucket,
        Key: `${key}`
        }).promise();
    }
}

/** 祝日・休日定義ファイルをパースして一致する日付があるか判定する */
/** 数値で始まらない行は読み飛ばし */
async function isNationalHoliday(testDate:Date) {
    try {
        const data = await s3get('syukujitsu.csv');
        const csvdata = data.Body.toString('utf-8');
        const records = parseSync( csvdata );
        for( const record of records ) {
            if( record[0].toString().match( '^[0-9]+' ) === null  ) { continue; }

            const nationalHoliday = new Date(record[0]);

            if( nationalHoliday.valueOf() === testDate.valueOf() ) {
                 return true;
             }
        }
        return false;
    } catch (error) {
        throw Error(error.message);
    }
}

/** 土曜日、日曜日は休日と判定 */
/** それ以外の曜日は定義ファイルから読み込んで判定 */
async function isHoliday(testDate:Date) {
    const day = testDate.getDay();
    if( day === 0 || day === 6 ) { return true; }
    if( await isNationalHoliday(testDate) ) { return true; };

    return false;
}

/** コマンドライン引数の日付の休日・祝日判定結果を返す */
async function main() {
    let argv:string = '';
    if( process.argv.length === 3 ) {
        argv = process.argv[2];
        if( Date.parse(argv) ) {
            let checkDate = new Date(argv);
            console.log(await isHoliday(checkDate));
        }
    }
}

main();

まとめ

日本の祝日の正確な定義は、内閣府のサイトにあるCSVファイルを利用する方がよいこと、その実装例をご紹介しました。

お知らせ

弊社ではAmazon Connectのキャンペーンを行なっております。

今週と来週に「無料Amazon Connectハンズオンセミナー」を開催致します。導入を検討されておられる方は、是非、お申し込み下さい。

【3月 東京】「1時間でクラウド型コンタクトセンターを構築できるようになる!無料Amazon Connectハンズオンセミナー」を開催します

また音声を中心とした各種ソリューションの開発支援も行なっております。

Photo on VisualHunt