Claude Code for Freezegun: Freeze Time in Tests — Claude Skills 360 Blog
Blog / AI / Claude Code for Freezegun: Freeze Time in Tests
AI

Claude Code for Freezegun: Freeze Time in Tests

Published: December 11, 2027
Read time: 5 min read
By: Claude Skills 360

Freezegun patches time functions for deterministic datetime testing. pip install freezegun. from freezegun import freeze_time. Decorator: @freeze_time("2024-01-15"). Context: with freeze_time("2024-06-01 10:30:00"):. Datetime: @freeze_time(datetime(2024, 3, 15, 9, 0, 0)). Time object: @freeze_time("2024-01-01", tick=True) — real time from frozen start. Move: freezer.move_to("2024-06-01") inside context. Patches: datetime.datetime.now(), datetime.datetime.utcnow(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), time.strftime(), time.monotonic(), uuid.uuid1(). Pytest fixture: @pytest.fixture def frozen_time(): with freeze_time("2024-01-01"): yield. Class decorator: @freeze_time("2024-01-01") class TestMyClass: — applies to all methods. Ignore module: @freeze_time("2024-01-01", ignore=["module.name"]). Timezone: @freeze_time("2024-01-01 00:00:00", tz_offset=5). Auto tick: with freeze_time("2024-01-01", auto_tick_seconds=10): — time advances 10s/real-second. Real time: with freeze_time("2024-01-01") as frozen: frozen.move_to("2024-02-01"). Type: isinstance(datetime.datetime.now(), FakeDatetime). Multiple decorators: @freeze_time("2024-01-01") @freeze_time("2024-02-01") — outer wins. Async: works with async def test functions. freeze_time also patches dateutil.parser.parse results. Claude Code generates Freezegun time-controlled test fixtures, expiry logic tests, and cron scheduling tests.

CLAUDE.md for Freezegun

## Freezegun Stack
- Version: freezegun >= 1.4
- Decorator: @freeze_time("YYYY-MM-DD HH:MM:SS") on function or class
- Context: with freeze_time("...") as frozen_time:
- Advances: tick=True (real tempo from frozen start) | auto_tick_seconds=N
- Move: frozen_time.move_to("YYYY-MM-DD") inside context
- Patches: datetime.now/utcnow, date.today, time.time, time.localtime, uuid.uuid1
- Async: works on async def tests automatically
- Ignore: ignore=["third_party.module"] to exclude from freezing

Freezegun Time Control Pipeline

# tests/freezegun_pipeline.py — time control for tests with Freezegun
from __future__ import annotations
import datetime
import time
import uuid
from contextlib import contextmanager
from typing import Generator

import pytest
from freezegun import freeze_time
from freezegun.api import FakeDatetime


# ── 0. Sample code under test ─────────────────────────────────────────────────
# These are the functions we want to test with controlled time.

def get_current_timestamp() -> str:
    """Return ISO 8601 UTC timestamp."""
    return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")


def is_session_expired(created_at: datetime.datetime, ttl_hours: int = 24) -> bool:
    """Check whether a session has expired."""
    return datetime.datetime.utcnow() > created_at + datetime.timedelta(hours=ttl_hours)


def days_until_expiry(expires_at: datetime.date) -> int:
    """Return days remaining until an expiry date. Negative = already expired."""
    return (expires_at - datetime.date.today()).days


def generate_dated_filename(prefix: str, extension: str = "csv") -> str:
    """Generate filename with today's date: prefix_YYYYMMDD.csv"""
    today = datetime.date.today()
    return f"{prefix}_{today.strftime('%Y%m%d')}.{extension}"


def greeting_for_time(dt: datetime.datetime = None) -> str:
    """Return Good morning/afternoon/evening based on hour."""
    dt = dt or datetime.datetime.now()
    hour = dt.hour
    if 5 <= hour < 12:
        return "Good morning"
    elif 12 <= hour < 17:
        return "Good afternoon"
    elif 17 <= hour < 22:
        return "Good evening"
    return "Good night"


class TokenBucket:
    """Simple token bucket rate limiter that uses real time."""

    def __init__(self, capacity: int, refill_rate: float):
        self.capacity     = capacity
        self.refill_rate  = refill_rate      # tokens per second
        self.tokens       = float(capacity)
        self.last_refill  = time.monotonic()

    def consume(self, n: int = 1) -> bool:
        """Try to consume n tokens. Returns True if allowed."""
        now   = time.monotonic()
        delta = now - self.last_refill
        self.tokens = min(self.capacity,
                          self.tokens + delta * self.refill_rate)
        self.last_refill = now
        if self.tokens >= n:
            self.tokens -= n
            return True
        return False


