diff --git a/Misc/NEWS.d/next/Library/2026-04-04-14-25-43.gh-issue-148085.o97yTo.rst b/Misc/NEWS.d/next/Library/2026-04-04-14-25-43.gh-issue-148085.o97yTo.rst new file mode 100644 index 00000000000000..14dcfd479bc401 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-04-14-25-43.gh-issue-148085.o97yTo.rst @@ -0,0 +1,4 @@ +Cache ``time.time``, ``time.struct_time``, and ``time.strftime`` imports, +speeding up :meth:`~datetime.date.timetuple` and +:meth:`~datetime.date.strftime` by up to 2x. Patch by Maurycy +Pawłowski-Wieroński. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 163e499d957b2e..6347e456c7e30b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -52,6 +52,10 @@ typedef struct { /* The interned Unix epoch datetime instance */ PyObject *epoch; + + PyObject *time_time; + PyObject *time_struct_time; + PyObject *time_strftime; } datetime_state; /* The module has a fixed number of static objects, due to being exposed @@ -1879,10 +1883,19 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, assert(object && format && timetuple); assert(PyUnicode_Check(format)); - PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime"); - if (strftime == NULL) { + PyObject *current_mod = NULL; + datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { return NULL; } + if (st->time_strftime == NULL) { + st->time_strftime = PyImport_ImportModuleAttrString("time", "strftime"); + if (st->time_strftime == NULL) { + RELEASE_CURRENT_STATE(st, current_mod); + return NULL; + } + } + PyObject *strftime = st->time_strftime; /* Scan the input format, looking for %z/%Z/%f escapes, building * a new format. Since computing the replacements for those codes @@ -2042,7 +2055,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, Py_XDECREF(zreplacement); Py_XDECREF(colonzreplacement); Py_XDECREF(Zreplacement); - Py_XDECREF(strftime); + RELEASE_CURRENT_STATE(st, current_mod); return result; Error: @@ -2059,13 +2072,20 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, static PyObject * time_time(void) { - PyObject *result = NULL; - PyObject *time = PyImport_ImportModuleAttrString("time", "time"); - - if (time != NULL) { - result = PyObject_CallNoArgs(time); - Py_DECREF(time); + PyObject *current_mod = NULL; + datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { + return NULL; + } + if (st->time_time == NULL) { + st->time_time = PyImport_ImportModuleAttrString("time", "time"); + if (st->time_time == NULL) { + RELEASE_CURRENT_STATE(st, current_mod); + return NULL; + } } + PyObject *result = PyObject_CallNoArgs(st->time_time); + RELEASE_CURRENT_STATE(st, current_mod); return result; } @@ -2075,21 +2095,27 @@ time_time(void) static PyObject * build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag) { - PyObject *struct_time; - PyObject *result; - - struct_time = PyImport_ImportModuleAttrString("time", "struct_time"); - if (struct_time == NULL) { + PyObject *current_mod = NULL; + datetime_state *st = GET_CURRENT_STATE(current_mod); + if (st == NULL) { return NULL; } + if (st->time_struct_time == NULL) { + st->time_struct_time = PyImport_ImportModuleAttrString("time", "struct_time"); + if (st->time_struct_time == NULL) { + RELEASE_CURRENT_STATE(st, current_mod); + return NULL; + } + } - result = PyObject_CallFunction(struct_time, "((iiiiiiiii))", + PyObject *result = PyObject_CallFunction(st->time_struct_time, + "((iiiiiiiii))", y, m, d, hh, mm, ss, weekday(y, m, d), days_before_month(y, m) + d, dstflag); - Py_DECREF(struct_time); + RELEASE_CURRENT_STATE(st, current_mod); return result; } @@ -7414,6 +7440,9 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) .us_per_week = Py_NewRef(st_old->us_per_week), .seconds_per_day = Py_NewRef(st_old->seconds_per_day), .epoch = Py_NewRef(st_old->epoch), + .time_time = Py_XNewRef(st_old->time_time), + .time_struct_time = Py_XNewRef(st_old->time_struct_time), + .time_strftime = Py_XNewRef(st_old->time_strftime), }; return 0; } @@ -7458,6 +7487,10 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) return -1; } + st->time_time = NULL; + st->time_struct_time = NULL; + st->time_strftime = NULL; + return 0; } @@ -7467,6 +7500,10 @@ traverse_state(datetime_state *st, visitproc visit, void *arg) /* heap types */ Py_VISIT(st->isocalendar_date_type); + Py_VISIT(st->time_time); + Py_VISIT(st->time_struct_time); + Py_VISIT(st->time_strftime); + return 0; } @@ -7482,6 +7519,9 @@ clear_state(datetime_state *st) Py_CLEAR(st->us_per_week); Py_CLEAR(st->seconds_per_day); Py_CLEAR(st->epoch); + Py_CLEAR(st->time_time); + Py_CLEAR(st->time_struct_time); + Py_CLEAR(st->time_strftime); return 0; }