銀月の符号

Python 使い見習いの日記・雑記

祝日判定 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)
勤労感謝の日

ダウンロード

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()