Python datetimes made easy with timezone-aware datetime manipulation and human-readable formatting
—
Time manipulation utilities for deterministic testing. Pendulum provides comprehensive testing support including freezing time, traveling to specific moments, and managing time flow for reproducible tests.
Functions for freezing time at specific moments for deterministic testing.
def freeze() -> None:
"""
Freeze time at current moment.
All subsequent calls to now(), today(), etc. will return the same time
until travel_back() is called or time is unfrozen.
"""
def freeze(dt: DateTime) -> None:
"""
Freeze time at specific DateTime.
Parameters:
- dt: DateTime to freeze time at
"""Functions for moving through time during tests.
def travel(
years: int = 0,
months: int = 0,
weeks: int = 0,
days: int = 0,
hours: int = 0,
minutes: int = 0,
seconds: int = 0,
microseconds: int = 0
) -> None:
"""
Travel forward or backward in time by specified duration.
Parameters:
- years: Years to travel (positive for future, negative for past)
- months: Months to travel
- weeks: Weeks to travel
- days: Days to travel
- hours: Hours to travel
- minutes: Minutes to travel
- seconds: Seconds to travel
- microseconds: Microseconds to travel
Note: Time continues to flow normally after traveling
"""
def travel_to(dt: DateTime, freeze: bool = False) -> None:
"""
Travel to specific DateTime.
Parameters:
- dt: Target DateTime to travel to
- freeze: Whether to freeze time at the target DateTime
"""
def travel_back() -> None:
"""
Return to real time.
Unfreezes time and restores normal time flow.
"""The underlying time manipulation class that provides testing functionality.
class Traveller:
def __init__(self, datetime_class: type[DateTime]):
"""
Initialize Traveller for specific DateTime class.
Parameters:
- datetime_class: DateTime class to control
"""
def freeze(self, dt: DateTime | None = None) -> None:
"""
Freeze time at specific DateTime or current time.
Parameters:
- dt: DateTime to freeze at (None for current time)
"""
def travel(
self,
years: int = 0,
months: int = 0,
weeks: int = 0,
days: int = 0,
hours: int = 0,
minutes: int = 0,
seconds: int = 0,
microseconds: int = 0
) -> None:
"""Travel by specified duration"""
def travel_to(self, dt: DateTime, freeze: bool = False) -> None:
"""Travel to specific DateTime"""
def travel_back(self) -> None:
"""Return to real time"""import pendulum
import time
# Freeze time at current moment
print("Before freeze:", pendulum.now())
pendulum.freeze()
# Time is now frozen
time.sleep(2) # Wait 2 seconds
print("After 2 seconds:", pendulum.now()) # Same time as before
# All time functions return frozen time
print("Today:", pendulum.today())
print("Now:", pendulum.now())
print("Now UTC:", pendulum.now('UTC'))
# Unfreeze time
pendulum.travel_back()
print("After unfreeze:", pendulum.now()) # Real current timeimport pendulum
# Freeze at specific date/time
target_dt = pendulum.datetime(2024, 12, 25, 10, 30, 0, tz='UTC')
pendulum.freeze(target_dt)
print("Frozen at:", pendulum.now()) # 2024-12-25T10:30:00+00:00
print("Today:", pendulum.today()) # 2024-12-25T00:00:00+00:00
# All timezone-aware functions work with frozen time
print("Paris time:", pendulum.now('Europe/Paris'))
print("Tokyo time:", pendulum.now('Asia/Tokyo'))
pendulum.travel_back()import pendulum
print("Real time:", pendulum.now())
# Travel to the future
pendulum.travel(days=7, hours=3)
print("Future time:", pendulum.now()) # 7 days and 3 hours later
# Travel to the past
pendulum.travel(days=-14) # 14 days ago from current position
print("Past time:", pendulum.now())
# Travel back to real time
pendulum.travel_back()
print("Back to real time:", pendulum.now())import pendulum
# Travel to specific moment
birthday = pendulum.datetime(2024, 6, 15, 12, 0, 0)
pendulum.travel_to(birthday)
print("Traveled to:", pendulum.now())
print("Is birthday:", pendulum.now().format('MMMM Do, YYYY'))
# Travel and freeze
new_year = pendulum.datetime(2025, 1, 1, 0, 0, 0)
pendulum.travel_to(new_year, freeze=True)
print("New Year (frozen):", pendulum.now())
# Time is now frozen at New Year
pendulum.travel_back()import pendulum
def get_age(birth_date):
"""Calculate age based on current date"""
today = pendulum.today()
return today.year - birth_date.year - (
(today.month, today.day) < (birth_date.month, birth_date.day)
)
# Test age calculation
birth_date = pendulum.date(1990, 3, 15)
# Test before birthday in 2024
pendulum.travel_to(pendulum.datetime(2024, 3, 10))
age_before = get_age(birth_date)
print(f"Age before birthday: {age_before}") # 33
# Test after birthday in 2024
pendulum.travel_to(pendulum.datetime(2024, 3, 20))
age_after = get_age(birth_date)
print(f"Age after birthday: {age_after}") # 34
pendulum.travel_back()import pendulum
def is_business_hours():
"""Check if current time is during business hours (9 AM - 5 PM, Mon-Fri)"""
now = pendulum.now('America/New_York')
# Check if weekday (Monday=0, Friday=4)
if now.day_of_week > 4: # Saturday=5, Sunday=6
return False
# Check if between 9 AM and 5 PM
return 9 <= now.hour < 17
# Test during business hours
business_time = pendulum.datetime(2024, 3, 15, 14, 30, tz='America/New_York') # Friday 2:30 PM
pendulum.travel_to(business_time)
print(f"Business hours: {is_business_hours()}") # True
# Test outside business hours
weekend_time = pendulum.datetime(2024, 3, 16, 14, 30, tz='America/New_York') # Saturday 2:30 PM
pendulum.travel_to(weekend_time)
print(f"Weekend: {is_business_hours()}") # False
# Test after hours
evening_time = pendulum.datetime(2024, 3, 15, 18, 30, tz='America/New_York') # Friday 6:30 PM
pendulum.travel_to(evening_time)
print(f"After hours: {is_business_hours()}") # False
pendulum.travel_back()import pendulum
from contextlib import contextmanager
@contextmanager
def frozen_time(dt):
"""Context manager for temporarily freezing time"""
pendulum.freeze(dt)
try:
yield
finally:
pendulum.travel_back()
@contextmanager
def time_travel(dt):
"""Context manager for temporarily traveling to specific time"""
pendulum.travel_to(dt)
try:
yield
finally:
pendulum.travel_back()
# Use with context managers
test_time = pendulum.datetime(2024, 7, 4, 12, 0, 0)
with frozen_time(test_time):
print("Inside frozen context:", pendulum.now())
# Time is frozen at test_time
print("Outside context:", pendulum.now()) # Real time
with time_travel(test_time):
print("Inside travel context:", pendulum.now())
# Time continues flowing from test_time
print("Back to real time:", pendulum.now())import pendulum
def days_until_next_friday():
"""Calculate days until next Friday"""
today = pendulum.today()
days_ahead = 4 - today.day_of_week # Friday is day 4
if days_ahead <= 0: # Today is Friday or later
days_ahead += 7
return days_ahead
# Test from different days of the week
test_cases = [
("Monday", pendulum.date(2024, 3, 11)), # Monday
("Wednesday", pendulum.date(2024, 3, 13)), # Wednesday
("Friday", pendulum.date(2024, 3, 15)), # Friday
("Sunday", pendulum.date(2024, 3, 17)), # Sunday
]
for day_name, test_date in test_cases:
pendulum.travel_to(pendulum.datetime(test_date.year, test_date.month, test_date.day))
days = days_until_next_friday()
print(f"{day_name}: {days} days until Friday")
pendulum.travel_back()import pendulum
def get_market_status():
"""Get stock market status based on NYC time"""
ny_time = pendulum.now('America/New_York')
# Market closed on weekends
if ny_time.day_of_week > 4:
return "Closed (Weekend)"
# Market hours: 9:30 AM - 4:00 PM EST/EDT
if ny_time.time() < pendulum.time(9, 30):
return "Pre-market"
elif ny_time.time() < pendulum.time(16, 0):
return "Open"
else:
return "After-hours"
# Test during different times
test_times = [
("Pre-market", pendulum.datetime(2024, 3, 15, 8, 0, tz='America/New_York')),
("Market open", pendulum.datetime(2024, 3, 15, 12, 0, tz='America/New_York')),
("After hours", pendulum.datetime(2024, 3, 15, 18, 0, tz='America/New_York')),
("Weekend", pendulum.datetime(2024, 3, 16, 12, 0, tz='America/New_York')),
]
for description, test_time in test_times:
pendulum.travel_to(test_time)
status = get_market_status()
print(f"{description}: {status}")
pendulum.travel_back()import pendulum
import unittest
class TestTimeDependent(unittest.TestCase):
def setUp(self):
"""Set up test with known time"""
self.test_time = pendulum.datetime(2024, 3, 15, 10, 30, 0)
pendulum.freeze(self.test_time)
def tearDown(self):
"""Restore real time after each test"""
pendulum.travel_back()
def test_current_time(self):
"""Test that time is frozen during test"""
now1 = pendulum.now()
now2 = pendulum.now()
self.assertEqual(now1, now2)
self.assertEqual(now1, self.test_time)
def test_date_calculation(self):
"""Test date calculations with frozen time"""
today = pendulum.today()
expected = pendulum.date(2024, 3, 15)
self.assertEqual(today.date(), expected)
tomorrow = today.add(days=1)
expected_tomorrow = pendulum.date(2024, 3, 16)
self.assertEqual(tomorrow.date(), expected_tomorrow)
# Running the tests would work with frozen timeInstall with Tessl CLI
npx tessl i tessl/pypi-pendulum