Wednesday, October 15, 2025

Helpful Python Libraries You May Not Have Heard Of:  Freezegun


that testing our code is a vital and important a part of the software program improvement life cycle. That is maybe much more necessary once we’re discussing AI and ML methods, the place an inherent uncertainty and hallucinatory component are doubtlessly already baked in from the outset.

And inside that basic testing framework, testing code that behaves in a different way primarily based on the present date or time is usually a actual headache. How do you reliably verify logic that triggers solely at midnight, calculates relative dates (“2 hours in the past”), or handles tough conditions like leap years or month-ends? Manually mocking Python’s datetime module may be cumbersome and error-prone.

In the event you’ve ever wrestled with this, you’re not alone. However what when you may merely … cease time? And even journey by it inside your checks?

That’s exactly what the Freezegun library permits you to do. It’s a chic answer to a standard testing drawback, but many skilled Python builders have by no means heard of it.

Freezegun permits your Python checks to simulate particular moments in time by mocking the datetime, date, time, and pendulum Python modules. It’s easy to make use of however highly effective for creating deterministic and dependable checks for time-sensitive code.

Why is Freezegun so useful?

  1. Determinism. That is Freezegun’s major profit. Exams involving time grow to be fully predictable. Working datetime.now() inside a frozen block returns the identical frozen timestamp, eliminating flaky checks brought on by millisecond variations or date rollovers throughout check execution.
  2. Simplicity. In comparison with manually patching datetime.now or utilizing unittest.mock, Freezegun is commonly a lot cleaner and requires much less boilerplate code, particularly when briefly altering the time.
  3. Time Journey. Simply simulate particular dates and occasions — previous, current, or future. That is essential for testing edge circumstances, comparable to year-end processing, leap seconds, daylight saving time transitions, or just verifying logic tied to particular occasions.
  4. Relative Time Testing. Take a look at features that calculate relative occasions (e.g., “expires in 3 days”) by freezing time and creating timestamps relative to that frozen second.
  5. Tick Tock. Freezegun permits time to advance (“tick”) from the frozen second inside a check, which is ideal for testing timeouts, durations, or sequences of time-dependent occasions.

Hopefully, I’ve satisfied you that Freezegun might be a worthwhile addition to your Python toolbox. Let’s see it in motion by wanting by some pattern code snippets.

Organising a dev atmosphere

However earlier than that, let’s arrange a improvement atmosphere to experiment with. I take advantage of Miniconda for this, however you should utilize any software with which you’re acquainted.

I’m a Home windows consumer, however I usually develop utilizing WSL2 Ubuntu for Home windows, which is what I’ll be doing right here.

All of the code I present ought to work equally effectively beneath Home windows or Unix-like working methods.

# Create and activate a brand new dev atmosphere
#
(base) $ conda create -n freezegun python=3.12 -y
(base) $ conda activate freezegun

Now, we are able to set up the remaining vital libraries.

(freezegun) $ pip set up freezegun jupyter

I’ll be utilizing Jupyter Pocket book to run my code. To comply with alongside, sort jupyter pocket book into your command immediate. It is best to see a jupyter pocket book open in your browser. If that doesn’t occur mechanically, you’ll seemingly see a screenful of knowledge after the jupyter pocket book command. Close to the underside, you’ll discover a URL to repeat and paste into your browser to launch the Jupyter Pocket book.

Your URL can be totally different to mine, but it surely ought to look one thing like this:-

http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69da

A fast apart: The code I’m exhibiting in my examples beneath makes heavy use of the Python assert command. In the event you haven’t come throughout this operate earlier than or haven’t completed a lot unit testing in Python, assert is used to check if a situation is true, and if it isn’t, it raises an AssertionError.This helps catch points throughout improvement and is usually used for debugging and validating assumptions within the code.

Instance 1: Fundamental Time Freezing utilizing a Decorator

The commonest approach to make use of Freezegun is by way of its decorator@freeze_time, which lets you “set” a selected time of day to check numerous time-related features.

import datetime
from freezegun import freeze_time

