summaryrefslogtreecommitdiff
path: root/dev-python/time-machine/files/time-machine-3.2.0-py3.15.patch
blob: 3c45ec3aff0b047682e3c4729c07a688cdd1d9eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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()