mrkeyoor.com_
Thu 06 Aug 10:58 UTC
PyPITestingupdated 06 Aug 2026

freezegun

FreezeGun lets a Python test pretend the clock says whatever you want. You wrap a test in @freeze_time("2012-01-14") or a with block, and every call to datetime.datetime.now(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), and time.strftime() returns that frozen moment instead of the real one. It does this by replacing the datetime and time classes and then walking modules that already imported them so their references point at the fakes too, which is why it works on code you did not write. Inside the block you can also step the clock forward by hand with tick(), jump to a different date with move_to(), or let it run at an automatic increment per call.

Verdict

Still the default answer for time in Python tests, and the API is pleasant enough that you will write more time-sensitive tests than you otherwise would. Reach for time-machine instead if freeze overhead shows in your suite runtime, or if the year-long gap since the last release worries you more than switching cost.

API stability5/5freeze_time has taken the same core arguments for years and the 1.x line has not broken callers; newer options like real_asyncio and auto_tick_seconds were added without changing existing behavior.
Docs3/5One long README with runnable examples for every feature, which is enough to get started, but there is no docs site, no troubleshooting page for the common "my code still sees the real clock" case, and the API reference is a raw dump of function signatures.
Maintenance2/5Version 1.5.5 was published in August 2025 and the repository's last push was August 2025, with 120 open issues (161 counting PRs). Nothing is broken today, but reviews and releases have effectively paused.
Ecosystem5/5Roughly 14.2 million downloads a week and the assumed default in Django, Flask, and pytest projects; plugins such as pytest-freezer wrap it, and most tutorials on testing time in Python reach for it first.

Use it if

  • You are testing anything that expires: sessions, JWTs, password reset tokens, cache TTLs, trial periods, rate-limit windows. Freezing the clock beats sprinkling sleep() calls or injecting a clock object through five layers of code
  • The code under test calls datetime.now() directly and you cannot refactor it to take an injectable clock, which describes most real codebases and every third-party library you depend on
  • You need a test to be deterministic across a date boundary: end-of-month billing, leap day, daylight saving transitions, or a report that behaves differently on the first of the month
  • You want to simulate elapsed time inside one test without waiting: freeze, act, tick forward 30 days, assert the subscription lapsed
Skip it if

Setup reality

pip install freezegun pulls one dependency, python-dateutil, and needs Python 3.8 or newer. No compiler, no config file, nothing to register with pytest. The annoyances show up later. Some libraries break when their datetime references get swapped, so freezegun ships a default ignore list covering threading, Queue, selenium, gi, parts of pytest internals, and a few others; when a library you use misbehaves under a freeze you add it yourself with freeze_time(ignore=[...]) or globally via freezegun.configure(extend_ignore_list=[...]). Default arguments evaluated at import time are never frozen, because they were computed before your test ran. Decorating a class freezes each callable on it, which the README itself hedges about with "may not work in every case". And if you use asyncio, remember that monotonic time is frozen unless you opt out with real_asyncio=True.

Patterns

Freeze the clock for a whole testfreeze-with-decorator

import datetime
from freezegun import freeze_time

@freeze_time("2012-01-14")
def test_expiry():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
    assert datetime.date.today() == datetime.date(2012, 1, 14)

A bare date string freezes at midnight. The decorator also works on a unittest.TestCase subclass (it covers setUp and tearDown too) and on async test coroutines.

Freeze only part of a testfreeze-with-context-manager

import datetime
from freezegun import freeze_time

def test_partial():
    real_now = datetime.datetime.now()
    with freeze_time("2012-01-14 12:00:01"):
        assert datetime.datetime.now() == datetime.datetime(2012, 1, 14, 12, 0, 1)
    assert datetime.datetime.now() >= real_now

Use this when you want most of the test on the real clock and only one call frozen. Everything is restored on exit, including on exceptions.

Start and stop the freeze by handmanual-start-stop

import datetime
from freezegun import freeze_time

freezer = freeze_time("2012-01-14 12:00:01")
freezer.start()
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14, 12, 0, 1)
freezer.stop()

Useful in setUp/tearDown pairs or session fixtures. If an exception escapes before stop(), the clock stays frozen for the rest of the process, so wrap it in try/finally outside of a fixture.

Advance the frozen clock inside a testtick-forward-manually

import datetime
from freezegun import freeze_time

def test_token_expires():
    with freeze_time("2024-03-01 09:00:00") as frozen:
        token = issue_token(ttl=datetime.timedelta(minutes=15))
        frozen.tick()  # default step is 1 second
        assert token.is_valid()

        frozen.tick(delta=datetime.timedelta(minutes=20))
        assert not token.is_valid()

tick() accepts a timedelta or a float number of seconds. time.monotonic() moves with it, so code measuring elapsed time sees the jump as well.

Jump to a completely different datemove-to-a-date

