https://github.com/python/cpython/commit/a11ab0920c720b36d9f0d962c1bbb4df9f513e43 commit: a11ab0920c720b36d9f0d962c1bbb4df9f513e43 branch: main author: Maurycy Pawłowski-Wieroński <[email protected]> committer: StanFromIreland <[email protected]> date: 2026-08-29T09:30:57+01:00 summary:
gh-148085: `datetime` cache `time` module lookups (#148088) Co-authored-by: Stan Ulbrych <[email protected]> files: A Misc/NEWS.d/next/Library/2026-04-04-14-25-43.gh-issue-148085.o97yTo.rst M Modules/_datetimemodule.c 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..55cb29a4e83a4c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-04-14-25-43.gh-issue-148085.o97yTo.rst @@ -0,0 +1,6 @@ +Speed up :meth:`~datetime.date.timetuple` and :meth:`~datetime.date.strftime` +by caching the ``time.struct_time`` and ``time.strftime`` lookups, and +:meth:`~datetime.datetime.today` by reading the clock directly instead of +calling :func:`time.time`. Patching :mod:`time` after importing +:mod:`datetime` no longer affects these methods. Patch by Maurycy +Pawłowski-Wieroński. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index cd02b298b406e6..ec71a89a2b04ec 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -52,6 +52,9 @@ typedef struct { /* The interned Unix epoch datetime instance */ PyObject *epoch; + + PyObject *time_struct_time; + PyObject *time_strftime; } datetime_state; /* The module has a fixed number of static objects, due to being exposed @@ -1892,10 +1895,12 @@ 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; } + 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 @@ -2055,7 +2060,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: @@ -2068,41 +2073,26 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, * from C. Perhaps they should be. */ -/* Call time.time() and return its result (a Python float). */ -static PyObject * -time_time(void) -{ - PyObject *result = NULL; - PyObject *time = PyImport_ImportModuleAttrString("time", "time"); - - if (time != NULL) { - result = PyObject_CallNoArgs(time); - Py_DECREF(time); - } - return result; -} - /* Build a time.struct_time. The weekday and day number are automatically * computed from the y,m,d args. */ 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; } - 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; } @@ -3337,7 +3327,11 @@ datetime_date_today_impl(PyTypeObject *type) type); } - PyObject *time = time_time(); + PyTime_t ts; + if (PyTime_Time(&ts) < 0) { + return NULL; + } + PyObject *time = PyFloat_FromDouble(PyTime_AsSecondsDouble(ts)); if (time == NULL) { return NULL; } @@ -7449,6 +7443,8 @@ 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_struct_time = Py_NewRef(st_old->time_struct_time), + .time_strftime = Py_NewRef(st_old->time_strftime), }; return 0; } @@ -7493,6 +7489,15 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module) return -1; } + st->time_struct_time = PyImport_ImportModuleAttrString("time", "struct_time"); + if (st->time_struct_time == NULL) { + return -1; + } + st->time_strftime = PyImport_ImportModuleAttrString("time", "strftime"); + if (st->time_strftime == NULL) { + return -1; + } + return 0; } @@ -7502,6 +7507,9 @@ traverse_state(datetime_state *st, visitproc visit, void *arg) /* heap types */ Py_VISIT(st->isocalendar_date_type); + Py_VISIT(st->time_struct_time); + Py_VISIT(st->time_strftime); + return 0; } @@ -7517,6 +7525,8 @@ 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_struct_time); + Py_CLEAR(st->time_strftime); return 0; } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
