日本の休日 ========== Thunderbirdなどのカレンダーに日本の休日を表示したいと思った。 `Thunderbirdのwebサイト `__ には各国の休日の情報がiCalenderファイル(.ics)の形式で収められている。 しかし、日本の情報は2007年以降更新されていないが、2020-2021は東京オリンピックの影響で、 休日が臨時に変更されている。(その他の休日にも2007以降の変更がある。) 日本の休日については、内閣府がその\ `ホームページ `__ で公式なアナウンスを出している。特に、“昭和30年(1955年)から令和4年(2022年)国民の祝日”を\ `csv形式のファイル `__ として公開している。 このプログラムの目的はこのcsv形式のファイルからThunderbirdなどのCalendarプログラムで利用可能なiCal形式のファイル(.ics)を生成することです。 利用するモジュール ------------------ このプログラムでは以下のモジュールを使います。\ ``import``\ に失敗するようでしたら、\ ``pip``\ などのツールを使って準備しましょう。 urllib: 標準のwebアクセスのためのモジュール。csvファイルをダウンロードするために使用。 io: データをあたかもFile(stream)のように取り扱えるようにするモジュール。ダウンロードしたデータを、中間ファイルに保存しないで、メモリ上に置いたまま利用するため。 pandas: データ解析のためのモジュール。このなかのDataFrameを利用する。 csvファイルの日付データ変換などを効率よく(短いプログラムで)実現するために利用。 sqlite: ファイルベースのRDBMS sqliteを使うためのモジュール。データの検索などを簡単に行うために利用できる。本体のプログラムでは未利用なので、なくても良い。 icalendar: iCalデータ取扱のためのモジュール。icsファイル作成に使用する。 .. code:: ipython3 #!/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 データのダウンロード -------------------- データをwebアドレス(``cvsuri``)からダウンロードして、 1. 手元のファイルとして保存したり(``load_and_save_cvs``)、  2. pands DataFrameに変換して(``load_as_dataframe``)別のプログラムで利用したり、 3. sqlite3のon memory データベスに変換(``load_as db``)する 関数等をここで定義しています。 .. code:: ipython3 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 Calendar オブジェクト --------------------- iCalのファイルを作成する準備として、icalender.Calendarクラスを継承したCalendar Classを定義します。このクラスには休日(日付と休日名)を登録するための ``add_holiday`` メソッドを追加しておきます。 Calendearファイルはicanldear.Calendarクラスが持つ、\ ``to_ical``\ を使って書き出します。 .. code:: ipython3 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) プログラムの本体 ---------------- 上で用意した関数やクラスを使ってwebから取り込んだデータを使って、icsファイルを作成します。 .. code:: ipython3 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) #ファイルに書き出す。 プログラムの実行 ---------------- 完成したプログラムを単独で利用することを考えて、いつものおまじないをいれておきます。 .. code:: ipython3 if __name__ == "__main__": main() print("Done!") .. parsed-literal:: downloading : syukujitsu.csv 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!