mirror of
https://github.com/gentoo-mirror/gentoo.git
synced 2026-09-17 02:39:08 -07:00
Signed-off-by: Alfred Wingate <parona@protonmail.com> Part-of: https://codeberg.org/gentoo/gentoo/pulls/1582 Signed-off-by: Michał Górny <mgorny@gentoo.org>
238 lines
8.0 KiB
Diff
238 lines
8.0 KiB
Diff
https://github.com/adamchainz/time-machine/issues/610
|
|
https://github.com/adamchainz/time-machine/pull/618
|
|
https://github.com/adamchainz/time-machine/commit/83c385186424c920228cd38d819f2488de617be2
|
|
|
|
From 83c385186424c920228cd38d819f2488de617be2 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Lum=C3=ADr=20=27Frenzy=27=20Balhar?=
|
|
<frenzy.madness@gmail.com>
|
|
Date: Wed, 29 Jul 2026 23:52:51 +0200
|
|
Subject: [PATCH] Mock `datetime.date.today()` directly (#618)
|
|
|
|
Co-authored-by: Adam Johnson <me@adamj.eu>
|
|
--- a/docs/usage.rst
|
|
+++ b/docs/usage.rst
|
|
@@ -89,7 +89,9 @@ Main API
|
|
|
|
All datetime functions in the standard library are mocked to move to the destination current datetime:
|
|
|
|
+ * ``datetime.date.today()``
|
|
* ``datetime.datetime.now()``
|
|
+ * ``datetime.datetime.today()``
|
|
* ``datetime.datetime.utcnow()``
|
|
* ``time.clock_gettime()`` (only for ``CLOCK_REALTIME``)
|
|
* ``time.clock_gettime_ns()`` (only for ``CLOCK_REALTIME``)
|
|
@@ -355,6 +357,8 @@ Main API
|
|
with time_machine.travel(...):
|
|
...
|
|
|
|
+.. _escape-hatch:
|
|
+
|
|
Escape hatch API
|
|
================
|
|
|
|
@@ -373,10 +377,18 @@ The functions are:
|
|
|
|
Returns ``True`` if ``time_machine.travel()`` is active, ``False`` otherwise.
|
|
|
|
+* ``escape_hatch.datetime.date.today()``
|
|
+
|
|
+ Wraps the real ``datetime.date.today()``.
|
|
+
|
|
* ``escape_hatch.datetime.datetime.now()``
|
|
|
|
Wraps the real ``datetime.datetime.now()``.
|
|
|
|
+* ``escape_hatch.datetime.datetime.today()``
|
|
+
|
|
+ Wraps the real ``datetime.datetime.today()``.
|
|
+
|
|
* ``escape_hatch.datetime.datetime.utcnow()``
|
|
|
|
Wraps the real ``datetime.datetime.utcnow()``.
|
|
--- a/src/_time_machine.c
|
|
+++ b/src/_time_machine.c
|
|
@@ -10,6 +10,7 @@ typedef struct {
|
|
PyObject *datetime_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 +25,7 @@ typedef struct {
|
|
_PyCFunctionFastWithKeywords original_now;
|
|
#endif
|
|
PyCFunction original_utcnow;
|
|
+ PyCFunction original_date_today;
|
|
PyCFunction original_clock_gettime;
|
|
PyCFunction original_clock_gettime_ns;
|
|
PyCFunction original_gmtime;
|
|
@@ -130,6 +132,33 @@ 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 calls cls.fromtimestamp(), which
|
|
+ * returns the right type for date, datetime, and subclasses of either.
|
|
+ */
|
|
+
|
|
+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 *timestamp = PyObject_CallMethod(time_machine_module, "time", NULL);
|
|
+ Py_DECREF(time_machine_module);
|
|
+ if (timestamp == NULL) {
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ PyObject *result = PyObject_CallMethod(cls, "fromtimestamp", "O", timestamp);
|
|
+
|
|
+ Py_DECREF(timestamp);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
/* time.clock_gettime() */
|
|
|
|
static PyObject *
|
|
@@ -446,6 +475,9 @@ _time_machine_patch(PyObject *module, PyObject *unused)
|
|
if (state->original_time)
|
|
Py_RETURN_NONE;
|
|
|
|
+ state->original_date_today = state->date_today->m_ml->ml_meth;
|
|
+ state->date_today->m_ml->ml_meth = _time_machine_today;
|
|
+
|
|
#if PY_VERSION_HEX >= 0x030d00a4
|
|
state->original_now =
|
|
(PyCFunctionFastWithKeywords)state->datetime_datetime_now->m_ml->ml_meth;
|
|
@@ -517,6 +549,9 @@ _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;
|
|
+
|
|
/*
|
|
time.clock_gettime(), only available on Unix platforms.
|
|
*/
|
|
@@ -637,6 +672,17 @@ _time_machine_exec(PyObject *module)
|
|
goto error;
|
|
}
|
|
|
|
+ PyObject *date_class = PyObject_GetAttrString(state->datetime_module, "date");
|
|
+ if (date_class == NULL) {
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ state->date_today = (PyCFunctionObject *)PyObject_GetAttrString(date_class, "today");
|
|
+ Py_DECREF(date_class);
|
|
+ if (state->date_today == NULL) {
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
state->time_module = PyImport_ImportModule("time");
|
|
if (state->time_module == NULL) {
|
|
goto error;
|
|
@@ -702,6 +748,7 @@ _time_machine_exec(PyObject *module)
|
|
Py_CLEAR(state->datetime_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);
|
|
@@ -721,6 +768,7 @@ _time_machine_traverse(PyObject *module, visitproc visit, void *arg)
|
|
Py_VISIT(state->datetime_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);
|
|
@@ -740,6 +788,7 @@ _time_machine_clear(PyObject *module)
|
|
Py_CLEAR(state->datetime_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);
|
|
--- a/src/time_machine/__init__.py
|
|
+++ b/src/time_machine/__init__.py
|
|
@@ -535,11 +535,23 @@ def time_machine_fixture(
|
|
# escape hatch
|
|
|
|
|
|
+class _EscapeHatchDatetimeDate:
|
|
+ def today(self) -> dt.date:
|
|
+ # date.today() is equivalent to datetime.now().date().
|
|
+ result: dt.datetime = _time_machine.original_now(None)
|
|
+ return result.date()
|
|
+
|
|
+
|
|
class _EscapeHatchDatetimeDatetime:
|
|
def now(self, tz: dt.tzinfo | None = None) -> dt.datetime:
|
|
result: dt.datetime = _time_machine.original_now(tz)
|
|
return result
|
|
|
|
+ def today(self) -> dt.datetime:
|
|
+ # datetime.today() is equivalent to datetime.now() without a timezone.
|
|
+ result: dt.datetime = _time_machine.original_now(None)
|
|
+ return result
|
|
+
|
|
def utcnow(self) -> dt.datetime:
|
|
result: dt.datetime = _time_machine.original_utcnow()
|
|
return result
|
|
@@ -547,6 +559,7 @@ def utcnow(self) -> dt.datetime:
|
|
|
|
class _EscapeHatchDatetime:
|
|
def __init__(self) -> None:
|
|
+ self.date = _EscapeHatchDatetimeDate()
|
|
self.datetime = _EscapeHatchDatetimeDatetime()
|
|
|
|
|
|
--- a/tests/test_time_machine.py
|
|
+++ b/tests/test_time_machine.py
|
|
@@ -1317,6 +1317,17 @@ def test_is_travelling_true(self):
|
|
with time_machine.travel(EPOCH):
|
|
assert time_machine.escape_hatch.is_travelling() is True
|
|
|
|
+ def test_date_today(self):
|
|
+ real_today = dt.date.today()
|
|
+
|
|
+ with time_machine.travel(EPOCH):
|
|
+ eh_today = time_machine.escape_hatch.datetime.date.today()
|
|
+ assert eh_today >= real_today
|
|
+
|
|
+ with pytest.raises(ValueError) as excinfo:
|
|
+ time_machine.escape_hatch.datetime.date.today()
|
|
+ assert excinfo.value.args == ("Not currently time-travelling.",)
|
|
+
|
|
def test_datetime_now(self):
|
|
real_now = dt.datetime.now()
|
|
|
|
@@ -1335,6 +1346,17 @@ def test_datetime_now_tz(self):
|
|
eh_now = time_machine.escape_hatch.datetime.datetime.now(tz=dt.timezone.utc)
|
|
assert eh_now >= real_now
|
|
|
|
+ def test_datetime_today(self):
|
|
+ real_today = dt.datetime.today()
|
|
+
|
|
+ with time_machine.travel(EPOCH):
|
|
+ eh_today = time_machine.escape_hatch.datetime.datetime.today()
|
|
+ assert eh_today >= real_today
|
|
+
|
|
+ with pytest.raises(ValueError) as excinfo:
|
|
+ time_machine.escape_hatch.datetime.datetime.today()
|
|
+ assert excinfo.value.args == ("Not currently time-travelling.",)
|
|
+
|
|
def test_datetime_utcnow(self):
|
|
real_now = dt.datetime.utcnow()
|
|
|