mrkeyoor.com_
Sat 08 Aug 20:59 UTC
PyPITestingupdated 08 Aug 2026

time-machine

time-machine changes the current time seen by Python's standard date and time functions by hooking them at the CPython C layer. Tests can travel with a decorator, sync or async context manager, unittest class decorator, pytest marker, or pytest fixture, then move or shift the clock. Unlike patching one import, it affects references throughout the interpreter, including functions captured before the test starts.

Verdict

time-machine is an excellent CPython test tool when import-safe and fast clock changes matter. Its global interpreter effect is the reason it works well and the reason it is unsafe for tests that need isolated concurrent clocks.

API stability4/5The travel decorator and context manager, tick flag, Traveller move and shift methods, and pytest integration form a compact public API. Version 3 adds explicit naive-time modes and supports new CPython releases. Defaults differ from freezegun and MIXED mode remains for compatibility, so behavior is stable once a project sets its time policy explicitly.
Docs5/5The docs cover every mocked function, destination type, tick behavior, sync and async use, unittest and pytest integration, timezone changes, naive modes, the real-time escape hatch, installation limits, and detailed comparisons with patching, freezegun, and libfaketime. The global concurrency and external-process warnings are prominent rather than hidden.
Maintenance5/5Version 3.3.1 was uploaded on August 4, 2026 and the repository was pushed on August 5, 2026. The README displays continuous integration and full coverage, and GitHub shows only 10 open issues and pull requests. Supporting CPython through 3.15 and free-threaded builds requires ongoing low-level work that the release history is visibly receiving.
Ecosystem4/5The package integrates automatically with pytest, supports unittest decorators and ordinary context managers, parses common ISO destinations without extras, and offers extras for dateutil parsing and freezegun migration. It has 989 GitHub stars. Its reach is intentionally narrower than pure-Python tools because CPython is mandatory and non-Python processes are unaffected.

Use it if

  • A large CPython test suite needs faster time control than module-scanning patch libraries
  • Code imports datetime and time functions in many places that are awkward to patch individually
  • Tests need to move or shift time during one scenario rather than freeze a single instant
  • You want pytest markers and fixtures plus direct decorator and context-manager forms
Skip it if

Setup reality

`python -m pip install time-machine` installs version 3.3.1 for CPython 3.10 through 3.15, including free-threaded variants from 3.14. It is a C-extension technique, so unsupported interpreters are a hard stop rather than a missing extra. ISO-formatted strings work without another package; wider natural-language parsing needs `time-machine[dateutil]`, and the source-rewriting migration command needs `time-machine[cli]`. The first-run surprise is that `tick=True` is the default: the first observed value is the destination and later calls advance with elapsed real time. Pass `tick=False` for exact frozen assertions. The second is naive datetime interpretation. The default MIXED mode treats naive datetime objects and dates as UTC but naive strings as local time for backward compatibility. New projects should deliberately select LOCAL or ERROR in test setup. Travel changes time process-wide within the interpreter, so parallel threads, async tasks, background callbacks, and unrelated tests can observe it; scope travel tightly and avoid leaking manually started travellers. Database `NOW()`, child processes, and remote APIs are unaffected. A ZoneInfo-aware destination also changes the process timezone through `time.tzset()` only on Unix, and it does not change framework concepts such as Django's active timezone. pytest discovers the plugin automatically, which adds a `time_machine` fixture and marker that may conflict in name with the imported module unless aliases are clear.

Patterns

Freeze time inside a context managerfreeze-context

import datetime as dt
import time_machine

with time_machine.travel('2025-01-15 12:00:00+00:00', tick=False):
    assert dt.datetime.now(dt.UTC) == dt.datetime(2025, 1, 15, 12, tzinfo=dt.UTC)

Pass `tick=False` for an exact frozen instant. The default is true, so later calls normally advance with real elapsed time.

Travel for one test functiondecorate-test

import datetime as dt
import time_machine

@time_machine.travel(dt.datetime(1985, 10, 26, tzinfo=dt.UTC), tick=False)
def test_release_date():
    assert dt.date.today() == dt.date(1985, 10, 26)

