Skip to content

Commit 7d22741

Browse files
committed
Add option for deterministic tick
I was somewhat surprised to find that `tick` wasn't deterministic and actually increments by time passed. At $WORK, this was causing some tests to become flakey and not reproducible. This commit adds an optional `tick_delta` argument such that it's possible to make the tick deterministic.
1 parent 980b93f commit 7d22741

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

README.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Usage
6363

6464
If you’re coming from freezegun or libfaketime, see also the below section on migrating.
6565

66-
``travel(destination, *, tick=True)``
66+
``travel(destination, *, tick=True, tick_delta=None)``
6767
-------------------------------------
6868

6969
``travel()`` is a class that allows time travel, to the datetime specified by ``destination``.
@@ -98,6 +98,16 @@ If ``True``, the default, successive calls to mocked functions return values inc
9898
So after starting travel to ``0.0`` (the UNIX epoch), the first call to any datetime function will return its representation of ``1970-01-01 00:00:00.000000`` exactly.
9999
The following calls "tick," so if a call was made exactly half a second later, it would return ``1970-01-01 00:00:00.500000``.
100100

101+
If ``tick`` is ``True``, setting ``tick_delta`` makes the tick deterministic, for example:
102+
103+
.. code-block:: python
104+
105+
with time_machine.travel(dt.datetime(2023, 1, 1), tick_delta=dt.timedelta(microseconds=1)):
106+
assert dt.datetime.now() == dt.datetime(2023, 1, 1, 0, 0, microsecond=0)
107+
assert dt.datetime.now() == dt.datetime(2023, 1, 1, 0, 0, microsecond=1)
108+
assert dt.datetime.now() == dt.datetime(2023, 1, 1, 0, 0, microsecond=2)
109+
...
110+
101111
Mocked Functions
102112
^^^^^^^^^^^^^^^^
103113

src/time_machine/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,14 @@ def __init__(
125125
destination_timestamp: float,
126126
destination_tzname: str | None,
127127
tick: bool,
128+
tick_delta: dt.timedelta | None = None,
128129
) -> None:
129130
self._destination_timestamp_ns = int(
130131
destination_timestamp * NANOSECONDS_PER_SECOND
131132
)
132133
self._destination_tzname = destination_tzname
133134
self._tick = tick
135+
self._tick_delta = tick_delta
134136
self._requested = False
135137

136138
def time(self) -> float:
@@ -140,6 +142,11 @@ def time_ns(self) -> int:
140142
if not self._tick:
141143
return self._destination_timestamp_ns
142144

145+
if self._tick_delta is not None:
146+
destination_timestamp_ns_before = self._destination_timestamp_ns
147+
self._destination_timestamp_ns += self._tick_delta.microseconds * 1000
148+
return destination_timestamp_ns_before
149+
143150
base = SYSTEM_EPOCH_TIMESTAMP_NS + self._destination_timestamp_ns
144151
now_ns: int = _time_machine.original_time_ns()
145152

@@ -200,11 +207,12 @@ def _stop(self) -> None:
200207

201208

202209
class travel:
203-
def __init__(self, destination: DestinationType, *, tick: bool = True) -> None:
210+
def __init__(self, destination: DestinationType, *, tick: bool = True, tick_delta: dt.timedelta | None = None) -> None:
204211
self.destination_timestamp, self.destination_tzname = extract_timestamp_tzname(
205212
destination
206213
)
207214
self.tick = tick
215+
self.tick_delta = tick_delta
208216

209217
def start(self) -> Coordinates:
210218
_time_machine.patch_if_needed()
@@ -217,6 +225,7 @@ def start(self) -> Coordinates:
217225
destination_timestamp=self.destination_timestamp,
218226
destination_tzname=self.destination_tzname,
219227
tick=self.tick,
228+
tick_delta=self.tick_delta,
220229
)
221230
coordinates_stack.append(coordinates)
222231
coordinates._start()

tests/test_time_machine.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,20 @@ def test_time_time_ns_no_tick():
337337
assert time.time_ns() == int(EPOCH * NANOSECONDS_PER_SECOND)
338338

339339

340+
def test_tick_delta() -> None:
341+
ts = list[dt.datetime]()
342+
with time_machine.travel(dt.datetime(2023, 1, 1), tick_delta=dt.timedelta(microseconds=1)):
343+
for _ in range(5):
344+
ts.append(dt.datetime.now())
345+
assert ts == [
346+
dt.datetime(2023, 1, 1, 0, 0, microsecond=0),
347+
dt.datetime(2023, 1, 1, 0, 0, microsecond=1),
348+
dt.datetime(2023, 1, 1, 0, 0, microsecond=2),
349+
dt.datetime(2023, 1, 1, 0, 0, microsecond=3),
350+
dt.datetime(2023, 1, 1, 0, 0, microsecond=4),
351+
]
352+
353+
340354
# all supported forms
341355

342356

0 commit comments

Comments
 (0)