NAME
Test::Mockingbird::TimeTravel - Deterministic, controllable time for Perl tests
VERSION
Version 0.12
SYNOPSIS
use Test::Mockingbird::TimeTravel qw(
now freeze_time travel_to advance_time rewind_time
restore_all with_frozen_time
);
freeze_time('2025-01-01T00:00:00Z');
is now(), 1735689600, 'time is frozen';
advance_time(2 => 'minutes');
is now(), 1735689720, 'time advanced deterministically';
with_frozen_time '2025-01-02T12:00:00Z' => sub {
is now(), 1735819200, 'block sees overridden time';
};
is now(), 1735689720, 'outer time restored after block';
restore_all();
isnt now(), 1735689720, 'real time restored';
DESCRIPTION
Test::Mockingbird::TimeTravel provides a lightweight, deterministic time-control layer for Perl tests. It does not monkey-patch CORE::time(). Instead it provides a dedicated now() function and a small set of declarative operations that manipulate an internal frozen clock.
LIMITATIONS
now()is not a drop-in fortime()-
Production code must call
now()for time control to take effect. Code that callsCORE::time(),POSIX::time(), orTime::HiRes::time()directly is not affected. - Fractional seconds not supported
-
All timestamps are integer epoch seconds. Sub-second resolution is not available.
- Thread safety
-
$ACTIVE,$CURRENT_EPOCH, and$BASE_EPOCHare package globals. Concurrent threads that manipulate time state will race.
METHODS
now
Return the current time.
Returns $CURRENT_EPOCH when frozen, CORE::time() otherwise.
API SPECIFICATION
Input (Params::Validate::Strict schema)
none
Output (Returns::Set schema)
returns: Int -- epoch seconds
FORMAL SPECIFICATION
now ≙ ($ACTIVE = 1 ⇒ returns $CURRENT_EPOCH) ∧ ($ACTIVE = 0 ⇒ returns CORE::time())
freeze_time
Freeze the clock at a specific timestamp.
my $epoch = freeze_time('2025-01-01T00:00:00Z');
API SPECIFICATION
Input (Params::Validate::Strict schema)
$timestamp -- Str|Int (see _parse_timestamp for accepted formats)
Output (Returns::Set schema)
returns: Int -- frozen epoch
MESSAGES
"Invalid timestamp format for TimeTravel: ..." -- unrecognised format
FORMAL SPECIFICATION
freeze_time ≙
∀ ts : Str|Int •
post $ACTIVE' = 1 ∧ $CURRENT_EPOCH' = parse(ts) ∧ $BASE_EPOCH' = parse(ts)
travel_to
Move the frozen clock to a new timestamp without unfreezing.
travel_to('2025-06-01T00:00:00Z');
Croaks if called while TimeTravel is inactive.
API SPECIFICATION
Input (Params::Validate::Strict schema)
$timestamp -- Str|Int
Output (Returns::Set schema)
returns: Int -- new epoch
MESSAGES
"travel_to() called while TimeTravel is inactive" -- freeze_time() not called first
FORMAL SPECIFICATION
travel_to ≙
pre $ACTIVE = 1
post $CURRENT_EPOCH' = parse(ts) ∧ $BASE_EPOCH unchanged ∧ $ACTIVE unchanged
advance_time
Advance the frozen clock by a duration.
advance_time(30); # 30 seconds
advance_time(2 => 'minutes'); # 2 minutes
Croaks if called while TimeTravel is inactive.
API SPECIFICATION
Input (Params::Validate::Strict schema)
$amount -- Int
$unit -- Str, optional (second|minute|hour|day, plural forms ok)
Output (Returns::Set schema)
returns: Int -- new epoch
MESSAGES
"advance_time() called while TimeTravel is inactive" -- not frozen
"Unknown time unit '...'" -- unrecognised unit string
FORMAL SPECIFICATION
advance_time ≙
pre $ACTIVE = 1
post $CURRENT_EPOCH' = $CURRENT_EPOCH + to_seconds(amount, unit)
rewind_time
Rewind the frozen clock by a duration.
rewind_time(30); # 30 seconds
rewind_time(1 => 'hour'); # 1 hour
Croaks if called while TimeTravel is inactive.
API SPECIFICATION
Input (Params::Validate::Strict schema)
$amount -- Int
$unit -- Str, optional
Output (Returns::Set schema)
returns: Int -- new epoch
MESSAGES
"rewind_time() called while TimeTravel is inactive" -- not frozen
FORMAL SPECIFICATION
rewind_time ≙
pre $ACTIVE = 1
post $CURRENT_EPOCH' = $CURRENT_EPOCH - to_seconds(amount, unit)
restore_all
Return to real time and clear all frozen state.
restore_all();
Idempotent. Called automatically by Test::Mockingbird::DeepMock.
API SPECIFICATION
Input (Params::Validate::Strict schema)
none
Output (Returns::Set schema)
returns: undef
FORMAL SPECIFICATION
restore_all ≙
post $ACTIVE' = 0 ∧ $CURRENT_EPOCH' = undef ∧ $BASE_EPOCH' = undef
with_frozen_time
Temporarily override time inside a code block, restoring previous state afterward even if the block throws.
with_frozen_time '2025-01-02T12:00:00Z' => sub {
is now(), 1735819200, 'block sees overridden time';
};
Fully nestable.
API SPECIFICATION
Input (Params::Validate::Strict schema)
$timestamp -- Str|Int
$code -- CodeRef
Output (Returns::Set schema)
returns: Any -- whatever $code returns
MESSAGES
"with_frozen_time() requires a coderef" -- second arg not a CodeRef
"with_frozen_time() requires a timestamp" -- first arg undefined
FORMAL SPECIFICATION
with_frozen_time ≙
∀ ts : Str|Int; code : CodeRef •
pre defined(ts) ∧ ref(code) = 'CODE'
post (save prev_state; freeze(ts); run code; restore prev_state)
∧ exceptions rethrown
SUPPORT
Please report bugs at https://github.com/nigelhorne/Test-Mockingbird/issues.
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
SEE ALSO
REPOSITORY
https://github.com/nigelhorne/Test-Mockingbird
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to GPL2 licence terms.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 122:
Non-ASCII character seen before =encoding in '≙'. Assuming UTF-8