An aware datetime avoids machine-dependent naive-time interpretation. The decorator restores real time when the function exits.

Apply travel with a pytest markeruse-pytest-marker

import datetime as dt
import pytest

@pytest.mark.time_machine(dt.datetime(2015, 10, 21, tzinfo=dt.UTC), tick=False)
def test_future_message():
    assert dt.date.today().isoformat() == '2015-10-21'

The installed pytest plugin registers the marker automatically and applies travel during function-scoped fixture setup and teardown too.

Move and shift the pytest fixture clockmove-pytest-clock

import datetime as dt

def test_expiry(time_machine):
    time_machine.move_to(dt.datetime(2026, 1, 1, tzinfo=dt.UTC), tick=False)
    assert not subscription.expired()
    time_machine.shift(dt.timedelta(days=31))
    assert subscription.expired()

The fixture does not change time until `move_to()` is called. It is function-scoped and exposes the Traveller movement methods.

Allow time to advance after traveltest-ticking-time

import time
import time_machine

with time_machine.travel(0.0, tick=True):
    first = time.time()
    do_work()
    second = time.time()
    assert second >= first

Ticking advances by real elapsed duration after the first mocked call. Avoid exact equality assertions when tick is enabled.

Scope travel around async codetravel-async

import time
import time_machine

async def test_async_job():
    async with time_machine.travel(1000.0, tick=False):
        await run_job()
        assert time.time() == 1000.0

The context manager supports async syntax, but the clock is still shared with every concurrent task and thread in the interpreter.

Test local time with ZoneInfo on Unixchange-timezone

import datetime as dt
import time
from zoneinfo import ZoneInfo
import time_machine

la = ZoneInfo('America/Los_Angeles')
with time_machine.travel(dt.datetime(2015, 10, 21, 16, 29, tzinfo=la), tick=False):
    assert dt.datetime.now().hour == 16
    assert time.tzname == ('PST', 'PDT')

Timezone mocking calls `time.tzset()` and is Unix-only. It does not change Django's active timezone or an external service's timezone.

Reject naive travel destinationsset-naive-policy

import time_machine

# Put this in test bootstrap or conftest.py.
time_machine.naive_mode = time_machine.NaiveMode.ERROR

ERROR prevents dates and naive datetimes from silently changing meaning across machines. The default MIXED mode treats objects and strings differently.

Use real time for an external signatureaccess-real-time

import time_machine

with time_machine.travel('2001-01-01', tick=False):
    real_now = time_machine.escape_hatch.datetime.datetime.now()
    signature = sign_external_request(now=real_now)

Escape-hatch time methods raise ValueError when travel is not active, which helps prevent accidental use in ordinary application code.

Manually manage travel with guaranteed cleanupmanual-start-stop

import time_machine

travel = time_machine.travel(1234.0, tick=False)
traveller = travel.start()
try:
    assert traveller is not None
    exercise_code()
finally:
    travel.stop()

Prefer a decorator or context manager. Manual nested travellers must be stopped in reverse order even when the test raises.

Travel relative to the current clockrelative-travel

import datetime as dt
import time_machine

with time_machine.travel(dt.timedelta(days=7), tick=False):
    run_week_later_checks()

A timedelta destination is relative to current time. An explicit aware datetime is usually more reproducible when the expected date matters.

Apply one clock to a unittest classdecorate-unittest-class

import time
import unittest
import time_machine

@time_machine.travel(0.0, tick=False)
class EpochTests(unittest.TestCase):
    def test_epoch(self):
        self.assertEqual(time.time(), 0.0)

Class decoration is supported for unittest.TestCase subclasses and spans class setup through class teardown, not only individual test methods.

Alternatives

PackageRegistryPick it when
freezegunPyPIYou need its familiar API, wider interpreter approach, or unsupported migration features and can accept module-scanning overhead
pytest-freezerPyPIYou want a pytest-focused fixture layer around frozen time rather than a broader direct API
libfaketimePyPIYou need operating-system-level time interception and can manage Unix preload constraints