nexusphp / clock
Nexus Clock decouples applications from calendar time, monotonic time, and waiting for better testing.
Requires
- php: ^8.3
- psr/clock: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.34 || ^0.35
- nexusphp/cs-config: ^3.29
- nexusphp/tachycardia: ^2.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.2
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.5 || ^13.3
Provides
This package is auto-updated.
Last update: 2026-08-25 19:36:34 UTC
README
Nexus Clock decouples applications from calendar time, monotonic time, and waiting for better testing.
Requirements
- PHP 8.3+
- Composer
Installation
composer require nexusphp/clock
Usage
Reading calendar time
Clock is PSR-20's ClockInterface and nothing more. Use it wherever code needs to know
what time it is in the world: timestamps, due dates, expiry checks.
use Nexus\Clock\Clock; use Nexus\Clock\SystemClock; final readonly class AuditTrail { public function __construct( private Clock $clock = new SystemClock(), ) {} public function record(string $event): string { return \sprintf('[%s] %s', $this->clock->now()->format(\DATE_ATOM), $event); } }
SystemClock defaults to UTC, the only value a library should ever pick. Pass a
\DateTimeZone or a timezone name when the application knows better.
Measuring elapsed time
Stopwatch answers how long since some earlier moment. Wall time is the wrong instrument for
durations, since NTP steps, manual adjustment, and suspend/resume all corrupt a difference of
two calendar readings. HighResolutionStopwatch reads the system's monotonic timer instead.
use Nexus\Clock\HighResolutionStopwatch; $stopwatch = new HighResolutionStopwatch(); $start = $stopwatch->read(); doWork(); $elapsedSeconds = $stopwatch->read() - $start;
Waiting
Delay is a scheduling act, not a time reading. NativeDelay blocks the current thread with
usleep(). Asynchronous consumers implement Delay once over their own event loop (a Revolt
timer, a ReactPHP timer) and keep the same seam. Zero or negative seconds are a no-op, and a
duration that is not a finite representable number of seconds throws InvalidDurationException.
use Nexus\Clock\NativeDelay; $delay = new NativeDelay(); $delay->sleep(0.5);
Testing with FrozenClock
FrozenClock implements all three interfaces behind one advanceable state. When code sleeps
two seconds, the frozen now() and the frozen read() both advance by two seconds, keeping
the test scenario coherent. Its default epoch is deterministic, so two instances built in one
test agree with each other.
use Nexus\Clock\FrozenClock; $clock = new FrozenClock('2026-08-25 12:00:00+00:00'); $clock->advance(30); $clock->now(); // 2026-08-25 12:00:30, exactly $clock->read(); // 30.0
advance() and sleep() behave identically. A test freezing a deadline advances a clock, a
test pacing a retry loop observes the sleep.
Contributing
Contributions are very much welcome. If you see an improvement or bugfix, open a PR now!
License
Released under the MIT License.