nexusphp/clock

Nexus Clock decouples applications from calendar time, monotonic time, and waiting for better testing.

Maintainers

Package info

github.com/NexusPHP/clock

pkg:composer/nexusphp/clock

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.x-dev 2026-08-25 19:36 UTC

This package is auto-updated.

Last update: 2026-08-25 19:36:34 UTC


README

PHP version Latest Stable Version Unit Tests Code Style Static Analysis Mutation Tests Coverage Status license MIT Total Downloads

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.