def get_greeting():
    now = datetime.datetime.now()
    print(f"  Inside get_greeting(), now = {now}") # Added print
    if now.hour < 12:
        return "Good morning!"
    elif 12 <= now.hour < 18:
        return "Good afternoon!"
    else:
        return "Good night!"

# Take a look at the morning greeting
@freeze_time("2023-10-27 09:00:00")
def test_morning_greeting():
    print("Working test_morning_greeting:")
    greeting = get_greeting()
    print(f"  -> Bought greeting: '{greeting}'")
    assert greeting == "Good morning!"

# Take a look at the night greeting
@freeze_time("2023-10-27 21:30:00")
def test_evening_greeting():
    print("nRunning test_evening_greeting:")
    greeting = get_greeting()
    print(f"  -> Bought greeting: '{greeting}'")
    assert greeting == "Good night!"

# Run the checks
test_morning_greeting()
test_evening_greeting()
print("nBasic decorator checks handed!")

# --- Failure State of affairs ---
# What occurs if we do not freeze time?
print("n--- Working with out freeze_time (would possibly fail relying on precise time) ---")
def test_morning_greeting_unfrozen():
    print("Working test_morning_greeting_unfrozen:")
    greeting = get_greeting()
    print(f"  -> Bought greeting: '{greeting}'")
    # This assertion is now unreliable! It relies on once you run the code.
    attempt:
        assert greeting == "Good morning!" 
        print("  (Handed by probability)")
    besides AssertionError:
        print("  (Failed as anticipated - time wasn't 9 AM)")

test_morning_greeting_unfrozen()

And the output.

Working test_morning_greeting:
  Inside get_greeting(), now = 2023-10-27 09:00:00
  -> Bought greeting: 'Good morning!'

Working test_evening_greeting:
  Inside get_greeting(), now = 2023-10-27 21:30:00
  -> Bought greeting: 'Good night!'

Fundamental decorator checks handed!

