tez-utils-clock
Testable clock abstractions and a high-resolution stopwatch for PHP — ClockInterface, SystemClock, FrozenClock for deterministic tests, and Stopwatch with lap support. PHP 8.3+, no dependencies.
Installation
composer require tez/utils-clock
Components
| Class | Purpose |
|---|---|
ClockInterface |
PSR-20 compatible clock contract |
SystemClock |
Returns the actual current time |
FrozenClock |
Returns a fixed time — for deterministic tests |
Stopwatch |
High-resolution elapsed time and lap measurement |
ClockInterface
interface ClockInterface
{
public function now(): \DateTimeImmutable;
}
PSR-20 compatible. Inject ClockInterface into services instead of calling new \DateTimeImmutable() directly — this makes time controllable in tests without mocking.
SystemClock
Returns the actual current time, honoring the system timezone.
use Tez\Utils\Clock\SystemClock;
$clock = new SystemClock();
$now = $clock->now(); // \DateTimeImmutable
FrozenClock
Returns a fixed DateTimeImmutable — always the same instant until set() is called. Use it in tests to control time without relying on the system clock.
use Tez\Utils\Clock\FrozenClock;
$clock = new FrozenClock(new \DateTimeImmutable('2026-01-01 12:00:00'));
$clock->now(); // 2026-01-01 12:00:00
$clock->now(); // 2026-01-01 12:00:00 — always the same
// Advance to a new instant
$clock->set(new \DateTimeImmutable('2026-06-15 08:30:00'));
$clock->now(); // 2026-06-15 08:30:00
Testing example
final class OrderServiceTest extends TestCase
{
public function testOrderExpiresAfterDeadline(): void
{
$clock = new FrozenClock(new \DateTimeImmutable('2026-01-01 12:00:00'));
$service = new OrderService($clock);
self::assertFalse($service->isExpired($order));
// Advance the clock past the deadline
$clock->set(new \DateTimeImmutable('2026-01-10 00:00:00'));
self::assertTrue($service->isExpired($order));
}
}
Stopwatch
High-resolution elapsed time measurement using hrtime() (nanosecond precision). Not affected by NTP adjustments or system clock changes.
use Tez\Utils\Clock\Stopwatch;
$sw = Stopwatch::start();
// ... do work ...
$sw->elapsed(); // total seconds since start (float)
$sw->elapsedMs(); // total milliseconds since start (float)
Lap timing
lap() returns the elapsed seconds since the last lap() call (or since start if no lap yet), then moves the lap marker forward.
$sw = Stopwatch::start();
// ... first stage ...
$stage1 = $sw->lap(); // seconds for stage 1
// ... second stage ...
$stage2 = $sw->lap(); // seconds for stage 2 only
$total = $sw->elapsed(); // total since start (unaffected by laps)
Reset
$sw->reset(); // resets both the total timer and the lap marker to now
Requirements
- PHP 8.3+
- No runtime dependencies
License
MIT