Testing

Patching the current time

Sometimes you need to control what .now() returns, typically for testing. whenever supports various ways to do this, depending on your needs:

  1. With whenever.patch_current_time(). It affects only whenever, not the standard library or other libraries. See its documentation for more details.

  2. With the time-machine package. Using time-machine does affect the standard library and other libraries, which can lead to unintended side effects. Note that time-machine doesn’t support PyPy.

Note

freezegun isn’t supported. Use time-machine instead.

The context manager yields a TimePatch handle to the time patch. Use shift() for exact elapsed-time movement and move_to() to set a new exact time:

>>> from whenever import Instant, patch_current_time
>>> with patch_current_time(Instant("2024-01-01T00:00:00Z"), keep_ticking=False) as p:
...     p.shift(hours=2)
...     assert Instant.now() == Instant("2024-01-01T02:00:00Z")
...     p.move_to(Instant("2024-06-01T12:00:00Z"))
...     assert Instant.now() == Instant("2024-06-01T12:00:00Z")

shift() moves by elapsed time; days= and weeks= count 24-hour days and warn, as Instant.add() does. For months or years, compute the target and pass it to move_to(). A time patch is either frozen (keep_ticking=False) or ticking (keep_ticking=True). With a ticking patch, shifts apply to the patched current instant at the moment of the call and the clock then continues ticking from the result.

The patch affects only Whenever’s current-time functions. Its state is global to the interpreter/module and therefore visible to every thread. Overlapping or nested patches raise RuntimeError, as does using a handle after its context exits. Decorator use does not inject the handle into the decorated function; use a with statement when you need it. In free-threaded builds, Whenever synchronizes access to this global state.

Tip

Instead of relying on patching, consider using dependency injection instead. This is less error-prone and more explicit.

You can do this by adding now argument to your function, like this:

def greet(name, now=Instant.now):
    current_time = now()
    # more code here...

# in normal use, you don't notice the difference:
greet('bob')

# to test it, pass a custom function:
greet('alice', now=lambda: Instant.from_utc(2023, 1, 1))

Patching the system time zone

For changing the system time zone in tests, set the TZ environment variable and use the reset_system_tz() helper function to update the time zone cache. Do note that this function only affects whenever, and not the standard library’s behavior.

Below is an example of a testing helper that can be used with pytest:

import os
import pytest
from contextlib import contextmanager
from unittest.mock import patch
from whenever import reset_system_tz

@contextmanager
def system_tz_ams():
    try:
        with patch.dict(os.environ, {"TZ": "Europe/Amsterdam"}):
            reset_system_tz()  # update the time zone cache
            yield
    finally:
        reset_system_tz()  # don't forget to set the old time zone back!