Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-time-machine for
openSUSE:Factory checked in at 2026-07-02 22:13:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-time-machine (Old)
and /work/SRC/openSUSE:Factory/.python-time-machine.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-time-machine"
Thu Jul 2 22:13:52 2026 rev:21 rq:1363122 version:3.2.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-time-machine/python-time-machine.changes
2026-01-29 17:44:51.541115221 +0100
+++
/work/SRC/openSUSE:Factory/.python-time-machine.new.1982/python-time-machine.changes
2026-07-02 22:19:08.980865556 +0200
@@ -1,0 +2,6 @@
+Thu Jul 2 06:58:33 UTC 2026 - Dirk Müller <[email protected]>
+
+- add python-315-datetime-patching.patch to fix python 3.15+
+ support
+
+-------------------------------------------------------------------
New:
----
python-315-datetime-patching.patch
----------(New B)----------
New:
- add python-315-datetime-patching.patch to fix python 3.15+
support
----------(New E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-time-machine.spec ++++++
--- /var/tmp/diff_new_pack.4oAhl5/_old 2026-07-02 22:19:09.800893727 +0200
+++ /var/tmp/diff_new_pack.4oAhl5/_new 2026-07-02 22:19:09.800893727 +0200
@@ -25,6 +25,8 @@
URL: https://github.com/adamchainz/time-machine
# pypi packages don't contain the tests anymore since 2.2.0, see changelog
Source:
https://github.com/adamchainz/time-machine/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+# PATCH-FIX-UPSTREAM: Add date.today() and datetime.today() patching for
Python 3.15+
+Patch:
https://github.com/adamchainz/time-machine/pull/618.patch#/python-315-datetime-patching.patch
BuildRequires: %{python_module devel >= 3.8}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools}
@@ -50,7 +52,7 @@
It can be used independently, as a function decorator, or as a context manager.
%prep
-%setup -q -n time-machine-%{version}
+%autosetup -p1 -n time-machine-%{version}
%build
%pyproject_wheel
++++++ python-315-datetime-patching.patch ++++++
>From 164cc84a398202df7b45a9c41411602f8a1d67c7 Mon Sep 17 00:00:00 2001
From: Lumir Balhar <[email protected]>
Date: Thu, 2 Apr 2026 12:49:00 +0200
Subject: [PATCH] Add date.today() and datetime.today() patching for Python
3.15+
Since datetime.datetime.today() inherits from date.today(), we patch
date.today() once with a unified wrapper that checks the calling class
type and dispatches to the appropriate function. This ensures both
date.today() and datetime.today() work correctly and return the right type.
Related to:
https://github.com/python/cpython/commit/fc3e22a06c9b7c93ee379b5554a40a275745e15f
Fixes: https://github.com/adamchainz/time-machine/issues/610
---
src/_time_machine.c | 132 +++++++++++++++++++++++++++++++++++
src/time_machine/__init__.py | 21 ++++++
2 files changed, 153 insertions(+)
diff --git a/src/_time_machine.c b/src/_time_machine.c
index da8fd4cb..ed6e3866 100644
--- a/src/_time_machine.c
+++ b/src/_time_machine.c
@@ -8,8 +8,10 @@ typedef struct {
PyObject *datetime_module;
PyObject *time_module;
PyObject *datetime_class;
+ PyObject *date_class;
PyCFunctionObject *datetime_datetime_now;
PyCFunctionObject *datetime_datetime_utcnow;
+ PyCFunctionObject *date_today;
PyCFunctionObject *time_clock_gettime;
PyCFunctionObject *time_clock_gettime_ns;
PyCFunctionObject *time_gmtime;
@@ -24,6 +26,8 @@ typedef struct {
_PyCFunctionFastWithKeywords original_now;
#endif
PyCFunction original_utcnow;
+ PyCFunction original_date_today;
+ PyCFunction original_datetime_today;
PyCFunction original_clock_gettime;
PyCFunction original_clock_gettime_ns;
PyCFunction original_gmtime;
@@ -130,6 +134,96 @@ PyDoc_STRVAR(original_utcnow_doc,
\n\
Call datetime.datetime.utcnow() after patching.");
+/* datetime.date.today() and datetime.datetime.today()
+ * Note: datetime.datetime doesn't define its own today(), it inherits from
date.
+ * So we patch date.today() with a wrapper that checks the calling class type.
+ */
+
+static PyObject *
+_time_machine_today(PyObject *cls, PyObject *args)
+{
+ PyObject *time_machine_module = PyImport_ImportModule("time_machine");
+ if (time_machine_module == NULL) {
+ return NULL; // Propagate ImportError
+ }
+ PyObject *datetime_module = PyImport_ImportModule("datetime");
+ if (datetime_module == NULL) {
+ Py_DECREF(time_machine_module);
+ return NULL; // Propagate ImportError
+ }
+ PyObject *datetime_class = PyObject_GetAttrString(datetime_module,
"datetime");
+ if (datetime_class == NULL) {
+ Py_DECREF(datetime_module);
+ Py_DECREF(time_machine_module);
+ return NULL; // Propagate AttributeError
+ }
+
+ /* Check if cls is datetime.datetime (or subclass) vs just date */
+ int is_datetime = PyObject_IsSubclass(cls, datetime_class);
+ if (is_datetime == -1) {
+ Py_DECREF(datetime_class);
+ Py_DECREF(datetime_module);
+ Py_DECREF(time_machine_module);
+ return NULL; // Propagate error
+ }
+
+ const char *func_name = (is_datetime == 1) ? "datetime_today" :
"date_today";
+ PyObject *time_machine_func = PyObject_GetAttrString(time_machine_module,
func_name);
+ if (time_machine_func == NULL) {
+ Py_DECREF(datetime_class);
+ Py_DECREF(datetime_module);
+ Py_DECREF(time_machine_module);
+ return NULL; // Propagate AttributeError
+ }
+
+ PyObject *result = PyObject_CallObject(time_machine_func, args);
+
+ Py_DECREF(time_machine_func);
+ Py_DECREF(datetime_class);
+ Py_DECREF(datetime_module);
+ Py_DECREF(time_machine_module);
+
+ return result;
+}
+
+static PyObject *
+_time_machine_original_date_today(PyObject *module, PyObject *args)
+{
+ _time_machine_state *state = get_time_machine_state(module);
+
+ if (state->original_date_today == NULL) {
+ PyErr_SetString(PyExc_ValueError, "Not currently time-travelling.");
+ return NULL;
+ }
+
+ PyObject *result = state->original_date_today(state->date_class, args);
+
+ return result;
+}
+PyDoc_STRVAR(original_date_today_doc,
+ "original_date_today() -> date\n\
+\n\
+Call datetime.date.today() after patching.");
+
+static PyObject *
+_time_machine_original_datetime_today(PyObject *module, PyObject *args)
+{
+ _time_machine_state *state = get_time_machine_state(module);
+
+ if (state->original_datetime_today == NULL) {
+ PyErr_SetString(PyExc_ValueError, "Not currently time-travelling.");
+ return NULL;
+ }
+
+ PyObject *result = state->original_datetime_today(state->datetime_class,
args);
+
+ return result;
+}
+PyDoc_STRVAR(original_datetime_today_doc,
+ "original_datetime_today() -> datetime\n\
+\n\
+Call datetime.datetime.today() after patching.");
+
/* time.clock_gettime() */
static PyObject *
@@ -458,6 +552,15 @@ _time_machine_patch(PyObject *module, PyObject *unused)
state->original_utcnow = state->datetime_datetime_utcnow->m_ml->ml_meth;
state->datetime_datetime_utcnow->m_ml->ml_meth = _time_machine_utcnow;
+ /* Patch today() - since datetime.datetime.today() inherits from
date.today(),
+ * we only need to patch date.today() once. Save both originals first for
+ * the escape hatch, then patch with a unified wrapper that dispatches
based
+ * on the calling class type.
+ */
+ state->original_date_today = state->date_today->m_ml->ml_meth;
+ state->original_datetime_today = state->date_today->m_ml->ml_meth;
+ state->date_today->m_ml->ml_meth = _time_machine_today;
+
/*
time.clock_gettime(), only available on Unix platforms.
*/
@@ -517,6 +620,10 @@ _time_machine_unpatch(PyObject *module, PyObject *unused)
state->datetime_datetime_utcnow->m_ml->ml_meth = state->original_utcnow;
state->original_utcnow = NULL;
+ state->date_today->m_ml->ml_meth = state->original_date_today;
+ state->original_date_today = NULL;
+ state->original_datetime_today = NULL;
+
/*
time.clock_gettime(), only available on Unix platforms.
*/
@@ -566,6 +673,14 @@ static PyMethodDef module_functions[] = {
(PyCFunction)_time_machine_original_utcnow,
METH_NOARGS,
original_utcnow_doc},
+ {"original_date_today",
+ (PyCFunction)_time_machine_original_date_today,
+ METH_NOARGS,
+ original_date_today_doc},
+ {"original_datetime_today",
+ (PyCFunction)_time_machine_original_datetime_today,
+ METH_NOARGS,
+ original_datetime_today_doc},
#if PY_VERSION_HEX >= 0x030d00a2
{"original_clock_gettime",
(PyCFunction)_time_machine_original_clock_gettime,
@@ -637,6 +752,17 @@ _time_machine_exec(PyObject *module)
goto error;
}
+ state->date_class = PyObject_GetAttrString(state->datetime_module, "date");
+ if (state->date_class == NULL) {
+ goto error;
+ }
+
+ state->date_today =
+ (PyCFunctionObject *)PyObject_GetAttrString(state->date_class,
"today");
+ if (state->date_today == NULL) {
+ goto error;
+ }
+
state->time_module = PyImport_ImportModule("time");
if (state->time_module == NULL) {
goto error;
@@ -700,8 +826,10 @@ _time_machine_exec(PyObject *module)
error:
Py_CLEAR(state->datetime_module);
Py_CLEAR(state->datetime_class);
+ Py_CLEAR(state->date_class);
Py_CLEAR(state->datetime_datetime_now);
Py_CLEAR(state->datetime_datetime_utcnow);
+ Py_CLEAR(state->date_today);
Py_CLEAR(state->time_module);
Py_CLEAR(state->time_clock_gettime);
Py_CLEAR(state->time_clock_gettime_ns);
@@ -719,8 +847,10 @@ _time_machine_traverse(PyObject *module, visitproc visit,
void *arg)
_time_machine_state *state = get_time_machine_state(module);
Py_VISIT(state->datetime_module);
Py_VISIT(state->datetime_class);
+ Py_VISIT(state->date_class);
Py_VISIT(state->datetime_datetime_now);
Py_VISIT(state->datetime_datetime_utcnow);
+ Py_VISIT(state->date_today);
Py_VISIT(state->time_module);
Py_VISIT(state->time_clock_gettime);
Py_VISIT(state->time_clock_gettime_ns);
@@ -738,8 +868,10 @@ _time_machine_clear(PyObject *module)
_time_machine_state *state = get_time_machine_state(module);
Py_CLEAR(state->datetime_module);
Py_CLEAR(state->datetime_class);
+ Py_CLEAR(state->date_class);
Py_CLEAR(state->datetime_datetime_now);
Py_CLEAR(state->datetime_datetime_utcnow);
+ Py_CLEAR(state->date_today);
Py_CLEAR(state->time_module);
Py_CLEAR(state->time_clock_gettime);
Py_CLEAR(state->time_clock_gettime_ns);
diff --git a/src/time_machine/__init__.py b/src/time_machine/__init__.py
index 8903187a..a34c81c0 100644
--- a/src/time_machine/__init__.py
+++ b/src/time_machine/__init__.py
@@ -413,6 +413,16 @@ def utcnow() -> dt.datetime:
return dt.datetime.fromtimestamp(time(),
dt.timezone.utc).replace(tzinfo=None)
+def date_today() -> dt.date:
+ # Convert timestamp to date in local timezone
+ return dt.datetime.fromtimestamp(time()).date()
+
+
+def datetime_today() -> dt.datetime:
+ # Convert timestamp to datetime in local timezone
+ return dt.datetime.fromtimestamp(time())
+
+
# time module
@@ -544,10 +554,21 @@ def utcnow(self) -> dt.datetime:
result: dt.datetime = _time_machine.original_utcnow()
return result
+ def today(self) -> dt.datetime:
+ result: dt.datetime = _time_machine.original_datetime_today()
+ return result
+
+
+class _EscapeHatchDatetimeDate:
+ def today(self) -> dt.date:
+ result: dt.date = _time_machine.original_date_today()
+ return result
+
class _EscapeHatchDatetime:
def __init__(self) -> None:
self.datetime = _EscapeHatchDatetimeDatetime()
+ self.date = _EscapeHatchDatetimeDate()
class _EscapeHatchTime: