Fix: freezegun Not Working — Datetime Not Frozen, Timezone Issues, and Async Tests
Quick Answer
How to fix freezegun errors — freeze_time decorator not affecting datetime.now, timezone-aware datetime mismatch, time.time not frozen, async test time leak, third-party library still using real time, and tick parameter behavior.
The Error
You decorate a test with @freeze_time and datetime.now() returns the real time:
from freezegun import freeze_time
from datetime import datetime
@freeze_time("2025-01-01")
def test_frozen():
print(datetime.now()) # Today's real date, not 2025-01-01Or timezone-aware datetimes don’t match:
from datetime import datetime, timezone
@freeze_time("2025-01-01 12:00:00")
def test():
assert datetime.now(timezone.utc) == datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
# AssertionError — they're differentOr async tests leak time into other tests:
@pytest.mark.asyncio
@freeze_time("2025-01-01")
async def test_async():
await asyncio.sleep(1) # Real sleep — time advances
assert datetime.now() == datetime(2025, 1, 1) # Fails — datetime advancedOr a third-party library inside the frozen scope sees real time:
@freeze_time("2025-01-01")
def test_jwt():
token = jwt.encode({"exp": 1234567890}, "secret", algorithm="HS256")
# JWT library uses real time for expiry, ignores freeze_timeOr auto_tick_seconds doesn’t behave as expected:
@freeze_time("2025-01-01", auto_tick_seconds=1)
def test():
t1 = time.time()
t2 = time.time()
# t2 == t1, not t1 + 1 — wrong behaviorfreezegun is the dominant time-mocking library for Python tests — @freeze_time("2025-01-01") patches datetime, time, and related modules to return a fixed moment. The decorator-based API is simple, but the interaction with timezone-aware datetimes, async code, and libraries that cache time produces subtle failures. This guide covers each common issue.
Why This Happens
freezegun patches the standard library modules (datetime, time, calendar) at the Python level by replacing their functions with mocks. Code that imported these modules before freezegun activates uses the unpatched versions — common with from datetime import datetime as DT style imports cached at module load time.
Async code runs in event loops where time is tracked separately by asyncio — await asyncio.sleep(N) actually waits N seconds of real time, regardless of freezegun. Real sleeps advance the wall clock even while datetime is frozen.
Fix 1: Decorator and Context Manager Patterns
from freezegun import freeze_time
from datetime import datetime
import time
# Decorator
@freeze_time("2025-01-01 12:00:00")
def test_with_decorator():
assert datetime.now() == datetime(2025, 1, 1, 12, 0, 0)
assert int(time.time()) == 1735732800
# Context manager
def test_with_context():
with freeze_time("2025-01-01 12:00:00"):
assert datetime.now() == datetime(2025, 1, 1, 12, 0, 0)
# Outside the context — real time again
# Class decorator (all methods)
@freeze_time("2025-01-01")
class TestFrozenSuite:
def test_one(self):
assert datetime.now().year == 2025
def test_two(self):
assert datetime.now().year == 2025Time format flexibility:
freeze_time("2025-01-01") # Date only — midnight
freeze_time("2025-01-01 12:00:00") # ISO datetime
freeze_time("2025-01-01T12:00:00") # ISO with T separator
freeze_time("2025-01-01 12:00:00+00:00") # With timezone
freeze_time(datetime(2025, 1, 1, 12, 0, 0)) # datetime object
freeze_time(date(2025, 1, 1)) # date objectCommon Mistake: Importing datetime at module level before freezegun is configured. The cached reference may point at the real datetime even inside @freeze_time. Always import inside the function, or use freezegun.api.register_call_stack_inspection to handle the edge case. In practice, from datetime import datetime then using datetime.now() inside the test works fine — the issue is only when libraries cache the datetime class itself.
Fix 2: Timezone-Aware Datetimes
from freezegun import freeze_time
from datetime import datetime, timezone
@freeze_time("2025-01-01 12:00:00")
def test():
# freeze_time defaults to UTC
naive = datetime.now()
aware = datetime.now(timezone.utc)
print(naive) # 2025-01-01 12:00:00 (no tz)
print(aware) # 2025-01-01 12:00:00+00:00Specify timezone explicitly:
from datetime import timezone, timedelta
from freezegun import freeze_time
tz_jst = timezone(timedelta(hours=9)) # Japan Standard Time
@freeze_time("2025-01-01 12:00:00", tz_offset=9)
def test_tokyo_time():
# freeze_time interprets the string as UTC, but datetime.now(tz_jst) shows local
local = datetime.now(tz_jst)
assert local == datetime(2025, 1, 1, 21, 0, 0, tzinfo=tz_jst)
# UTC 12:00 = JST 21:00Or freeze with explicit tz datetime:
from datetime import datetime, timezone
@freeze_time(datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc))
def test():
assert datetime.now(timezone.utc).hour == 12Common Mistake: Comparing a naive datetime.now() to an aware datetime constant. The naive one returns “current frozen time, no tz” while the aware one returns “current frozen time in UTC”. They’re equal numerically but Python raises TypeError: can't compare offset-naive and offset-aware datetimes. Always be consistent — either both aware or both naive.
# WRONG
@freeze_time("2025-01-01")
def test():
assert datetime.now() == datetime(2025, 1, 1, tzinfo=timezone.utc)
# TypeError
# CORRECT
@freeze_time("2025-01-01")
def test():
assert datetime.now(timezone.utc) == datetime(2025, 1, 1, tzinfo=timezone.utc)Fix 3: Ticking and Time Advancement
By default, freezegun returns the same instant every time you call time.time() or datetime.now(). To advance time:
from freezegun import freeze_time
from datetime import datetime, timedelta
# Manual advance
with freeze_time("2025-01-01") as frozen:
assert datetime.now() == datetime(2025, 1, 1, 0, 0, 0)
frozen.tick(delta=timedelta(seconds=30))
assert datetime.now() == datetime(2025, 1, 1, 0, 0, 30)
frozen.tick(delta=timedelta(minutes=5))
assert datetime.now() == datetime(2025, 1, 1, 0, 5, 30)
# Or move to a specific time
frozen.move_to("2025-01-02 09:00:00")
assert datetime.now() == datetime(2025, 1, 2, 9, 0, 0)Auto-ticking — time advances by N seconds on each call:
@freeze_time("2025-01-01", auto_tick_seconds=1)
def test():
t1 = time.time()
t2 = time.time()
assert t2 - t1 == 1 # Each call advances 1 secondauto_tick_seconds=1 is useful for testing rate limiting or timeout logic — every time check advances by a known interval.
tick=True (real time, just shifted):
@freeze_time("2025-01-01", tick=True)
def test():
# Time progresses in real time, but starting from 2025-01-01
t1 = time.time()
time.sleep(0.1)
t2 = time.time()
assert t2 - t1 >= 0.1Common Mistake: Using tick=True for tests that need deterministic timing. The wall clock advances during test execution — small delays compound and tests become flaky. Prefer auto_tick_seconds or explicit tick() calls.
Fix 4: Async Code and asyncio.sleep
import asyncio
from freezegun import freeze_time
from datetime import datetime
@freeze_time("2025-01-01")
async def test_async():
start = datetime.now()
await asyncio.sleep(1) # Real 1-second wait
end = datetime.now()
print(end - start) # 0:00:01 (real time advanced)freezegun freezes datetime/time but asyncio.sleep uses the event loop’s monotonic clock, which freezegun doesn’t patch. Real sleeps happen and the datetime stays frozen — confusing mismatch.
For async tests, mock asyncio.sleep separately or use a time-skipping pattern:
from unittest.mock import patch
@pytest.mark.asyncio
@freeze_time("2025-01-01")
async def test_async():
async def fake_sleep(seconds):
# Advance freezegun's time when sleep is called
with freeze_time(datetime.now() + timedelta(seconds=seconds)):
return
with patch("asyncio.sleep", new=fake_sleep):
await my_async_function_that_sleeps()Pro Tip: For testing scheduled async code, use libraries built for time-virtualized async testing — aiojobs, trio.testing.MockClock, or pytest-asyncio with mock_clock fixtures. freezegun alone doesn’t simulate asyncio’s internal scheduling.
Trio’s MockClock (for trio-based async code):
import trio
from trio.testing import MockClock
async def test_with_clock():
async with trio.open_nursery() as nursery:
clock = MockClock()
async def task():
await trio.sleep(60) # MockClock makes this instant
return "done"
nursery.start_soon(task)Fix 5: Libraries That Cache Time
Some libraries grab the current time at module load and cache it:
# my_module.py
from datetime import datetime
START_TIME = datetime.now() # Captured once at import
# test_my_module.py
@freeze_time("2025-01-01")
def test():
from my_module import START_TIME
assert START_TIME == datetime(2025, 1, 1) # Fails — captured before freezeSolution — reload the module under freeze:
import importlib
from freezegun import freeze_time
@freeze_time("2025-01-01")
def test():
import my_module
importlib.reload(my_module)
assert my_module.START_TIME == datetime(2025, 1, 1)Or restructure the code to use lazy evaluation:
# my_module.py
from datetime import datetime
def get_start_time():
return datetime.now()
# Always returns current time, freezable in testsCommon Mistake: JWT, OAuth, and signed-cookie libraries often have now() baked into their token generation. The frozen time doesn’t propagate if these libraries use time.time() cached at import. Inspect the library — most modern libraries (pyjwt, authlib) accept a current_time parameter, or you can pass the time explicitly:
import jwt
from freezegun import freeze_time
import time
@freeze_time("2025-01-01")
def test_jwt():
token = jwt.encode(
{"exp": int(time.time()) + 3600}, # Uses frozen time
"secret",
algorithm="HS256",
)Fix 6: pytest Fixtures with freeze_time
import pytest
from freezegun import freeze_time
from datetime import datetime
@pytest.fixture
def frozen_time():
with freeze_time("2025-01-01") as frozen:
yield frozen
def test_with_fixture(frozen_time):
assert datetime.now() == datetime(2025, 1, 1)
frozen_time.tick(timedelta(hours=1))
assert datetime.now() == datetime(2025, 1, 1, 1, 0, 0)Parametrize across multiple frozen times:
import pytest
from freezegun import freeze_time
@pytest.mark.parametrize("frozen_at", [
"2025-01-01",
"2025-06-15",
"2025-12-31",
])
def test_multiple_dates(frozen_at):
with freeze_time(frozen_at):
# Test logic for each date
...pytest-freezer (modern fork) — better integration:
pip install pytest-freezerdef test_with_marker(freezer):
freezer.move_to("2025-01-01")
assert datetime.now().year == 2025
freezer.move_to("2025-12-31")
assert datetime.now().month == 12For pytest fixture patterns that work cleanly with freezegun, see pytest fixture not found.
Fix 7: Specific Use Cases
Test scheduled jobs:
from freezegun import freeze_time
from datetime import datetime, timedelta
def is_business_hour():
now = datetime.now()
return 9 <= now.hour < 17 and now.weekday() < 5
@freeze_time("2025-04-24 10:00:00") # Thursday 10am
def test_in_hours():
assert is_business_hour() is True
@freeze_time("2025-04-24 22:00:00") # Thursday 10pm
def test_out_of_hours():
assert is_business_hour() is False
@freeze_time("2025-04-26 10:00:00") # Saturday 10am
def test_weekend():
assert is_business_hour() is FalseTest cache expiration:
@freeze_time("2025-01-01 12:00:00") as frozen
def test_cache_expires():
cache.set("key", "value", ttl=300) # 5 minutes
assert cache.get("key") == "value"
frozen.tick(timedelta(seconds=299))
assert cache.get("key") == "value"
frozen.tick(timedelta(seconds=2))
assert cache.get("key") is None # ExpiredTest rate limiting:
@freeze_time("2025-01-01 12:00:00") as frozen
def test_rate_limit():
for _ in range(5):
assert rate_limiter.allow("user-1") is True
assert rate_limiter.allow("user-1") is False # 6th request blocked
frozen.tick(timedelta(minutes=1))
assert rate_limiter.allow("user-1") is True # Window resetCommon Mistake: Testing time-sensitive code without freezing time. Tests that pass at 10:30am may fail at 11:00am because of a “current time” check inside the code. Always freeze time in any test that touches datetime.now(), time.time(), or anything that uses them.
Fix 8: Performance and Limitations
freezegun has overhead — patching the datetime module on entry/exit takes time. For tests that repeatedly enter and exit freeze_time contexts, the overhead adds up.
Use a wider scope when possible:
# SLOW — freezes per call
def test_many():
for date in dates:
with freeze_time(date):
do_something()
# FASTER — freeze once, advance via tick
with freeze_time(dates[0]) as frozen:
for date in dates:
frozen.move_to(date)
do_something()Skip freezing entirely when datetime calls are rare:
# If only ONE datetime call matters, mock it directly
from unittest.mock import patch
from datetime import datetime
with patch("mymodule.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2025, 1, 1)
do_something()Direct mocking is faster than full freezegun for tightly-scoped patches.
freezegun doesn’t patch:
- C extensions that read time directly (rare; some crypto libs)
os.times(),os.stat()timestamps- File system mtimes
- The system kernel’s clock (
gettimeofdaysyscall directly)
For these, use platform-specific mocking or restructure the code to inject time as a dependency.
Still Not Working?
freezegun vs Alternatives
- freezegun — Most popular, mature, covers most Python time APIs.
- pytest-freezer — Newer fork with better pytest integration.
- time-machine — Faster (Cython implementation), drop-in replacement for freezegun.
For projects with thousands of frozen-time tests, time-machine can cut test suite time significantly. API is nearly identical.
Integration with Faker
When generating test data with Faker that includes timestamps, fix the seed AND freeze time for fully deterministic output:
from freezegun import freeze_time
from faker import Faker
fake = Faker()
fake.seed_instance(42)
@freeze_time("2025-01-01")
def test_deterministic_data():
timestamp = fake.date_time_this_year() # Predictable
name = fake.name() # Predictable (from seed)Combining with Hypothesis
For property-based tests with time, freeze inside the test rather than via decorator:
from hypothesis import given, strategies as st
from freezegun import freeze_time
@given(st.datetimes(min_value=datetime(2020, 1, 1), max_value=datetime(2030, 12, 31)))
def test_with_random_time(some_datetime):
with freeze_time(some_datetime):
result = my_function()
assert result.created_at == some_datetimeFor Hypothesis-specific patterns that combine with freezegun, see Hypothesis not working.
Async-Aware Time Mocking
For asyncio code that does scheduled work, pytest plugins like pytest-asyncio and frameworks like anyio provide MockClock-style helpers. Combine with freezegun for datetime calls:
import pytest
import asyncio
from freezegun import freeze_time
@pytest.mark.asyncio
@freeze_time("2025-01-01")
async def test_async_workflow(monkeypatch):
# Mock asyncio.sleep to not actually wait
async def instant_sleep(seconds):
pass
monkeypatch.setattr(asyncio, "sleep", instant_sleep)
await my_workflow_with_delays()For asyncio testing patterns that integrate with time mocking, see Python asyncio not running.
Debugging Why Time Isn’t Frozen
import time
import datetime
from freezegun import freeze_time
@freeze_time("2025-01-01")
def debug_time():
print(f"time.time(): {time.time()}") # 1735689600.0 if frozen
print(f"datetime.now(): {datetime.datetime.now()}")
print(f"datetime.utcnow(): {datetime.datetime.utcnow()}")
debug_time()If the printed times match 2025-01-01, freezegun is active. If they show today’s date, something is bypassing freezegun — likely a cached import or a library using a non-patched time source.
For broader testing patterns that combine freezegun with mocking, see pytest fixture not found. For Moto-based AWS mocking that often pairs with freezegun for time-sensitive AWS workflows, see Moto not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Moto Not Working — Mock Decorator, Real AWS Calls Leaking, and v4 to v5 Migration
How to fix Moto errors — mock not activating, real AWS credentials used in tests, ImportError mock_s3 removed in v5, fixtures with multiple services, NoCredentialsError despite mock, and standalone server mode.
Fix: Nox Not Working — Session Errors, Virtualenv Backends, and Reuse Logic
How to fix Nox errors — no noxfile.py found, session not detected, virtualenv backend uv not installed, session.install fails outside virtualenv, parametrize matrix exploding, and reuse_venv confusion.
Fix: Hypothesis Not Working — Strategy Errors, Flaky Tests, and Shrinking Issues
How to fix Hypothesis errors — Unsatisfied assumption, Flaky test detected, HealthCheck data_too_large, strategy composition failing, example database stale, settings profile not found, and stateful testing errors.
Fix: Tox Not Working — Environment Creation, Config Errors, and Multi-Python Testing
How to fix Tox errors — ERROR cannot find Python interpreter, tox.ini config parsing error, allowlist_externals required, recreating environments slow, pyproject.toml integration, and matrix env selection.