import datetime
from freezegun import freeze_time

@freeze_time("2012-01-14", as_arg=True)
def test_month_rollover(frozen):
    assert datetime.date.today() == datetime.date(2012, 1, 14)
    frozen.move_to("2014-02-12")
    assert datetime.date.today() == datetime.date(2014, 2, 12)

move_to takes anything freeze_time takes: a string, a date, or a datetime. as_arg=True passes the controller in as the first positional argument; as_kwarg='name' passes it by keyword instead.

Let time keep running from a fixed startticking-clock

import datetime
from freezegun import freeze_time

@freeze_time("2020-01-14", tick=True)
def test_time_still_moves():
    assert datetime.datetime.now() > datetime.datetime(2020, 1, 14)

@freeze_time("2020-01-14", auto_tick_seconds=15)
def test_fixed_step():
    first = datetime.datetime.now()
    second = datetime.datetime.now()
    assert second - first == datetime.timedelta(seconds=15)

tick=True restarts real time from the given point, so results are not reproducible to the microsecond. auto_tick_seconds is deterministic and overrides tick, which is usually what you want for assertions.

Freeze with a timezone offsettimezone-offset

import datetime
from freezegun import freeze_time

@freeze_time("2012-01-14 03:21:34", tz_offset=-4)
def test_local_time():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 13, 23, 21, 34)
    assert datetime.date.today() == datetime.date(2012, 1, 13)

@freeze_time("2012-01-14 03:21:34", tz_offset=-datetime.timedelta(hours=3, minutes=30))
def test_half_hour_offset():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 13, 23, 51, 34)

The frozen string is treated as UTC and tz_offset shifts what local calls see. Offsets can be a whole number of hours or a timedelta for places like India and Newfoundland.

Wrap freeze_time in a pytest fixturepytest-fixture

import pytest
from freezegun import freeze_time

@pytest.fixture
def frozen_clock():
    with freeze_time("2024-01-01 00:00:00") as frozen:
        yield frozen

def test_daily_job(frozen_clock):
    run_daily_job()
    frozen_clock.tick(delta=datetime.timedelta(days=1))
    run_daily_job()

Yielding from inside the with block guarantees the clock is restored even if the test fails. Do not make this fixture session-scoped unless you want every test in the session frozen.

Exclude libraries that break under a freezeignore-packages

from freezegun import freeze_time
import freezegun

with freeze_time("2020-10-06", ignore=["threading", "tensorflow"]):
    ...

# or set it once for the whole test session:
freezegun.configure(extend_ignore_list=["tensorflow"])

configure(default_ignore_list=...) replaces the built-in list (threading, selenium, gi, parts of pytest and more); extend_ignore_list adds to it. Replacing it by accident is a common cause of hangs.

Keep asyncio working while the clock is frozenasyncio-real-monotonic

import asyncio
import datetime
from freezegun import freeze_time

@freeze_time("2012-01-14", real_asyncio=True)
async def test_asyncio():
    await asyncio.sleep(1)
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)

Without real_asyncio=True the event loop sees a frozen time.monotonic(), so asyncio.sleep() and any timeout built on it never complete and the test hangs instead of failing.

Pass dates, callables, or generatorsflexible-input-formats

import datetime
from freezegun import freeze_time

with freeze_time("Jan 14th, 2012"):          # parsed by dateutil
    ...

with freeze_time(lambda: datetime.datetime(2012, 1, 14)):
    ...

dates = (datetime.datetime(y, 1, 1) for y in range(2010, 2012))
with freeze_time(dates):                      # 2010-01-01
    ...
with freeze_time(dates):                      # 2011-01-01
    ...

Generators advance one value per freeze_time call and raise StopIteration when exhausted. String parsing goes through python-dateutil, so ambiguous formats like 03/04/2012 follow dateutil's rules, not yours.

Know what freeze_time cannot reachwhat-stays-unfrozen

import datetime as dt
from freezegun import freeze_time

def report(default=dt.date.today()):   # evaluated at import, not at call
    print(default)

with freeze_time("2000-01-01"):
    report()        # prints today's real date
    report(dt.date.today())  # prints 2000-01-01

Default arguments are computed when the function is defined, so they escape the freeze. The same blind spot applies to clocks outside Python: SQL NOW(), Redis TTLs, and timestamps generated inside C extensions all stay on the real clock.

Alternatives

PackageRegistryPick it when
time-machinePyPISame idea, implemented as a C extension that patches the clock at the CPython level; pick it when freeze overhead shows up in your suite runtime or you want nested freezes to behave predictably.
pytest-freezerPyPIYou want a pytest fixture wrapper around freezegun instead of decorators, so freezing is requestable per test like any other fixture.
libfaketimePyPIYou need the fake clock to reach code outside Python, including C extensions and subprocesses, because it works via LD_PRELOAD rather than module patching.