4. 日本の休日

Thunderbirdなどのカレンダーに日本の休日を表示したいと思った。 Thunderbirdのwebサイト には各国の休日の情報がiCalenderファイル(.ics)の形式で収められている。 しかし、日本の情報は2007年以降更新されていないが、2020-2021は東京オリンピックの影響で、 休日が臨時に変更されている。(その他の休日にも2007以降の変更がある。)

日本の休日については、内閣府がそのホームページ で公式なアナウンスを出している。特に、“昭和30年(1955年)から令和4年(2022年)国民の祝日”をcsv形式のファイル として公開している。

このプログラムの目的はこのcsv形式のファイルからThunderbirdなどのCalendarプログラムで利用可能なiCal形式のファイル(.ics)を生成することです。

4.1. 利用するモジュール

このプログラムでは以下のモジュールを使います。importに失敗するようでしたら、pipなどのツールを使って準備しましょう。

urllib: 標準のwebアクセスのためのモジュール。csvファイルをダウンロードするために使用。

io: データをあたかもFile(stream)のように取り扱えるようにするモジュール。ダウンロードしたデータを、中間ファイルに保存しないで、メモリ上に置いたまま利用するため。

pandas: データ解析のためのモジュール。このなかのDataFrameを利用する。 csvファイルの日付データ変換などを効率よく(短いプログラムで)実現するために利用。

sqlite: ファイルベースのRDBMS sqliteを使うためのモジュール。データの検索などを簡単に行うために利用できる。本体のプログラムでは未利用なので、なくても良い。

icalendar: iCalデータ取扱のためのモジュール。icsファイル作成に使用する。

#!/usr/bin/env python
# coding: utf-8
"""
 This program generates ics file for the Japanese Holidays
 based on the information from Japanese goverment web page.
"""
import urllib.request
from urllib.request import urlopen
# macosではSSL証明書を正しくつかえるように以下の文が必要
import os, certifi
os.environ["SSL_CERT_FILE"]=certifi.where()

import io
from io import StringIO, BytesIO

import pandas

import sqlite3

import icalendar, pytz, datetime, uuid
from icalendar import vBoolean, vCalAddress, vDate, vDatetime,vDuration,vFloat, vFrequency, vInt, vPeriod, vText, vTime, vUri, vUTCOffset, vWeekday

JST = pytz.timezone("Asia/Tokyo")

# note: this csv uses shift-jis encoding.
JHuri="https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"
# a list of codecs supported by python can be found in https://docs.python.org/ja/3/library/codecs.html

4.2. データのダウンロード

データをwebアドレス(cvsuri)からダウンロードして、

  1. 手元のファイルとして保存したり(load_and_save_cvs)、

  2. pands DataFrameに変換して(load_as_dataframe)別のプログラムで利用したり、

  3. sqlite3のon memory データベスに変換(load_as db)する

関数等をここで定義しています。

def load_and_save_cvs(cvsuri):
    fn=cvsuri.split("/")[-1]
    with urlopen(cvsuri) as inf , open(fn,"wb") as outf:
        print ("downloading :", fn)
        data=inf.read()
        outf.write(data)

def load_as_dataframe(cvsuri):
    fn=cvsuri.split("/")[-1]
    with urlopen(cvsuri) as inf:
        print ("downloading :", fn)
        data=inf.read().decode("shift_jis") #file encoding in this csv file is shift JIS.
        sio=io.StringIO(data) # store data in to StringIO object.
        # interprete string in '国民の祝日・休日月日'(the first)  column as datetime.
        df=pandas.read_csv(sio, # use StringIO object as if it is a open file object.
                           parse_dates=['国民の祝日・休日月日'], # column 0 shoud be read as date
                           infer_datetime_format=True
                          )
        #  df=pandas.read_csv(sio, parse_dates=[0], infer_datetime_format=True)
    print(df.info())
    return df

