mrkeyoor.com_
Wed 23 Sept 00:33 UTC
PyPITestingupdated 19 Sept 2026

time-machine review

time-machine changes what Python's standard `datetime` and `time` functions report by hooking CPython at the C layer. Tests can travel through a decorator, sync or async context manager, pytest marker, pytest fixture, or manually controlled Traveller, then move or shift the clock. That interpreter-level hook reaches functions imported before travel starts. Version 3.5.0 adds `None` as a destination for `travel()` and `move_to()`, which freezes the actual current instant when paired with `tick=False`.

Verdict

time-machine 3.4.0 installed in 0.2 seconds and imported its compiled hook in 0.02 seconds on our CPython box; current 3.5.0 now adds a `None` destination for freezing the present. Install it for fast process-wide clock control, and avoid it when concurrent tests need isolated clocks or the time source lives outside Python.

We installed it

Lab card: what happened when we installed time-machineScreenshot of time-machine documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport _time_machine in 0.02s · compiled extensions · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does time-machine install cleanly?

Yes. In a fresh container with an empty cache, pip install time-machine finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does time-machine need to run?

Python >=3.10, and a platform wheel with compiled extensions. In our run import _time_machine succeeded in 0.02s, and the package ships py.typed for type checkers.

time-machine or freezegun: which should you use?

freezegun: Use it for its familiar patching API, wider interpreter approach, or migration features absent here. time-machine 3.4.0 installed in 0.2 seconds and imported its compiled hook in 0.02 seconds on our CPython box; current 3.5.0 now adds a None destination for freezing the present.

When should you not use time-machine?

Your suite runs on PyPy or another interpreter. The project supports CPython because its mechanism depends on CPython C APIs.

API stability4/5The `travel` decorator and context manager, `tick` switch, Traveller movement methods, pytest fixture, marker, and escape hatch form a small consistent API. Version 3.5.0 extends destinations with `None` instead of changing existing call shapes. Timezone and naive-value rules remain areas where defaults matter, so teams should set their policy explicitly and review the changelog across major upgrades.
Docs5/5The official documentation lists every affected standard-library function, supported destination types, ticking semantics, timezone effects, naive modes, real-time escape hatch, decorators, async support, unittest and pytest integration, installation limits, and comparisons with freezegun and libfaketime. It directly warns that clock changes are global and that databases, subprocesses, and some native calls continue using real time.
Maintenance5/5PyPI uploaded 3.5.0 on August 25, 2026, and GitHub shows a push the same day. The unarchived repository has 994 stars and only 7 open issues and pull requests. Current work includes new destination behavior, CPython version and free-threaded build support, type information, and a compiled implementation, all of which require release engineering beyond ordinary pure-Python maintenance.
Ecosystem4/5The supplied weekly figure is 5,042,863 downloads. The package works through pytest auto-discovery, unittest class decoration, ordinary and async context managers, ISO destinations, optional dateutil parsing, and a migration CLI. Its boundary is clear: CPython is required, external processes remain untouched, and global interpreter state prevents the isolated per-task clock model some concurrent suites need.

Use it if

  • A CPython test suite imports date and time functions across many modules and patching every lookup is brittle.
  • One scenario needs to jump or shift the clock several times while retaining a clear cleanup scope.
  • Tests use pytest, unittest, ordinary decorators, or async context managers and need the same clock tool.
  • Runtime speed matters enough to accept a compiled CPython extension and process-global clock state.
Skip it if

Setup reality

We installed time-machine 3.4.0 in a clean Python 3.12 Bookworm container on August 22, 2026. The install finished in 0.2 seconds, left 1 package using 1 MB, and import _time_machine worked in 0.02 seconds. That measured release declared 2 direct dependencies, required Python 3.10 or newer, shipped py.typed plus a compiled .so, and produced 0 known findings in pip-audit. Its package metadata did not give us a license value; the GitHub repository declares MIT.

PyPI moved to 3.5.0 on August 25, after our sandbox run, so the install figures above describe 3.4.0 rather than the current wheel. Version 3.5.0 accepts None in travel() and Traveller.move_to(). With tick=False, that captures and freezes the present moment. Standard ISO strings need no extra parser; broader text parsing uses the dateutil extra, and the freezegun migration command has its own CLI extra.

The first behavioral surprise is tick=True, the default. The destination is returned first and subsequent calls advance with real elapsed time, so exact equality assertions need tick=False. Naive destinations also need a policy: the compatibility-oriented MIXED mode treats naive objects and strings differently. Set NaiveMode.ERROR or another deliberate mode in test bootstrap to prevent results changing with local machine timezone.

Travel is global inside the interpreter. Threads, concurrent tasks, background callbacks, and unrelated tests can see it until the context exits. Databases and child processes do not. An aware ZoneInfo destination may also change process timezone through time.tzset() on Unix; it does not change framework-level timezone settings. Prefer decorators and context managers, and if manual start() is unavoidable, stop nested travellers in reverse order inside finally.

Patterns

Freeze an exact UTC instant freeze-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)

Set `tick=False` for equality assertions. The default value is true, which advances the travelled clock with real elapsed time.

Freeze the present instant in 3.5.0 freeze-current-time

import datetime as dt
import time_machine

with time_machine.travel(None, tick=False):
    captured = dt.datetime.now(dt.UTC)
    assert dt.datetime.now(dt.UTC) == captured

A `None` destination was added in 3.5.0. Pin that minimum version wherever tests use this form.

Apply one clock to a test function decorate-test

@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 removes local-zone ambiguity, and the decorator restores the prior clock when the function exits.

Travel during pytest setup and test execution use-pytest-marker

@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 plugin registers this marker and applies travel during function-scoped fixture setup and teardown as well.

Move the pytest fixture between assertions move-pytest-clock

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 alter time until `move_to()` runs. It is function-scoped and exposes Traveller methods.

Let elapsed real time advance the destination test-ticking-time

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

Ticking begins from the destination and then follows elapsed wall time. Assertions should use ordering or a tolerance.

Enter travel with an async context manager travel-async

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

Async syntax controls cleanup only. Every task and thread in the interpreter still observes the same travelled clock.

Test local time in a Unix process change-timezone

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

Timezone switching uses `time.tzset()` and is Unix-only. It does not update Django's active timezone or remote systems.

Reject naive destinations in the suite set-naive-policy

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

The default MIXED compatibility mode interprets naive objects and naive strings differently. ERROR makes accidental ambiguity fail early.

Read real time inside a travelled scope access-real-time

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 functions raise `ValueError` outside active travel, which helps keep them out of ordinary application paths.

Guarantee cleanup for manual travel manual-start-stop

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

Manual travellers can leak global time after a failure. A context manager is safer; nested manual starts must stop in reverse order.

Move one week from the current clock relative-travel

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

A timedelta destination is relative to the clock at entry. Use an aware datetime when a fixed calendar result is the assertion.

Alternatives

PackageRegistryPick it when
freezegunPyPIUse it for its familiar patching API, wider interpreter approach, or migration features absent here.
pytest-freezerPyPIUse it when a pytest fixture is the main interface and direct Traveller control is unnecessary.
libfaketimePyPIUse it when Unix preload-based interception must reach beyond Python's standard date and time calls.

More testing guides

pytest · chai · jsdom · vitest · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.