Travel through time in your tests.
The core time travel functionality enables deterministic testing of time-dependent code by mocking Python's time and datetime functions. The system supports multiple destination formats, nested time travel, and both static and advancing time modes.
The primary interface for time travel, usable as a context manager, decorator, or imperative controller. Supports various destination formats and tick modes for different testing scenarios.
class travel:
def __init__(self, destination: DestinationType, *, tick: bool = True):
"""
Initialize time travel to a specific destination.
Parameters:
- destination: Target time (timestamp, datetime, string, etc.)
- tick: Whether time advances during travel (default: True)
"""
def start(self) -> Coordinates:
"""
Start time travel and return coordinates for manipulation.
Returns:
Coordinates object for time manipulation during travel
"""
def stop(self) -> None:
"""Stop time travel and restore real time."""
def __enter__(self) -> Coordinates:
"""Context manager entry - starts time travel."""
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
"""Context manager exit - stops time travel."""
async def __aenter__(self) -> Coordinates:
"""Async context manager entry - starts time travel."""
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
"""Async context manager exit - stops time travel."""
def __call__(self, wrapped):
"""
Decorator functionality for functions, async functions, and TestCase classes.
Parameters:
- wrapped: Function, async function, or unittest.TestCase subclass
Returns:
Decorated function or class with time travel applied
"""Usage examples:
import time_machine
from datetime import datetime, timezone, timedelta
# Context manager with datetime object
with time_machine.travel(datetime(2023, 1, 1, tzinfo=timezone.utc)):
print(datetime.now()) # 2023-01-01 00:00:00+00:00
# Context manager with timestamp
with time_machine.travel(0): # Unix epoch
print(datetime.now()) # 1970-01-01 00:00:00
# Context manager with ISO string
with time_machine.travel("2023-06-15T10:30:00Z"):
print(datetime.now()) # 2023-06-15 10:30:00+00:00
# Decorator on function
@time_machine.travel("2023-01-01")
def test_new_year():
assert datetime.now().year == 2023
# Decorator on async function
@time_machine.travel(datetime(2023, 1, 1))
async def async_test():
assert datetime.now().year == 2023
# Static time mode (tick=False)
with time_machine.travel("2023-01-01", tick=False):
time1 = datetime.now()
time.sleep(0.1) # Won't advance time
time2 = datetime.now()
assert time1 == time2
# Advancing time mode (tick=True, default)
with time_machine.travel("2023-01-01", tick=True):
time1 = datetime.now()
time.sleep(0.1) # Time advances
time2 = datetime.now()
assert time2 > time1Represents the current time travel state and provides methods for manipulating time during travel. Returned by travel.start() and passed to context manager blocks.
class Coordinates:
def time(self) -> float:
"""
Get current travel time as Unix timestamp.
Returns:
Current time as float seconds since Unix epoch
"""
def time_ns(self) -> int:
"""
Get current travel time as nanosecond timestamp.
Returns:
Current time as int nanoseconds since Unix epoch
"""
def shift(self, delta: dt.timedelta | int | float) -> None:
"""
Shift time by a relative amount.
Parameters:
- delta: Time delta as timedelta object, or seconds as int/float
"""
def move_to(self, destination: DestinationType, tick: bool | None = None) -> None:
"""
Move to a new absolute time destination.
Parameters:
- destination: New time destination (same formats as travel())
- tick: Override tick mode for new destination (optional)
"""Usage examples:
# Time manipulation with shift()
with time_machine.travel("2023-01-01 12:00:00") as traveller:
print(datetime.now()) # 2023-01-01 12:00:00
# Shift forward 1 hour
traveller.shift(3600)
print(datetime.now()) # 2023-01-01 13:00:00
# Shift backward 30 minutes using timedelta
traveller.shift(timedelta(minutes=-30))
print(datetime.now()) # 2023-01-01 12:30:00
# Time manipulation with move_to()
with time_machine.travel(0) as traveller:
print(datetime.now()) # 1970-01-01 00:00:00
# Jump to different date
traveller.move_to("2023-12-25 09:00:00")
print(datetime.now()) # 2023-12-25 09:00:00
# Change tick mode during travel
traveller.move_to("2023-01-01", tick=False)
# Time is now static until travel endsFunctions for extracting time information and converting between formats.
def extract_timestamp_tzname(destination: DestinationType) -> tuple[float, str | None]:
"""
Extract timestamp and timezone name from a destination.
Parameters:
- destination: Time destination in any supported format
Returns:
Tuple of (timestamp_float, timezone_name_or_none)
"""Time Machine supports nested time travel with a coordinate stack system:
# Nested time travel
with time_machine.travel("2023-01-01"):
print(datetime.now().year) # 2023
with time_machine.travel("2024-06-15"):
print(datetime.now().year) # 2024
print(datetime.now().month) # 6
print(datetime.now().year) # Back to 2023Time Machine supports timezone-aware time travel:
from zoneinfo import ZoneInfo
# Travel with timezone
eastern = ZoneInfo("America/New_York")
utc_time = datetime(2023, 6, 15, 16, 0, tzinfo=timezone.utc)
eastern_time = utc_time.astimezone(eastern)
with time_machine.travel(eastern_time):
# Environment TZ is set to America/New_York
local_time = datetime.now()
print(local_time) # Reflects Eastern timezoneTime Machine validates destination formats and raises appropriate exceptions:
# Invalid destination type
try:
time_machine.travel([1, 2, 3]) # Lists not supported
except TypeError as e:
print(f"Unsupported destination: {e}")
# Invalid string format
try:
time_machine.travel("not-a-date")
except ValueError as e:
print(f"Cannot parse date: {e}")Direct access to time-machine's patched time functions. These functions are automatically patched during time travel to return mocked time values.
def now(tz: dt.tzinfo | None = None) -> dt.datetime:
"""
Get current datetime during time travel.
Parameters:
- tz: Optional timezone info
Returns:
Current datetime object (mocked during travel)
"""
def utcnow() -> dt.datetime:
"""
Get current UTC datetime (naive) during time travel.
Returns:
Current UTC datetime without timezone info (mocked during travel)
"""
def clock_gettime(clk_id: int) -> float:
"""
Get clock time for specified clock ID.
Parameters:
- clk_id: Clock identifier (e.g., time.CLOCK_REALTIME)
Returns:
Clock time as float seconds (mocked for CLOCK_REALTIME during travel)
"""
def clock_gettime_ns(clk_id: int) -> int:
"""
Get clock time in nanoseconds for specified clock ID.
Parameters:
- clk_id: Clock identifier
Returns:
Clock time as int nanoseconds (mocked for CLOCK_REALTIME during travel)
"""
def gmtime(secs: float | None = None) -> struct_time:
"""
Convert timestamp to GMT time struct.
Parameters:
- secs: Optional timestamp, uses current travel time if None
Returns:
GMT time as struct_time (mocked during travel)
"""
def localtime(secs: float | None = None) -> struct_time:
"""
Convert timestamp to local time struct.
Parameters:
- secs: Optional timestamp, uses current travel time if None
Returns:
Local time as struct_time (mocked during travel)
"""
def strftime(format: str, t: tuple | struct_time | None = None) -> str:
"""
Format time using strftime.
Parameters:
- format: Format string
- t: Optional time tuple, uses current travel time if None
Returns:
Formatted time string (mocked during travel)
"""
def time() -> float:
"""
Get current time as Unix timestamp.
Returns:
Current time as float seconds since Unix epoch (mocked during travel)
"""
def time_ns() -> int:
"""
Get current time as nanosecond timestamp.
Returns:
Current time as int nanoseconds since Unix epoch (mocked during travel)
"""Usage examples:
import time_machine
import time
from datetime import datetime
from time import struct_time
# Module-level functions are automatically patched during travel
with time_machine.travel("2023-01-01 12:00:00"):
# These all return mocked time values
current_time = time_machine.time() # 1672574400.0 (2023-01-01 12:00:00)
current_ns = time_machine.time_ns() # 1672574400000000000
current_dt = time_machine.now() # 2023-01-01 12:00:00
current_utc = time_machine.utcnow() # 2023-01-01 12:00:00 (naive)
# Time formatting functions
gmt_struct = time_machine.gmtime() # struct_time for 2023-01-01 12:00:00 GMT
local_struct = time_machine.localtime() # struct_time for 2023-01-01 12:00:00 local
formatted = time_machine.strftime("%Y-%m-%d %H:%M:%S") # "2023-01-01 12:00:00"
# Clock functions (CLOCK_REALTIME is mocked, others use real values)
real_time = time_machine.clock_gettime(time.CLOCK_REALTIME) # Mocked
real_time_ns = time_machine.clock_gettime_ns(time.CLOCK_REALTIME) # Mockedfrom time import struct_time
DestinationBaseType = Union[
int, # Unix timestamp (seconds)
float, # Unix timestamp with fractional seconds
dt.datetime, # Datetime object (timezone-aware recommended)
dt.timedelta, # Relative time from current moment
dt.date, # Date object (converted to midnight UTC)
str, # ISO 8601 datetime string or parseable date string
]
DestinationType = Union[
DestinationBaseType,
Callable[[], DestinationBaseType], # Function returning destination
Generator[DestinationBaseType, None, None], # Generator yielding destinations
]
_TimeTuple = tuple[int, int, int, int, int, int, int, int, int]Install with Tessl CLI
npx tessl i tessl/pypi-time-machine