def load_as_db(cvsuri):
    """
    download csv file then convert to sqlite3 database on memory.
    """
    df=load_as_dataframe(cvsuri) # create dataframe
    db=sqlite3.connect(":memory:") # create sqlite3 database on memory.
    df.to_sql('holidays', db) # write all data to database.

    return db

4.3. Calendar オブジェクト

iCalのファイルを作成する準備として、icalender.Calendarクラスを継承したCalendar Classを定義します。このクラスには休日(日付と休日名)を登録するための add_holiday メソッドを追加しておきます。 Calendearファイルはicanldear.Calendarクラスが持つ、to_icalを使って書き出します。

class Calendar(icalendar.Calendar):
    """
    """
    # notify tis event 15 min before event
    alarm_setting=icalendar.Alarm().from_ical(
        "BEGIN:VALARM\n"
        "ACTION:DISPLAY\n"
        "TRIGGER;VALUE=DURATION:-PT15M\n"
        "DESCRIPTION:Default Mozilla Description\n"
        "END:VALARM"
    )
    def __init__(self):
        """
        """
        super(icalendar.Calendar,self).__init__()
        self.add(u"prodid","-//Japanese Holidays/based on Python.iCalendar//")
        self.add(u"Version",2.0)
        self.add(u"Category","Japanese Public Holidays")
        self.add(u"Summary","Official Japanese Public Holidays ")
        self.add_component(self.alarm_setting)
        self.add_component(icalendar.Timezone.from_ical(
            "BEGIN:VTIMEZONE\n"
            "TZID:/mozilla.org/20070129_1/Asia/Tokyo\n"
            "X-LIC-LOCATION:Asia/Tokyo\n"
            "BEGIN:STANDARD\n"
            "TZOFFSETFROM:+0900\n"
            "TZOFFSETTO:+0900\n"
            "TZNAME:JST\n"
            "DTSTART:19700101T000000\n"
            "END:STANDARD\n"
            "END:VTIMEZONE\n")
        )

    def add_holiday(self, date, name):
        ev=icalendar.Event()
        ev['uid']=uuid.uuid4()
        ev["categories"]=vText("Japanese Public Holidays")
        ev.add(
            'dtstart',
            vDate(
                datetime.datetime.fromtimestamp(
                   date.timestamp(),
                 tz=JST)
            ),
            encode=False
        )
        ev.add(
            'dtend',
            vDate(
                datetime.datetime.fromtimestamp(
                (date + datetime.timedelta(days=1)).timestamp(),
                tz=JST)
            ),
            encode=False
        )
        ev.add(
            'summary',
            name
        )
        ev.add(
            'description',
            vText(name, 'utf-8')
        )
        self.add_component(ev)

4.4. プログラムの本体

上で用意した関数やクラスを使ってwebから取り込んだデータを使って、icsファイルを作成します。

def main():
    # web上のデータからDataFrameを作成。
    df=load_as_dataframe(JHuri)

    cal=Calendar() # Calendar オブジェクトを作成し、DataFrameの休日データを登録
    for id, date, name in df.itertuples():
            cal.add_holiday(date, name)
    #.icsファイルを作成し、データをiCalendar形式で書き出します。
    with open(
            "JapaneseHolidays.ics",
            mode="w",
            encoding="utf-8" # for py2app. you need explicitly specify encoding.
    ) as f:
        s=cal.to_ical() # icalendar 形式に変換。
        s=s.decode("utf-8", "replace") # unicodeに変換
        f.write(s) #ファイルに書き出す。

4.5. プログラムの実行

完成したプログラムを単独で利用することを考えて、いつものおまじないをいれておきます。

if __name__ == "__main__":
    main()
    print("Done!")
downloading : syukujitsu.csv
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 975 entries, 0 to 974
Data columns (total 2 columns):
 #   Column      Non-Null Count  Dtype
---  ------      --------------  -----
 0   国民の祝日・休日月日  975 non-null    datetime64[ns]
 1   国民の祝日・休日名称  975 non-null    object
dtypes: datetime64[ns](1), object(1)
memory usage: 15.4+ KB
None
Done!