--- Working with out freeze_time (would possibly fail relying on precise time) ---
Working test_morning_greeting_unfrozen:
  Inside get_greeting(), now = 2025-04-16 15:00:37.363367
  -> Bought greeting: 'Good afternoon!'
  (Failed as anticipated - time wasn't 9 AM)

Instance 2: Fundamental Time Freezing utilizing a Context Supervisor

Create a “block” of frozen time.

import datetime
from freezegun import freeze_time

def process_batch_job():
    start_time = datetime.datetime.now()
    # Simulate work
    end_time = datetime.datetime.now() # In actuality, time would cross
    print(f"  Inside job: Begin={start_time}, Finish={end_time}") # Added print
    return (start_time, end_time)

def test_job_timestamps_within_frozen_block():
    print("nRunning test_job_timestamps_within_frozen_block:")
    frozen_time_str = "2023-11-15 10:00:00"
    with freeze_time(frozen_time_str):
        print(f"  Getting into frozen block at {frozen_time_str}")
        begin, finish = process_batch_job()
        
        print(f"  Asserting begin == finish: {begin} == {finish}")
        assert begin == finish
        print(f"  Asserting begin == frozen time: {begin} == {datetime.datetime(2023, 11, 15, 10, 0, 0)}")
        assert begin == datetime.datetime(2023, 11, 15, 10, 0, 0)
        print("  Assertions inside block handed.")
        
    print("  Exited frozen block.")
    now_outside = datetime.datetime.now()
    print(f"  Time outdoors block: {now_outside} (ought to be actual time)")
    # This assertion simply exhibits time is unfrozen, worth relies on actual time
    assert now_outside != datetime.datetime(2023, 11, 15, 10, 0, 0)

test_job_timestamps_within_frozen_block()
print("nContext supervisor check handed!")

The output.

 Working test_job_timestamps_within_frozen_block:
 Getting into frozen block at 2023-11-15 10:00:00
 Inside job: Begin=2023-11-15 10:00:00, Finish=2023-11-15 10:00:00
 Asserting begin == finish: 2023-11-15 10:00:00 == 2023-11-15 10:00:00
 Asserting begin == frozen time: 2023-11-15 10:00:00 == 2023-11-15 10:00:00
 Assertions inside block handed.
 Exited frozen block.
 Time outdoors block: 2025-04-16 15:10:15.231632 (ought to be actual time)

 Context supervisor check handed!

Instance 3: Advancing Time with tick

Simulate time passing inside a frozen interval.

import datetime
import time
from freezegun import freeze_time

def check_if_event_expired(event_timestamp, expiry_duration_seconds):
    now = datetime.datetime.now()
    expired = now > event_timestamp + datetime.timedelta(seconds=expiry_duration_seconds)
    print(f"  Checking expiry: Now={now}, Occasion={event_timestamp}, ExpiresAt={event_timestamp + datetime.timedelta(seconds=expiry_duration_seconds)} -> Expired={expired}")
    return expired

# --- Handbook ticking utilizing context supervisor ---
def test_event_expiry_manual_tick():
    print("nRunning test_event_expiry_manual_tick:")

    with freeze_time("2023-10-27 12:00:00") as freezer:
        event_time_in_freeze = datetime.datetime.now()
        expiry_duration = 60
        print(f"  Occasion created at: {event_time_in_freeze}")

        print("  Checking instantly after creation:")
        assert not check_if_event_expired(event_time_in_freeze, expiry_duration)

        # Advance time by 61 seconds
        delta_to_tick = datetime.timedelta(seconds=61)
        print(f"  Ticking ahead by {delta_to_tick}...")
        freezer.tick(delta=delta_to_tick)

        print(f"  Time after ticking: {datetime.datetime.now()}")
        print("  Checking after ticking:")
        assert check_if_event_expired(event_time_in_freeze, expiry_duration)

        print("  Handbook tick check completed.")

# --- Failure State of affairs ---
@freeze_time("2023-10-27 12:00:00")  # No tick=True or guide tick
def test_event_expiry_fail_without_tick():
    print("n--- Working test_event_expiry_fail_without_tick (EXPECT ASSERTION ERROR) ---")
    event_time = datetime.datetime.now()
    expiry_duration = 60
    print(f"  Occasion created at: {event_time}")

    # Simulate work or ready - with out tick, time does not advance!
    time.sleep(0.1)

    print(f"  Time after simulated wait: {datetime.datetime.now()}")
    print("  Checking expiry (incorrectly, time did not transfer):")
    attempt:
        # This could ideally be True, however can be False with out ticking
        assert check_if_event_expired(event_time, expiry_duration)
    besides AssertionError:
        print("  AssertionError: Occasion didn't expire, as anticipated with out tick.")
    print("  Failure state of affairs completed.")

# Run each checks
test_event_expiry_manual_tick()
test_event_expiry_fail_without_tick()

This outputs the next.

Working test_event_expiry_manual_tick:
  Occasion created at: 2023-10-27 12:00:00
  Checking instantly after creation:
  Checking expiry: Now=2023-10-27 12:00:00, Occasion=2023-10-27 12:00:00, ExpiresAt=2023-10-27 12:01:00 -> Expired=False
  Ticking ahead by 0:01:01...
  Time after ticking: 2023-10-27 12:01:01
  Checking after ticking:
  Checking expiry: Now=2023-10-27 12:01:01, Occasion=2023-10-27 12:00:00, ExpiresAt=2023-10-27 12:01:00 -> Expired=True
  Handbook tick check completed.

--- Working test_event_expiry_fail_without_tick (EXPECT ASSERTION ERROR) ---
  Occasion created at: 2023-10-27 12:00:00
  Time after simulated wait: 2023-10-27 12:00:00
  Checking expiry (incorrectly, time did not transfer):
  Checking expiry: Now=2023-10-27 12:00:00, Occasion=2023-10-27 12:00:00, ExpiresAt=2023-10-27 12:01:00 -> Expired=False
  AssertionError: Occasion didn't expire, as anticipated with out tick.
  Failure state of affairs completed.

Instance 4: Testing Relative Dates

Freezegun ensures steady “time in the past” logic.

import datetime
from freezegun import freeze_time

def format_relative_time(timestamp):
    now = datetime.datetime.now()
    delta = now - timestamp
    
    rel_time_str = ""
    if delta.days > 0:
        rel_time_str = f"{delta.days} days in the past"
    elif delta.seconds >= 3600:
        hours = delta.seconds // 3600
        rel_time_str = f"{hours} hours in the past"
    elif delta.seconds >= 60:
        minutes = delta.seconds // 60
        rel_time_str = f"{minutes} minutes in the past"
    else:
        rel_time_str = "simply now"
    print(f"  Formatting relative time: Now={now}, Timestamp={timestamp} -> '{rel_time_str}'")
    return rel_time_str

@freeze_time("2023-10-27 15:00:00")
def test_relative_time_formatting():
    print("nRunning test_relative_time_formatting:")
    
    # Occasion occurred 2 days and three hours in the past relative to frozen time
    past_event = datetime.datetime(2023, 10, 25, 12, 0, 0)
    assert format_relative_time(past_event) == "2 days in the past"

    # Occasion occurred 45 minutes in the past
    recent_event = datetime.datetime.now() - datetime.timedelta(minutes=45)
    assert format_relative_time(recent_event) == "45 minutes in the past"

    # Occasion occurred simply now
    current_event = datetime.datetime.now() - datetime.timedelta(seconds=10)
    assert format_relative_time(current_event) == "simply now"
    
    print("  Relative time checks handed!")

test_relative_time_formatting()

# --- Failure State of affairs ---
print("n--- Working relative time with out freeze_time (EXPECT FAILURE) ---")
def test_relative_time_unfrozen():
    # Use the identical previous occasion timestamp
    past_event = datetime.datetime(2023, 10, 25, 12, 0, 0) 
    print(f"  Testing with past_event = {past_event}")
    # This can examine towards the *precise* present time, not Oct twenty seventh, 2023
    formatted_time = format_relative_time(past_event)
    attempt:
        assert formatted_time == "2 days in the past" 
    besides AssertionError:
        # The precise distinction can be a lot bigger!
        print(f"  AssertionError: Anticipated '2 days in the past', however obtained '{formatted_time}'. Failed as anticipated.")

test_relative_time_unfrozen()

The output.

Working test_relative_time_formatting:
  Formatting relative time: Now=2023-10-27 15:00:00, Timestamp=2023-10-25 12:00:00 -> '2 days in the past'
  Formatting relative time: Now=2023-10-27 15:00:00, Timestamp=2023-10-27 14:15:00 -> '45 minutes in the past'
  Formatting relative time: Now=2023-10-27 15:00:00, Timestamp=2023-10-27 14:59:50 -> 'simply now'
  Relative time checks handed!

--- Working relative time with out freeze_time (EXPECT FAILURE) ---
  Testing with past_event = 2023-10-25 12:00:00
  Formatting relative time: Now=2023-10-27 12:00:00, Timestamp=2023-10-25 12:00:00 -> '2 days in the past'

Instance 5: Dealing with Particular Dates (Finish of Month)

Take a look at edge circumstances, comparable to leap years, reliably.

import datetime
from freezegun import freeze_time

def is_last_day_of_month(check_date):
    next_day = check_date + datetime.timedelta(days=1)
    is_last = next_day.month != check_date.month
    print(f"  Checking if {check_date} is final day of month: Subsequent day={next_day}, IsLast={is_last}")
    return is_last

print("nRunning particular date logic checks:")

@freeze_time("2023-02-28") # Non-leap 12 months
def test_end_of_february_non_leap():
    at the moment = datetime.date.at the moment()
    assert is_last_day_of_month(at the moment) is True

@freeze_time("2024-02-28") # Intercalary year
def test_end_of_february_leap_not_yet():
     at the moment = datetime.date.at the moment()
     assert is_last_day_of_month(at the moment) is False # Feb twenty ninth exists

@freeze_time("2024-02-29") # Intercalary year - final day
def test_end_of_february_leap_actual():
    at the moment = datetime.date.at the moment()
    assert is_last_day_of_month(at the moment) is True

@freeze_time("2023-12-31")
def test_end_of_year():
    at the moment = datetime.date.at the moment()
    assert is_last_day_of_month(at the moment) is True

test_end_of_february_non_leap()
test_end_of_february_leap_not_yet()
test_end_of_february_leap_actual()
test_end_of_year()
print("Particular date logic checks handed!")



#
# Output
#


Working particular date logic checks:
Checking if 2023-02-28 is final day of month: Subsequent day=2023-03-01, IsLast=True
Checking if 2024-02-28 is final day of month: Subsequent day=2024-02-29, IsLast=False
Checking if 2024-02-29 is final day of month: Subsequent day=2024-03-01, IsLast=True
Checking if 2023-12-31 is final day of month: Subsequent day=2024-01-01, IsLast=True
pecific date logic checks handed!

Instance 6: Time Zones

Take a look at timezone-aware code accurately, dealing with offsets and transitions like BST/GMT.

# Requires Python 3.9+ for zoneinfo or `pip set up pytz` for older variations
import datetime
from freezegun import freeze_time
attempt:
    from zoneinfo import ZoneInfo # Python 3.9+
besides ImportError:
    from pytz import timezone as ZoneInfo # Fallback for older Python/pytz

def get_local_and_utc_time():
    # Assume native timezone is Europe/London for this instance
    local_tz = ZoneInfo("Europe/London")
    now_utc = datetime.datetime.now(datetime.timezone.utc)
    now_local = now_utc.astimezone(local_tz)
    print(f"  Getting occasions: UTC={now_utc}, Native={now_local} ({now_local.tzname()})")
    return now_local, now_utc

# Freeze time as 9 AM UTC. London is UTC+1 in summer time (BST). Oct 27 is BST.
@freeze_time("2023-10-27 09:00:00", tz_offset=0) # tz_offset=0 means the frozen time string IS UTC
def test_time_in_london_bst():
    print("nRunning test_time_in_london_bst:")
    local_time, utc_time = get_local_and_utc_time()
    assert utc_time.hour == 9
    assert local_time.hour == 10 # London is UTC+1 on this date
    assert local_time.tzname() == "BST" 

# Freeze time as 9 AM UTC. Use December twenty seventh, which is GMT (UTC+0)
@freeze_time("2023-12-27 09:00:00", tz_offset=0)
def test_time_in_london_gmt():
    print("nRunning test_time_in_london_gmt:")
    local_time, utc_time = get_local_and_utc_time()
    assert utc_time.hour == 9
    assert local_time.hour == 9 # London is UTC+0 on this date
    assert local_time.tzname() == "GMT"

test_time_in_london_bst()
test_time_in_london_gmt()
print("nTimezone checks handed!")

#
# Output
#

 Working test_time_in_london_bst:
 Getting occasions: UTC=2023-10-27 09:00:00+00:00, Native=2023-10-27 10:00:00+01:00 (BST)

 Working test_time_in_london_gmt:
 Getting occasions: UTC=2023-12-27 09:00:00+00:00, Native=2023-12-27 09:00:00+00:00 (GMT)

 Timezone checks handed!

Instance 7: Express Time Journey with the move_to operate

Leap between particular time factors in a single check for complicated temporal sequences.

import datetime
from freezegun import freeze_time

class ReportGenerator:
    def __init__(self):
        self.creation_time = datetime.datetime.now()
        self.information = {"standing": "pending", "generated_at": None}
        print(f"  Report created at {self.creation_time}")

    def generate(self):
        self.information["status"] = "generated"
        self.information["generated_at"] = datetime.datetime.now()
        print(f"  Report generated at {self.information['generated_at']}")

    def get_status_update(self):
        now = datetime.datetime.now()
        if self.information["status"] == "generated":
            time_since_generation = now - self.information["generated_at"]
            standing = f"Generated {time_since_generation.seconds} seconds in the past."
        else:
            time_since_creation = now - self.creation_time
            standing = f"Pending for {time_since_creation.seconds} seconds."
        print(f"  Standing replace at {now}: '{standing}'")
        return standing

def test_report_lifecycle():
    print("nRunning test_report_lifecycle:")
    with freeze_time("2023-11-01 10:00:00") as freezer:
        report = ReportGenerator()
        assert report.information["status"] == "pending"
        
        # Examine standing after 5 seconds
        target_time = datetime.datetime(2023, 11, 1, 10, 0, 5)
        print(f"  Shifting time to {target_time}")
        freezer.move_to(target_time)
        assert report.get_status_update() == "Pending for five seconds."

        # Generate the report at 10:01:00
        target_time = datetime.datetime(2023, 11, 1, 10, 1, 0)
        print(f"  Shifting time to {target_time} and producing report")
        freezer.move_to(target_time)
        report.generate()
        assert report.information["status"] == "generated"
        assert report.get_status_update() == "Generated 0 seconds in the past."

        # Examine standing 30 seconds after technology
        target_time = datetime.datetime(2023, 11, 1, 10, 1, 30)
        print(f"  Shifting time to {target_time}")
        freezer.move_to(target_time)
        assert report.get_status_update() == "Generated 30 seconds in the past."
        
    print("  Advanced lifecycle check handed!")

test_report_lifecycle()

# --- Failure State of affairs ---
def test_report_lifecycle_fail_forgot_move():
    print("n--- Working lifecycle check (FAIL - forgot move_to) ---")
    with freeze_time("2023-11-01 10:00:00") as freezer:
        report = ReportGenerator()
        assert report.information["status"] == "pending"
        
        # We INTEND to verify standing after 5 seconds, however FORGET to maneuver time
        print(f"  Checking standing (time continues to be {datetime.datetime.now()})")
        # freezer.move_to("2023-11-01 10:00:05") # <-- Forgotten!
        attempt:
            assert report.get_status_update() == "Pending for five seconds."
        besides AssertionError as e:
            print(f"  AssertionError: {e}. Failed as anticipated.")
            
test_report_lifecycle_fail_forgot_move()

Right here’s the output.

Working test_report_lifecycle:
  Report created at 2023-11-01 10:00:00
  Shifting time to 2023-11-01 10:00:05
  Standing replace at 2023-11-01 10:00:05: 'Pending for five seconds.'
  Shifting time to 2023-11-01 10:01:00 and producing report
  Report generated at 2023-11-01 10:01:00
  Standing replace at 2023-11-01 10:01:00: 'Generated 0 seconds in the past.'
  Shifting time to 2023-11-01 10:01:30
  Standing replace at 2023-11-01 10:01:30: 'Generated 30 seconds in the past.'
  Advanced lifecycle check handed!

--- Working lifecycle check (FAIL - forgot move_to) ---
  Report created at 2023-11-01 10:00:00
  Checking standing (time continues to be 2023-11-01 10:00:00)
  Standing replace at 2023-11-01 10:00:00: 'Pending for 0 seconds.'
  AssertionError: . Failed as anticipated.

Abstract

Freezegun is a implausible software for any Python developer who wants to check code involving dates and occasions. It transforms doubtlessly flaky, hard-to-write checks into easy, sturdy, and deterministic ones. By permitting you to freeze, tick, and journey by time with ease — and by making it clear when time isn’t managed — it unlocks the flexibility to successfully and reliably check beforehand difficult situations.

For instance this, I offered a number of examples protecting totally different situations involving date and time testing and confirmed how utilizing Freezegun eliminates lots of the obstacles {that a} conventional testing framework would possibly encounter.

Whereas we’ve lined the core functionalities, you are able to do extra with Freezegun, and I like to recommend testing its GitHub web page.

In brief, Freezegun is a library it’s best to know and use in case your code offers with time and it’s essential check it totally and reliably.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles

PHP Code Snippets Powered By : XYZScripts.com