home changes contents help options

202:休日を判定する

Python でアプリを作っているときの「休日」の意味は作成中のアプリの仕様によりさまざまと思われます。 このため、その手助けとなる曜日判定と祝日判定の方法について説明することにします。

説明の前にコードの例示を

土日と祝日すべてを休日、それ以外は休日ではない、 としたときの休日判別関数の作成例です。 動作には jholiday モジュールが必要です。

import jholiday

def is_saturday_or_sunday(date):
    if 4 < date.weekday() < 7:
        return True
    return False

def is_holiday(date):
    if is_saturday_or_sunday(date) or \
       jholiday.holiday_name(date.year, date.month, date.day):
        return True
    return False

>>> import datetime
>>> is_holiday(datetime.date(2008, 1, 1))  # 火曜・元日
True
>>> is_holiday(datetime.date(2008, 2, 23)) # 土曜
True
>>> is_holiday(datetime.date(2008, 2, 24)) # 日曜
True
>>> is_holiday(datetime.date(2008, 2, 27)) # 水曜 orz
False

まず is_suturday_or_sunday 関数で土日かどうかを判別し、 その後、 jholiday.holiday_name 関数で国民の祝日かどうかを判別しています。

曜日判定

datetime

年月日を表すのに datetime.date オブジェクトを使っている場合は weekday メソッド、 isoweekday メソッドを使います。 weekday は月曜なら 0 、火曜なら 1 、水曜なら 2 、…、日曜なら 7 を返します。 isoweekday は日曜が 0 、土曜が 7 です。 2008 年 2 月 27 日(水)で試してみます。

>>> d = datetime.date(2008, 2, 27)
>>> d.weekday()
2

文字列に直す際には [u"月", u"火", u"水", u"木", u"金", u"土", u"日", ]? のようなリストを 用意しておくと便利かもしれません。

time

time モジュールの gmtime, localtime, strptime 関数にて得られる時刻値型 time.struct_time の曜日を確認するには tm_wday 属性を見ます。とりうる値は月曜 0 、火曜 1 、…、日曜 7 の整数です。

>>> d = time.strptime("Wed Feb 27 00:00:00 2008")
>>> d
(2008, 2, 27, 0, 0, 0, 2, 58, -1)
>>> d.tm_wday
2
>>> d[6]
2

Python 2.2 以前の時刻値は単なるタプル型であったらしいです。その名残なのかシーケンス風に 6 番目の要素にアクセスすることでも確認できます。

calendar

カレンダー関連の便利関数をまとめた calendar モジュールにも weekday 関数があります。 引数は年月日の 3 整数。戻り値はやはり月曜 0 、火曜 1 、…、日曜 7 の整数です。

>>> import calendar
>>> calendar.weekday(2008, 2, 27)
2

コードを試し書きしているコマンドライン上でふと、カレンダーを確認したくなったときには month 、 calendar 関数を使うことができます。 それぞれ 1 月分、 1 年分のカレンダーを模した文字列を返します。 ただし、デフォルトでは月曜が一番左にきてしまいます。 setfirstweekday(calendar.SUNDAY) で直すと見慣れた日本風カレンダーになります。

>>> print calendar.month(2008, 2)
   February 2008
Mo Tu We Th Fr Sa Su
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29
>>> calendar.setfirstweekday(calendar.SUNDAY)
>>> print calendar.month(2008, 2)
   February 2008
Su Mo Tu We Th Fr Sa
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29

GUI 環境が提供するカレンダー(や、あれば普段使っているスケジュール管理アプリ)を見たほうが 早いような気はします。常時 CUI 環境の方のためのネタでしょうか。 しかし、 Unix には cal コマンドが…。

祝日判定

jholiday

Add in Box の角田 桂一さん作の Excel アドイン『kt関数アドイン』というものがあります。 角田さんはこれで使われている 祝日判定ロジック を公開し、他言語移植版を募っています。 これに応じた blogSetomits の瀬戸口 光宏さんがこの 祝日判定ロジック を Python に移植してくれました。

Add in Box には html ドキュメント内のコードしかなく元のファイル名がわかりにくくなっています。 好きな名前で保存、使用しても問題ありませんが、オリジナルのファイル名は jholiday.py となっています。

使い方はいたってシンプルです。jholiday.holiday_name 関数に年、月、日を整数で渡します。 祝日ならば祝日名のユニコード文字列が、祝日でなければ None が返ります。

>>> import jholiday
>>> jholiday.holiday_name(2008, 1, 1)
u'\u5143\u65e5'
>>> print jholiday.holiday_name(2008, 1, 1)
元日
>>> jholiday.holiday_name(2008, 2, 27)
>>> print jholiday.holiday_name(2008, 2, 27)
None

ユニコード文字列(長さ 1 以上)は真なので 戻り値をそのまま祝日かどうかの条件判定文に使えます。

>>> if jholiday.holiday_name(2008, 1, 1):
...     print "祝日"
... else:
...     print "祝日以外"
...
祝日
>>> if jholiday.holiday_name(2008, 2, 27):
...     print "祝日"
... else:
...     print "祝日以外"
...
祝日以外

jholiday.holiday_name 関数は内部で 3 引数 year, month, day をもとに datetime.date(year, month, day) を呼び出しています。 これにより、年月日として使えない数を渡すと ValueError? 例外、数値以外(文字列など)を渡すと TypeError? 例外が 起こるようになっています。try, except 文で例外処理するときはこれらを捕まえてください。

>>> jholiday.holiday_name(-2008, 2, 27)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jholiday.py", line 83, in holiday_name
    date = datetime.date(year, month, day)
ValueError: year is out of range
>>> jholiday.holiday_name(2008, 2, "27")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jholiday.py", line 83, in holiday_name
    date = datetime.date(year, month, day)
TypeError: an integer is required

年月日を datetime.date で扱っている場合を考えます。 jholiday.holiday_name 関数は年月日の整数しか受け付けてくれないため、いちど整数に直して渡します。 しかし、内部では date 型に変換を行うので、ここがムダとなります。

>>> import datetime
>>> import jholiday
>>> date = datetime.date(2009, 1, 1)
>>> jholiday.holiday_name(date.year, date.month, date.day)
u'\u5143\u65e5'

date 型を直接引数にとるバージョンを作ってみるのもよいでしょう。