From 6531024564f54f61a64dbea4aee87a26bf94b00b Mon Sep 17 00:00:00 2001 From: Nate Dudenhoeffer Date: Wed, 2 Nov 2022 20:30:48 -0500 Subject: [PATCH 1/4] Add support for dt.timedelta to travel class fixes #38 --- CHANGELOG.rst | 4 ++++ README.rst | 1 + src/time_machine/__init__.py | 7 +++++++ tests/test_time_machine.py | 22 ++++++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c524071..71b1598 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog ========= +* Add support for ``datetime.timedelta`` to ``time_machine.travel()``. + + Thanks to Nate Dudenhoeffer in `PR #298 `__. + * Add ``shift()`` method to the ``time_machine`` pytest fixture. Thanks to Stefaan Lippens in `PR #312 `__. diff --git a/README.rst b/README.rst index 755325c..dc64e98 100644 --- a/README.rst +++ b/README.rst @@ -80,6 +80,7 @@ It may be: * A ``float`` or ``int`` specifying a `Unix timestamp `__ * A string, which will be parsed with `dateutil.parse `__ and converted to a timestamp. Again, if the result is naive, it will be assumed to have the UTC time zone. +* A ``datetime.timedelta`` relative to the current real time. .. |zoneinfo-instance| replace:: ``zoneinfo.ZoneInfo`` instance .. _zoneinfo-instance: https://docs.python.org/3/library/zoneinfo.html#zoneinfo.ZoneInfo diff --git a/src/time_machine/__init__.py b/src/time_machine/__init__.py index c0d6bda..283e4dd 100644 --- a/src/time_machine/__init__.py +++ b/src/time_machine/__init__.py @@ -84,6 +84,7 @@ int, float, dt.datetime, + dt.timedelta, dt.date, str, ] @@ -124,6 +125,12 @@ def extract_timestamp_tzname( if dest.tzinfo is None: dest = dest.replace(tzinfo=dt.timezone.utc) timestamp = dest.timestamp() + elif isinstance(dest, dt.timedelta): + if coordinates_stack: + raise TypeError( + "timedelta destination is not supported when already time travelling." + ) + timestamp = time() + dest.total_seconds() elif isinstance(dest, dt.date): timestamp = dt.datetime.combine( dest, dt.time(0, 0), tzinfo=dt.timezone.utc diff --git a/tests/test_time_machine.py b/tests/test_time_machine.py index 9736be4..cbb50a3 100644 --- a/tests/test_time_machine.py +++ b/tests/test_time_machine.py @@ -464,6 +464,28 @@ def test_destination_generator(): assert time.time() == EPOCH + 13.0 +def test_destination_delta(): + now = time.time() + with time_machine.travel(dt.timedelta(seconds=3600)): + assert now + 3600 < time.time() < now + 3601 + + +def test_destination_negative_delta(): + now = time.time() + with time_machine.travel(dt.timedelta(seconds=-3600)): + assert now - 3600 < time.time() < now - 3599 + + +@time_machine.travel(0) +def test_destination_delta_raises(): + with pytest.raises(TypeError) as excinfo: + time_machine.travel(dt.timedelta(seconds=3600)) + + assert excinfo.value.args == ( + "Timedelta destination is not supported when already time travelling.", + ) + + def test_traveller_object(): traveller = time_machine.travel(EPOCH + 10.0) assert time.time() >= LIBRARY_EPOCH From 6febe060531f62158e659b10f19c72fdd1e0e1e5 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 19 Sep 2023 11:08:06 +0100 Subject: [PATCH 2/4] Use <= in asserts --- README.rst | 4 +++- tests/test_time_machine.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index dc64e98..88db11f 100644 --- a/README.rst +++ b/README.rst @@ -77,10 +77,12 @@ It may be: If it has ``tzinfo`` set to a |zoneinfo-instance|_, the current timezone will also be mocked. * A ``datetime.date``. This will be converted to a UTC datetime with the time 00:00:00. +* A ``datetime.timedelta``. + This will be interpreted relative to the current time. + If already within a ``travel()`` block, the ``shift()`` method is easier to use (documented below). * A ``float`` or ``int`` specifying a `Unix timestamp `__ * A string, which will be parsed with `dateutil.parse `__ and converted to a timestamp. Again, if the result is naive, it will be assumed to have the UTC time zone. -* A ``datetime.timedelta`` relative to the current real time. .. |zoneinfo-instance| replace:: ``zoneinfo.ZoneInfo`` instance .. _zoneinfo-instance: https://docs.python.org/3/library/zoneinfo.html#zoneinfo.ZoneInfo diff --git a/tests/test_time_machine.py b/tests/test_time_machine.py index cbb50a3..e612035 100644 --- a/tests/test_time_machine.py +++ b/tests/test_time_machine.py @@ -467,13 +467,13 @@ def test_destination_generator(): def test_destination_delta(): now = time.time() with time_machine.travel(dt.timedelta(seconds=3600)): - assert now + 3600 < time.time() < now + 3601 + assert now + 3600 <= time.time() <= now + 3601 def test_destination_negative_delta(): now = time.time() with time_machine.travel(dt.timedelta(seconds=-3600)): - assert now - 3600 < time.time() < now - 3599 + assert now - 3600 <= time.time() <= now - 3599 @time_machine.travel(0) From b8aa4ea1dcb0fab35f4d4e38752ea8a88775d7a2 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 19 Sep 2023 11:08:46 +0100 Subject: [PATCH 3/4] Support dt.timedelta when nested --- src/time_machine/__init__.py | 4 ---- tests/test_time_machine.py | 10 +++------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/time_machine/__init__.py b/src/time_machine/__init__.py index 283e4dd..4fa2142 100644 --- a/src/time_machine/__init__.py +++ b/src/time_machine/__init__.py @@ -126,10 +126,6 @@ def extract_timestamp_tzname( dest = dest.replace(tzinfo=dt.timezone.utc) timestamp = dest.timestamp() elif isinstance(dest, dt.timedelta): - if coordinates_stack: - raise TypeError( - "timedelta destination is not supported when already time travelling." - ) timestamp = time() + dest.total_seconds() elif isinstance(dest, dt.date): timestamp = dt.datetime.combine( diff --git a/tests/test_time_machine.py b/tests/test_time_machine.py index e612035..fb3eebb 100644 --- a/tests/test_time_machine.py +++ b/tests/test_time_machine.py @@ -476,14 +476,10 @@ def test_destination_negative_delta(): assert now - 3600 <= time.time() <= now - 3599 -@time_machine.travel(0) def test_destination_delta_raises(): - with pytest.raises(TypeError) as excinfo: - time_machine.travel(dt.timedelta(seconds=3600)) - - assert excinfo.value.args == ( - "Timedelta destination is not supported when already time travelling.", - ) + with time_machine.travel(EPOCH): + with time_machine.travel(dt.timedelta(seconds=10)): + assert time.time() == EPOCH + 10.0 def test_traveller_object(): From 1e3a11dfc8ab2c3d5daecc435a57403f02d9e3f4 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 19 Sep 2023 11:09:32 +0100 Subject: [PATCH 4/4] Move and rename tests --- tests/test_time_machine.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/test_time_machine.py b/tests/test_time_machine.py index fb3eebb..0f086bf 100644 --- a/tests/test_time_machine.py +++ b/tests/test_time_machine.py @@ -444,6 +444,24 @@ def test_destination_date(): assert time.time() == EPOCH +def test_destination_timedelta(): + now = time.time() + with time_machine.travel(dt.timedelta(seconds=3600)): + assert now + 3600 <= time.time() <= now + 3601 + + +def test_destination_timedelta_negative(): + now = time.time() + with time_machine.travel(dt.timedelta(seconds=-3600)): + assert now - 3600 <= time.time() <= now - 3599 + + +def test_destination_timedelta_nested(): + with time_machine.travel(EPOCH): + with time_machine.travel(dt.timedelta(seconds=10)): + assert time.time() == EPOCH + 10.0 + + @time_machine.travel("1970-01-01 00:01 +0000") def test_destination_string(): assert time.time() == EPOCH + 60.0 @@ -464,24 +482,6 @@ def test_destination_generator(): assert time.time() == EPOCH + 13.0 -def test_destination_delta(): - now = time.time() - with time_machine.travel(dt.timedelta(seconds=3600)): - assert now + 3600 <= time.time() <= now + 3601 - - -def test_destination_negative_delta(): - now = time.time() - with time_machine.travel(dt.timedelta(seconds=-3600)): - assert now - 3600 <= time.time() <= now - 3599 - - -def test_destination_delta_raises(): - with time_machine.travel(EPOCH): - with time_machine.travel(dt.timedelta(seconds=10)): - assert time.time() == EPOCH + 10.0 - - def test_traveller_object(): traveller = time_machine.travel(EPOCH + 10.0) assert time.time() >= LIBRARY_EPOCH