class SubscriptionManager:
    """Manages subscriptions with expiry tracking."""

    def __init__(self):
        self.subscriptions: dict[str, datetime.date] = {}

    def subscribe(self, user_id: str, duration_days: int = 30) -> datetime.date:
        expires = datetime.date.today() + datetime.timedelta(days=duration_days)
        self.subscriptions[user_id] = expires
        return expires

    def is_active(self, user_id: str) -> bool:
        expires = self.subscriptions.get(user_id)
        if expires is None:
            return False
        return datetime.date.today() <= expires

    def days_remaining(self, user_id: str) -> int | None:
        expires = self.subscriptions.get(user_id)
        if expires is None:
            return None
        return (expires - datetime.date.today()).days


# ── 1. Decorator tests ────────────────────────────────────────────────────────

class TestTimestamps:

    @freeze_time("2024-01-15 10:30:00")
    def test_get_current_timestamp(self):
        result = get_current_timestamp()
        assert result == "2024-01-15T10:30:00Z"

    @freeze_time("2024-06-01")
    def test_date_today(self):
        assert datetime.date.today() == datetime.date(2024, 6, 1)

    @freeze_time("2024-12-25 00:00:00")
    def test_utcnow_frozen(self):
        now = datetime.datetime.utcnow()
        assert now.year  == 2024
        assert now.month == 12
        assert now.day   == 25

    @freeze_time("2024-01-15 09:00:00")
    def test_greeting_morning(self):
        assert greeting_for_time() == "Good morning"

    @freeze_time("2024-01-15 14:00:00")
    def test_greeting_afternoon(self):
        assert greeting_for_time() == "Good afternoon"

    @freeze_time("2024-01-15 20:00:00")
    def test_greeting_evening(self):
        assert greeting_for_time() == "Good evening"


class TestSessionExpiry:

    @freeze_time("2024-01-15 10:00:00")
    def test_fresh_session_not_expired(self):
        created = datetime.datetime(2024, 1, 15, 9, 0, 0)  # 1 hour ago
        assert not is_session_expired(created, ttl_hours=24)

    @freeze_time("2024-01-16 11:00:00")
    def test_expired_session(self):
        created = datetime.datetime(2024, 1, 15, 9, 0, 0)  # 26 hours ago
        assert is_session_expired(created, ttl_hours=24)

    @freeze_time("2024-01-15 09:00:01")
    def test_barely_not_expired(self):
        """Session created 24 hours minus 1 second ago — still valid."""
        created = datetime.datetime(2024, 1, 14, 9, 0, 2)
        assert not is_session_expired(created, ttl_hours=24)

    @freeze_time("2024-01-15 09:00:00")
    def test_exactly_on_boundary(self):
        """Session created exactly 24 hours ago — expired."""
        created = datetime.datetime(2024, 1, 14, 9, 0, 0)
        assert is_session_expired(created, ttl_hours=24)


# ── 2. Context manager tests ──────────────────────────────────────────────────

class TestContextManager:

    def test_filename_generation(self):
        with freeze_time("2024-06-15"):
            filename = generate_dated_filename("report")
        assert filename == "report_20240615.csv"

    def test_days_until_expiry_future(self):
        with freeze_time("2024-01-01"):
            future = datetime.date(2024, 1, 11)
            assert days_until_expiry(future) == 10

    def test_days_until_expiry_past(self):
        with freeze_time("2024-06-01"):
            past = datetime.date(2024, 5, 29)
            assert days_until_expiry(past) == -3

    def test_time_time_frozen(self):
        """time.time() is also frozen."""
        with freeze_time("2024-01-01 00:00:00"):
            t = time.time()
        assert t == pytest.approx(1704067200.0)    # Unix timestamp for 2024-01-01 UTC

    def test_uuid1_frozen(self):
        """uuid.uuid1() uses frozen time — two calls in same freeze give related UUIDs."""
        with freeze_time("2024-01-01"):
            u1 = uuid.uuid1()
            u2 = uuid.uuid1()
        # Both UUIDs have the same time component
        assert u1.time == u2.time

    def test_move_to(self):
        """move_to() shifts the frozen time within a context."""
        with freeze_time("2024-01-01") as frozen:
            assert datetime.date.today() == datetime.date(2024, 1, 1)
            frozen.move_to("2024-06-15")
            assert datetime.date.today() == datetime.date(2024, 6, 15)


# ── 3. Subscription lifecycle tests ──────────────────────────────────────────

