祝日判定 jholiday.py の C エクステンション
前回に引き続き、祝日判定を。
目的
ただ、 jholiday.py を爆速にしてみたかった(こういうの目的って呼ばないって)。
何十万件といった量を扱わない限り「一瞬」で終わる処理のため、自分には速くすることによるメリットはない。ただ、どこまで速くできるのか、という好奇心が勝ったので C エクステンションにしてみた。
結果
自分の環境にて 20 年分、約 7,300 日 を判定させたところ 約 2.7 倍の速度を持つことを確認。爆速と言えるほど速くはならなかった。残念。
追加機能
先日の私的改造版 jholiday.py にもあった、 holiday_name_date 関数を追加してみた。
>>> import cjholiday >>> print cjholiday.holiday_name(2009, 11, 23) 勤労感謝の日 >>> import datetime >>> d = datetime.date(2009, 11, 23) >>> print cjholiday.holiday_name_date(d) 勤労感謝の日
ダウンロード
- cjholiday ver 1.0.2 ソース (2009/4/23)
- cjholiday ver 1.0.2 Windows ・ Python 2.6 用バイナリ (2010/4/23)
Windows XP, VC++2008, Python 2.6.2 で作成と動作確認をしているが、他の環境でもコンパイルできると思う。 C ソースの文字コードと改行コードは環境に合わせたほうがよいかも。23:50追記。Ubuntu 8.04, gcc 4.2.4, Python 2.5.2 でのコンパイル&動作を確認。
更新履歴
- version 1.0.2
- version モジュール変数を追加。 1 月の 2000 年以降の箇所の if を else if にして、無駄な処理を省いた。正月の判定が速くなっている。
コード
cjholiday.c
#include <Python.h> #include <datetime.h> /* //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ //_/ //_/ CopyRight(C) K.Tsunoda(AddinBox) 2001 All Rights Reserved. //_/ ( http://www.h3.dion.ne.jp/~sakatsu/index.htm ) //_/ //_/ この祝日判定コードは『Excel:kt関数アドイン』で使用しているものです。 //_/ この関数では、2007年施行の改正祝日法(昭和の日)までを //_/ サポートしています(9月の国民の休日を含む)。 //_/ //_/ (*1)このコードを引用するに当たっては、必ずこのコメントも //_/ 一緒に引用する事とします。 //_/ (*2)他サイト上で本マクロを直接引用する事は、ご遠慮願います。 //_/ 【 http://www.h3.dion.ne.jp/~sakatsu/holiday_logic.htm 】 //_/ へのリンクによる紹介で対応して下さい。 //_/ (*3)[ktHolidayName]という関数名そのものは、各自の環境に //_/ おける命名規則に沿って変更しても構いません。 //_/ //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ */ /* * 追記 2009/11/18 fgshun http://d.hatena.ne.jp/fgshun/ * このコードは * SETOGUCHI Mitsuhiro (http://matatabi.homeip.net/) 氏のスクリプト * (http://www.h3.dion.ne.jp/~sakatsu/holiday_logic5.htm#Python) * を fgshun が C エクステンションとして組みなおしたものです。 */ static PyObject *DATE_CLASS; static PyObject *DELTA_DAY1; /* 元日 */ static PyObject *H_GANJITSU; static Py_UNICODE U_GANJITSU[] = { 0x5143, 0x65e5}; /* 成人の日 */ static PyObject *H_SEIJINNOHI; static Py_UNICODE U_SEIJINNOHI[] = { 0x6210, 0x4eba, 0x306e, 0x65e5}; /* 建国記念の日 */ static PyObject *H_KENKOKUKINENNOHI; static Py_UNICODE U_KENKOKUKINENNOHI[] = { 0x5efa, 0x56fd, 0x8a18, 0x5ff5, 0x306e, 0x65e5}; /* 春分の日 */ static PyObject *H_SHUNBUNNOHI; static Py_UNICODE U_SHUNBUNNOHI[] = { 0x6625, 0x5206, 0x306e, 0x65e5}; /* 昭和の日 */ static PyObject *H_SHOWANOHI; static Py_UNICODE U_SHOWANOHI[] = { 0x662d, 0x548c, 0x306e, 0x65e5}; /* 憲法記念日 */ static PyObject *H_KENPOKINENBI; static Py_UNICODE U_KENPOKINENBI[] = { 0x61b2, 0x6cd5, 0x8a18, 0x5ff5, 0x65e5}; /* みどりの日 */ static PyObject *H_MIDORINOHI; static Py_UNICODE U_MIDORINOHI[] = { 0x307f, 0x3069, 0x308a, 0x306e, 0x65e5}; /* こどもの日 */ static PyObject *H_KODOMONOHI; static Py_UNICODE U_KODOMONOHI[] = { 0x3053, 0x3069, 0x3082, 0x306e, 0x65e5}; /* 海の日 */ static PyObject *H_UMINOHI; static Py_UNICODE U_UMINOHI[] = { 0x6d77, 0x306e, 0x65e5}; /* 敬老の日 */ static PyObject *H_KEIRONOHI; static Py_UNICODE U_KEIRONOHI[] = { 0x656c, 0x8001, 0x306e, 0x65e5}; /* 秋分の日 */ static PyObject *H_SHUBUNNOHI; static Py_UNICODE U_SHUBUNNOHI[] = { 0x79cb, 0x5206, 0x306e, 0x65e5}; /* 体育の日 */ static PyObject *H_TAIKUNOHI; static Py_UNICODE U_TAIKUNOHI[] = { 0x4f53, 0x80b2, 0x306e, 0x65e5}; /* 文化の日 */ static PyObject *H_BUNKANOHI; static Py_UNICODE U_BUNKANOHI[] = { 0x6587, 0x5316, 0x306e, 0x65e5}; /* 勤労感謝の日 */ static PyObject *H_KINROKANSHANOHI; static Py_UNICODE U_KINROKANSHANOHI[] = { 0x52e4, 0x52b4, 0x611f, 0x8b1d, 0x306e, 0x65e5}; /* 天皇誕生日 */ static PyObject *H_TENNOTANJOBI; static Py_UNICODE U_TENNOTANJOBI[] = { 0x5929, 0x7687, 0x8a95, 0x751f, 0x65e5}; /* 振替休日 */ static PyObject *H_FURIKAEKYUJITSU; static Py_UNICODE U_FURIKAEKYUJITSU[] = { 0x632f, 0x66ff, 0x4f11, 0x65e5}; /* 国民の休日 */ static PyObject *H_KOKUMINNOKYUJITSU; static Py_UNICODE U_KOKUMINNOKYUJITSU[] = { 0x56fd, 0x6c11, 0x306e, 0x4f11, 0x65e5}; /* 皇太子明仁親王の結婚の儀 */ static PyObject *H_KOUTAISHIAKIHITOSHINNOUNOKEKKONNOGI; static Py_UNICODE U_KOUTAISHIAKIHITOSHINNOUNOKEKKONNOGI[] = { 0x7687, 0x592a, 0x5b50, 0x660e, 0x4ec1, 0x89aa, 0x738b, 0x306e, 0x7d50, 0x5a5a, 0x306e, 0x5100}; /* 昭和天皇の大喪の礼 */ static PyObject *H_SHOWATENNOUNOTAIMOUNOREI; static Py_UNICODE U_SHOWATENNOUNOTAIMOUNOREI[] = { 0x662d, 0x548c, 0x5929, 0x7687, 0x306e, 0x5927, 0x55aa, 0x306e, 0x793c}; /* 即位礼正殿の儀 */ static PyObject *H_SOKUIREISEIDENNOGI; static Py_UNICODE U_SOKUIREISEIDENNOGI[] = { 0x5373, 0x4f4d, 0x793c, 0x6b63, 0x6bbf, 0x306e, 0x5100}; /* 皇太子徳仁親王の結婚の儀 */ static PyObject *H_KOUTAISHINARUHITOSHINNOUNOKEKKONNOGI; static Py_UNICODE U_KOUTAISHINARUHITOSHINNOUNOKEKKONNOGI[] = { 0x7687, 0x592a, 0x5b50, 0x5fb3, 0x4ec1, 0x89aa, 0x738b, 0x306e, 0x7d50, 0x5a5a, 0x306e, 0x5100}; static int vernal_equinox(int year) { int day; if (year <= 1947){ day = 0; } else if (year <= 1979){ day = (int)(20.8357 + (0.242194 * (year - 1980)) - (year - 1983) / 4); } else if (year <= 2099){ day = (int)(20.8431 + (0.242194 * (year - 1980)) - (year - 1980) / 4); } else if (year <= 2150){ day = (int)(21.851 + (0.242194 * (year - 1980)) - (year - 1980) / 4); } else { day = 0; } return day; } static int autumn_equinox(int year) { int day; if (year <= 1947){ day = 0; } else if (year <= 1979){ day = (int)(23.2588 + (0.242194 * (year - 1980)) - (year - 1983) / 4); } else if (year <= 2099){ day = (int)(23.2488 + (0.242194 * (year - 1980)) - (year - 1980) / 4); } else if (year <= 2150){ day = (int)(24.2488 + (0.242194 * (year - 1980)) - (year - 1980) / 4); } else { day = 0; } return day; } static PyObject * holiday_name_date2(PyObject *date) { long year, month, day; long autumn; long weekday = -1; PyObject *name = Py_None; year = PyDateTime_GET_YEAR(date); month = PyDateTime_GET_MONTH(date); day = PyDateTime_GET_DAY(date); if (year < 1948) { Py_RETURN_NONE; } else if (year == 1948) { if (month < 7) { Py_RETURN_NONE; } else if (month == 7 && day < 20) { Py_RETURN_NONE; } } switch (month) { case 1: if (day == 1) { name = H_GANJITSU; } else if (year >= 2000) { if ((day - 1) / 7 == 1) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if (weekday == 0) { name = H_SEIJINNOHI; } } } else if (day == 15) { name = H_SEIJINNOHI; } break; case 2: if (day == 11 && year >= 1967) { name = H_KENKOKUKINENNOHI; } else if (year == 1989 && day == 24) { /* 1989/2/24 */ name = H_SHOWATENNOUNOTAIMOUNOREI; } break; case 3: if (day == vernal_equinox(year)) { name = H_SHUNBUNNOHI; } break; case 4: if (day == 29) { if (year >= 2007) { name = H_SHOWANOHI; } else if (year >= 1989) { name = H_MIDORINOHI; } else { name = H_TENNOTANJOBI; } } else if (year == 1959 && day == 10) { /* 1959/4/10 */ name = H_KOUTAISHIAKIHITOSHINNOUNOKEKKONNOGI; } break; case 5: if (day == 3) { name = H_KENPOKINENBI; } else if (day == 4) { if (year >= 2007) { name = H_MIDORINOHI; } else if (year >= 1986) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if (weekday != 0) { name = H_KOKUMINNOKYUJITSU; } } } else if (day == 5) { name = H_KODOMONOHI; } else if (day == 6) { if (year >= 2007) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if (weekday == 1 || weekday == 2) { name = H_FURIKAEKYUJITSU; } } } break; case 6: if (year == 1993 && day == 9) { /* 1993/6/9 */ name = H_KOUTAISHINARUHITOSHINNOUNOKEKKONNOGI; } break; case 7: if (year >= 2003) { if ((day - 1) / 7 == 2) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if (weekday == 0) { name = H_UMINOHI; } } } else if (year >= 1996 && day == 20) { name = H_UMINOHI; } break; case 9: autumn = autumn_equinox(year); if (day == autumn) { name = H_SHUBUNNOHI; } else { if (year >= 2003) { PyObject *weekday_py; weekday_py = PyObject_CallMethod( date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if ((day - 1) / 7 == 2 && weekday == 0) { name = H_KEIRONOHI; } else if (weekday == 1 && day == autumn - 1) { name = H_KOKUMINNOKYUJITSU; } } else if (year >= 1966 && day == 15) { name = H_KEIRONOHI; } } break; case 10: if (year >= 2000) { if ((day - 1) / 7 == 1) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); if (weekday == 0) { name = H_TAIKUNOHI; } } } else if (year >= 1966 && day == 10) { name = H_TAIKUNOHI; } break; case 11: if (day == 3) { name = H_BUNKANOHI; } else if (day == 23) { name = H_KINROKANSHANOHI; } else if (year == 1990 && day == 12) { name = H_SOKUIREISEIDENNOGI; } break; case 12: if (day == 23 && year >= 1989) { name = H_TENNOTANJOBI; } } if (name == Py_None) { if (weekday < 0) { PyObject *weekday_py; weekday_py = PyObject_CallMethod(date, "weekday", NULL); if (weekday_py == NULL) return NULL; weekday = PyInt_AS_LONG(weekday_py); Py_DECREF(weekday_py); } if (weekday == 0) { PyObject *prev, *prev_name; int prev_bool; prev = PyNumber_Subtract(date, DELTA_DAY1); if (prev == NULL) { return NULL; } prev_name = holiday_name_date2(prev); if (prev_name == NULL) { Py_DECREF(prev); return NULL; } prev_bool = PyObject_IsTrue(prev_name); if (prev_bool == 1) { name = H_FURIKAEKYUJITSU; } else if (prev_bool == -1) { Py_DECREF(prev); Py_DECREF(prev_name); return NULL; } Py_DECREF(prev); Py_DECREF(prev_name); } } Py_INCREF(name); return name; } static PyObject * holiday_name_date(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *date; static char *kwlist[] = {"date", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &date)) { return NULL; } if (!PyDate_Check(date)) { PyObject *r, *s; r = PyObject_Repr(date); if (r == NULL) { PyErr_SetString( PyExc_TypeError, "'date' argument is not datetime.date object"); return NULL; } s = PyString_FromFormat( "%s is not datetime.date", PyString_AS_STRING(r)); PyErr_SetObject(PyExc_TypeError, s); Py_DECREF(r); Py_DECREF(s); return NULL; } return holiday_name_date2(date); } static PyObject * holiday_name(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *date, *result; date = PyObject_Call(DATE_CLASS, args, kwargs); if (date == NULL) { return NULL; } result = holiday_name_date2(date); Py_DECREF(date); return result; } static PyMethodDef cjholiday_method[] = { {"holiday_name_date", (PyCFunction)holiday_name_date, METH_VARARGS | METH_KEYWORDS, "get name of holiday from datetime.date."}, {"holiday_name", (PyCFunction)holiday_name, METH_VARARGS | METH_KEYWORDS, "get name of holiday from ymd."}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initcjholiday(void) { PyObject *module; PyObject *datetime_module; module = Py_InitModule3( "cjholiday", cjholiday_method, "Holiday of Japan"); if (module == NULL) return; /* version */ if (PyModule_AddStringConstant(module, "version", "1.0.2")) return; /* 元日 */ if ((H_GANJITSU = PyUnicode_FromUnicode(U_GANJITSU, 2)) == NULL) return; /* 成人の日 */ if ((H_SEIJINNOHI = PyUnicode_FromUnicode(U_SEIJINNOHI, 4)) == NULL) return; /* 建国記念の日 */ if ((H_KENKOKUKINENNOHI = PyUnicode_FromUnicode(U_KENKOKUKINENNOHI, 6)) == NULL) return; /* 春分の日 */ if ((H_SHUNBUNNOHI = PyUnicode_FromUnicode(U_SHUNBUNNOHI, 4)) == NULL) return; /* 昭和の日 */ if ((H_SHOWANOHI = PyUnicode_FromUnicode(U_SHOWANOHI, 4)) == NULL) return; /* 憲法記念日 */ if ((H_KENPOKINENBI = PyUnicode_FromUnicode(U_KENPOKINENBI, 5)) == NULL) return; /* みどりの日 */ if ((H_MIDORINOHI = PyUnicode_FromUnicode(U_MIDORINOHI, 5)) == NULL) return; /* こどもの日 */ if ((H_KODOMONOHI = PyUnicode_FromUnicode(U_KODOMONOHI, 5)) == NULL) return; /* 海の日 */ if ((H_UMINOHI = PyUnicode_FromUnicode(U_UMINOHI, 3)) == NULL) return; /* 敬老の日 */ if ((H_KEIRONOHI = PyUnicode_FromUnicode(U_KEIRONOHI, 4)) == NULL) return; /* 秋分の日 */ if ((H_SHUBUNNOHI = PyUnicode_FromUnicode(U_SHUBUNNOHI, 4)) == NULL) return; /* 体育の日 */ if ((H_TAIKUNOHI = PyUnicode_FromUnicode(U_TAIKUNOHI, 4)) == NULL) return; /* 文化の日 */ if ((H_BUNKANOHI = PyUnicode_FromUnicode(U_BUNKANOHI, 4)) == NULL) return; /* 勤労感謝の日 */ if ((H_KINROKANSHANOHI = PyUnicode_FromUnicode(U_KINROKANSHANOHI, 6)) == NULL) return; /* 天皇誕生日 */ if ((H_TENNOTANJOBI = PyUnicode_FromUnicode(U_TENNOTANJOBI, 5)) == NULL) return; /* 振替休日 */ if ((H_FURIKAEKYUJITSU = PyUnicode_FromUnicode(U_FURIKAEKYUJITSU, 4)) == NULL) return; /* 国民の休日 */ if ((H_KOKUMINNOKYUJITSU = PyUnicode_FromUnicode(U_KOKUMINNOKYUJITSU, 5)) == NULL) return; /* 皇太子明仁親王の結婚の儀 */ if ((H_KOUTAISHIAKIHITOSHINNOUNOKEKKONNOGI = PyUnicode_FromUnicode(U_KOUTAISHIAKIHITOSHINNOUNOKEKKONNOGI, 12)) == NULL) return; /* 昭和天皇の大喪の礼 */ if ((H_SHOWATENNOUNOTAIMOUNOREI = PyUnicode_FromUnicode(U_SHOWATENNOUNOTAIMOUNOREI, 9)) == NULL) return; /* 即位礼正殿の儀 */ if ((H_SOKUIREISEIDENNOGI = PyUnicode_FromUnicode(U_SOKUIREISEIDENNOGI, 7)) == NULL) return; /* 皇太子徳仁親王の結婚の儀 */ if ((H_KOUTAISHINARUHITOSHINNOUNOKEKKONNOGI = PyUnicode_FromUnicode(U_KOUTAISHINARUHITOSHINNOUNOKEKKONNOGI, 12)) == NULL) return; /* datetime.date オブジェクトを得る */ datetime_module = PyImport_ImportModule("datetime"); if (datetime_module == NULL) { return; } DATE_CLASS = PyObject_GetAttrString(datetime_module, "date"); if (DATE_CLASS == NULL) { return; } Py_DECREF(datetime_module); /* datetime 関連の C API を使えるようにする */ PyDateTime_IMPORT; /* datetime.timedelta(days=1) */ if ((DELTA_DAY1 = PyDelta_FromDSU(1, 0, 0)) == NULL) return; return; } /* //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ //_/ CopyRight(C) K.Tsunoda(AddinBox) 2001 All Rights Reserved. //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ */
setup.py
from distutils.core import setup, Extension py_modules = ['test_cjholiday'] extensions = [Extension('cjholiday', sources = ['cjholiday.c'])] setup( name='cjholiday', version='1.0.2', author='fgshun', author_email='fgshun@lazy-moon.jp', url='http://www.lazy-moon.jp/', py_modules=py_modules, ext_modules=extensions)
test_cjholiday.py
jholiday.py と動作が違わないことを確認。
import unittest import datetime import jholiday import cjholiday def range_date(start, stop, step=datetime.timedelta(1)): date = start while date < stop: yield date date += step class HolidayNameTestCase(unittest.TestCase): def setUp(self): self.start = datetime.date(1946, 1, 1) self.end = datetime.date(2151, 1, 1) def test_holiday_name(self): j = jholiday.holiday_name c = cjholiday.holiday_name for date in range_date(self.start, self.end): self.assertEqual( j(date.year, date.month, date.day), c(date.year, date.month, date.day)) def test_holiday_name_date(self): j = jholiday.holiday_name c = cjholiday.holiday_name_date for date in range_date(self.start, self.end): self.assertEqual( j(date.year, date.month, date.day), c(date)) if __name__ == '__main__': unittest.main()