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]

Reply via email to