time-machine
Travel through time in your Python tests by mocking datetime and time at the C level.
Repository Health
Technical Analysis
time-machine lets Python test suites travel to any point in time, mocking every standard-library function that returns the current date or datetime — datetime.datetime.now(), time.time(), time.localtime(), and more. Instead of patching each import location the way unittest.mock does, it replaces the underlying C function pointers inside the datetime and time modules directly, so every piece of code that reads the clock sees the mocked value regardless of how it imported the function.
Because the mocking happens at the C layer rather than by walking loaded modules (as freezegun does), it stays fast even in large codebases, and it also affects functions hidden inside class attributes or default arguments that module-search-based tools miss. It ships as a pytest plugin out of the box, contributing both a time_machine fixture and a @pytest.mark.time_machine(...) marker, and can just as easily be used as a decorator, a synchronous or asynchronous context manager, or a unittest.TestCase class decorator.
A companion CLI (installed via the time-machine[cli] extra) automates migrating existing freezegun test suites to time-machine’s API, rewriting decorators, fixtures, and imports across a codebase in one pass.
What You Get
- C-level patching - swaps the function pointers behind
datetime.now(),time.time(),time.localtime(),time.strftime(), and other clock-reading standard-library functions, so every caller sees the mocked time regardless of how it imported the function. - Pytest plugin built in - auto-registers a
time_machinefixture and a@pytest.mark.time_machine(...)marker the moment the package is installed, no plugin configuration required. - Flexible destination types - accepts a
datetime,date,timedelta, Unix timestamp, ISO date string, generator, or callable as the point in time to travel to. - Ticking or frozen time -
tick=True(the default) keeps the mocked clock advancing in real time from the destination;tick=Falsefreezes it exactly. - Timezone mocking - travelling to a timezone-aware
datetimealso callstime.tzset()to mock the process’s current timezone on Unix. - Escape hatch API -
time_machine.escape_hatchexposes the real, unmocked datetime/time functions and anis_travelling()check, for code (like external-service authentication) that needs the genuine current time even mid-travel. - freezegun migration CLI - the optional
time-machine migratecommand rewrites a codebase’s freezegun decorators, fixtures, and imports to time-machine’s equivalents automatically.
Common Use Cases
- Deterministic date-dependent tests - freeze
datetime.now()to a fixed value so assertions abouttoday(), ages, expirations, or scheduled jobs don’t depend on when the test runs. - Testing time-based business logic - simulate specific historical or future dates (subscription renewals, holiday pricing, daylight-saving transitions) without waiting for real time to pass.
- Migrating off freezegun for speed - swap a slow freezegun-based suite to time-machine’s C-level mocking, using the bundled CLI to do most of the rewrite automatically.
- Testing timezone-sensitive code - travel to a
datetimewith azoneinfo.ZoneInfoattached to verify behavior under a specific local timezone viatime.tzset(). - Async test suites - use
async with time_machine.travel(...)to mock time around asynchronous test functions and coroutines.
Under The Hood
Architecture
time-machine’s core is a small CPython C extension (src/_time_machine.c) that stores original method pointers for the patched functions in process-wide statics, guarded by a platform-specific mutex (SRWLOCK on Windows, a POSIX mutex elsewhere) so patch()/unpatch() are safe across per-interpreter GILs and free-threaded builds. The Python-facing layer (src/time_machine/__init__.py) implements the public travel class as a context manager, decorator, and unittest.TestCase class decorator around this C module, tracking a per-interpreter traveller stack so nested travel() blocks unwind correctly even across exceptions. A separate cli.py module implements an independent migration tool, built on tokenize-rt, that rewrites freezegun call sites into time-machine equivalents via token-stream manipulation rather than an AST round-trip, preserving original formatting.
Tech Stack
The package is pure CPython C plus Python (no non-test runtime dependencies), built via setuptools with cibuildwheel producing prebuilt wheels across CPython 3.10 through 3.15, including free-threaded builds. It registers itself as a pytest11 entry point so pytest auto-discovers its plugin, and optionally depends on python-dateutil (for flexible string-date parsing) and tokenize-rt (only for the CLI extra). Docs are built with Sphinx and the Furo theme, published to Read the Docs.
Code Quality
The test suite is extensive relative to the library’s size — thousands of lines across test_time_machine.py, test_cli.py, plus dedicated Hypothesis-based fuzz tests (test_fuzz.py, test_cli_fuzz.py) that generate random inputs to catch edge cases in the C-level patching and the CLI’s token rewriting. CI runs the full matrix across every supported CPython version including free-threaded builds, and the project advertises 100% coverage. Typing is strict: the codebase is fully type-annotated and checked with mypy --strict, and linted/formatted with ruff, enforced via pre-commit.
What Makes It Unique
time-machine’s explicit design goal is combining freezegun’s ergonomic API with the speed and completeness of C-level mocking tools like libfaketime, without requiring LD_PRELOAD or re-executing the test process. Because it patches the actual C function pointers behind the standard library rather than searching for and replacing every module-level import (freezegun’s approach), its mocking cost doesn’t scale with the number of loaded modules, and it correctly reaches functions hidden behind class attributes or default arguments that import-search tools cannot find. Its stated limitation is that it only covers CPython (not PyPy) and cannot affect compiled extensions, like NumPy or SQLite, that read the system clock directly rather than through the mocked standard-library calls.