Testland
Browse all skills & agents

freezegun-python

Wraps freezegun (github.com/spulec/freezegun), the Python time-mocking library: @freeze_time decorator / context manager, freeze_time(...).start() + stop(), tick / move_to / tz_offset, and integration with datetime.now / time.time / time.localtime. Use when testing Python code that calls datetime / time. Composes dst-transition-reference + iso-8601-vs-rfc-3339-reference.

freezegun-python

Overview

freezegun is the canonical Python time-mocking library. Per github.com/spulec/freezegun, it patches datetime.datetime, datetime.date, time.time, time.gmtime, time.localtime, time.strftime, plus asyncio time across the test scope.

When to use

  • pytest / unittest tests for Python code using datetime / time.
  • Date-based fixtures (e.g., "today is 2026-05-20").
  • DST + timezone tests per dst-transition-reference.

Authoring

Install

pip install freezegun

Decorator (most common)

from freezegun import freeze_time
from datetime import datetime

@freeze_time("2026-05-20T14:30:00")
def test_today_is_may_20():
    assert datetime.now().strftime("%Y-%m-%d") == "2026-05-20"

Context manager

with freeze_time("2026-05-20T14:30:00"):
    assert datetime.now().strftime("%Y-%m-%d") == "2026-05-20"

Manual start/stop

freezer = freeze_time("2026-05-20T14:30:00")
freezer.start()
try:
    # ...
finally:
    freezer.stop()

Tick mode

@freeze_time("2026-05-20T14:30:00", tick=True)
def test_clock_advances():
    t1 = datetime.now()
    # ... a few ops later
    t2 = datetime.now()
    assert t2 > t1

tick=True lets real time pass from the frozen start point. Useful for tests that need duration measurement.

Move to a different time mid-test

@freeze_time("2026-05-20T14:30:00")
def test_advance_one_day(freezer):
    assert datetime.now().day == 20
    freezer.move_to("2026-05-21T14:30:00")
    assert datetime.now().day == 21

Or via freezer.tick(delta=timedelta(hours=24)).

Timezone offset

@freeze_time("2026-05-20T14:30:00", tz_offset=-5)
def test_eastern_time():
    # datetime.now() returns wall-clock; datetime.utcnow() returns UTC
    assert datetime.utcnow().hour == 19  # 14:30 + 5
    assert datetime.now().hour == 14

DST + zone tests

import pytest
from zoneinfo import ZoneInfo

@freeze_time("2026-03-08T07:30:00")  # 02:30 EST OR 03:30 EDT — depends on resolution
def test_spring_forward_handling():
    ny = datetime.now(ZoneInfo("America/New_York"))
    # Asserts against expected library behaviour per dst-transition-reference

Async support

@freeze_time("2026-05-20T14:30:00")
async def test_async_now():
    await asyncio.sleep(0)
    assert datetime.now().strftime("%Y") == "2026"

Per freezegun docs: "freezegun is compatible with asyncio."

Running

pytest tests/

CI integration

jobs:
  python-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v5
      - run: pip install -e ".[test]" freezegun
      - run: pytest tests/

Anti-patterns

Anti-patternWhy it failsFix
freeze_time("2026-05-20") (date only)freezegun interprets as midnight local; subtleUse ISO datetime
time.sleep(...) inside frozen-time blockSleep is real-time; frozen clock doesn't advanceUse freezer.tick()
Mock datetime.utcnow separatelyConflicts with freezegunLet freezegun do both
Forget freezer cleanup in fixturesCross-test contaminationUse decorator or with
Test DST without tz_offset or zoneinfoResult is UTC; misses local behaviourCombine with zoneinfo
@freeze_time on a class without decorate_class=TrueMethods not patchedUse class decorator explicitly
Test third-party C extensions calling system timefreezegun only patches Python-level APIsUse libfaketime

Limitations

  • C extensions bypass freezegun. A library calling clock_gettime() from C sees the real clock. Use libfaketime-c for those.
  • No leap-second simulation. See leap-second-reference.
  • tz_offset doesn't know about DST. For accurate local-zone behaviour, use datetime.now(tz=zoneinfo.ZoneInfo("...")).
  • Importing datetime before freezing. If a module imports datetime.now directly at module-load, the unfrozen value may be cached.

References