CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-time-machine

Travel through time in your tests.

Overview
Eval results
Files

core-time-travel.mddocs/

Core Time Travel

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.

Capabilities

Travel Class

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 > time1

Coordinates Class

Represents 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 ends

Utility Functions

Functions 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)
    """

Nested Time Travel

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 2023

Timezone Handling

Time 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 timezone

Error Handling

Time 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}")

Module-level Time Functions

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)  # Mocked

Type Definitions

from 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

docs

cli-migration.md

core-time-travel.md

escape-hatch.md

index.md

pytest-integration.md

tile.json