class TestSubscriptionManager:
    """Test temporal business logic with time travel."""

    def test_new_subscription_is_active(self):
        mgr = SubscriptionManager()
        with freeze_time("2024-01-01"):
            mgr.subscribe("user_1", duration_days=30)
        with freeze_time("2024-01-15"):
            assert mgr.is_active("user_1") is True

    def test_subscription_expires(self):
        mgr = SubscriptionManager()
        with freeze_time("2024-01-01"):
            mgr.subscribe("user_1", duration_days=30)  # expires 2024-01-31
        with freeze_time("2024-02-01"):
            assert mgr.is_active("user_1") is False

    def test_days_remaining_midway(self):
        mgr = SubscriptionManager()
        with freeze_time("2024-01-01"):
            mgr.subscribe("user_1", duration_days=30)
        with freeze_time("2024-01-11"):
            remaining = mgr.days_remaining("user_1")
        assert remaining == 20

    def test_renewal_extends_from_today(self):
        mgr = SubscriptionManager()
        with freeze_time("2024-01-01"):
            mgr.subscribe("user_1", duration_days=30)

        # Renew close to expiry
        with freeze_time("2024-01-28"):
            new_expiry = mgr.subscribe("user_1", duration_days=30)

        assert new_expiry == datetime.date(2024, 2, 27)


# ── 4. Class-level freeze ─────────────────────────────────────────────────────

@freeze_time("2024-03-15 12:00:00")
class TestAllFrozenToMarch:
    """All test methods in this class share the same frozen time."""

    def test_a(self):
        assert datetime.date.today() == datetime.date(2024, 3, 15)

    def test_b(self):
        assert datetime.datetime.utcnow().hour == 12

    def test_c(self):
        assert get_current_timestamp() == "2024-03-15T12:00:00Z"


# ── 5. pytest fixture approach ────────────────────────────────────────────────

@pytest.fixture
def fixed_today():
    """Reusable fixture: pin date to 2024-01-01 for any test."""
    with freeze_time("2024-01-01 09:00:00") as frozen:
        yield frozen


class TestWithFixture:

    def test_uses_fixed_today(self, fixed_today):
        assert datetime.date.today() == datetime.date(2024, 1, 1)

    def test_move_within_fixture(self, fixed_today):
        fixed_today.move_to("2024-12-31")
        assert datetime.date.today() == datetime.date(2024, 12, 31)


# ── Demo ──────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("Freezegun Time Control Demo")
    print("=" * 50)

    print("\n1. Decorator freeze:")
    @freeze_time("2024-06-15 08:00:00")
    def show_frozen():
        print(f"   now()   = {datetime.datetime.now()}")
        print(f"   today() = {datetime.date.today()}")
        print(f"   time()  = {time.time()}")
    show_frozen()

    print("\n2. Context manager with move_to:")
    with freeze_time("2024-01-01") as frozen:
        print(f"   start: {datetime.date.today()}")
        frozen.move_to("2024-07-04")
        print(f"   after move: {datetime.date.today()}")

    print("\n3. Subscription expiry:")
    mgr = SubscriptionManager()
    with freeze_time("2024-01-01"):
        mgr.subscribe("alice", 30)
    with freeze_time("2024-01-20"):
        print(f"   alice active Jan 20: {mgr.is_active('alice')} ({mgr.days_remaining('alice')} days left)")
    with freeze_time("2024-02-05"):
        print(f"   alice active Feb 5:  {mgr.is_active('alice')}")

For the unittest.mock.patch("datetime.datetime") alternative — patching datetime.datetime directly requires replacing datetime.datetime.now with a Mock, handling datetime.date separately, patching time.time in a third call, and re-patching if any transitive import also uses datetime, while @freeze_time("2024-01-15") patches all time surfaces (datetime.now/utcnow, date.today, time.time, time.localtime, time.monotonic, uuid.uuid1) in a single decorator with no mock setup code. For the pytest-freezegun alternative — pytest-freezegun is a thin wrapper that requires the freezer fixture name while using Freezegun directly with @freeze_time on the class applies to every method at once, frozen_time.move_to() shifts time mid-test for expiry boundary tests, and tick=True lets you write real-sleep assertions while starting from a deterministic timestamp. The Claude Skills 360 bundle includes Freezegun skill sets covering @freeze_time decorator, context manager with move_to, class-level freeze, pytest fixture pattern, session and subscription expiry tests, time.time/uuid.uuid1 coverage, greeting hour tests, dated filename generation, boundary condition tests, and auto_tick_seconds for sleep-based code. Start with the free tier to try time-control testing code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free