Search by

tezmanian / utils-clock

tezmanian

Testable clock abstractions and a high-resolution stopwatch for PHP — ClockInterface, SystemClock, FrozenClock for deterministic tests, and Stopwatch with lap support. PSR-20 compatible, PHP 8.3+.

Package info

github.com/tezmanian/tez-utils-clock

pkg:composer/tezmanian/utils-clock

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-10-19 06:49 UTC

This package is auto-updated.

Last update: 2026-10-19 19:56:02 UTC


README

CI Packagist Version PHP License: MIT

Testable clock abstractions and a high-resolution stopwatch for PHP — ClockInterface, SystemClock, FrozenClock for deterministic tests, and Stopwatch with lap and stop/resume support. PSR-20 compatible, PHP 8.3+.

Installation

composer require tezmanian/utils-clock

Package: tezmanian/utils-clock · Namespace: Tezmanian\Utils\Clock

Components

Class Purpose
ClockInterface Extends Psr\Clock\ClockInterface (PSR-20)
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;
}

Extends Psr\Clock\ClockInterface (PSR-20), so every clock here works wherever a PSR-20 clock is expected. 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 Tezmanian\Utils\Clock\SystemClock;

$clock = new SystemClock();
$now   = $clock->now(); // \DateTimeImmutable

// With an explicit timezone
$tokyo = new SystemClock(new \DateTimeZone('Asia/Tokyo'));

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 Tezmanian\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

// Move relative to the current instant (DateInterval or relative string)
$clock->advance('+5 minutes');
$clock->advance(new \DateInterval('P1D'));
$clock->advance('-1 hour'); // rewind

advance() throws InvalidArgumentException for strings that are not a valid relative time; the clock stays unchanged in that case.

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->advance('+9 days');

        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 Tezmanian\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)

Stop and resume

$sw->stop();      // freezes elapsed() and lap()
$sw->isRunning(); // false
$sw->resume();    // continues; the stopped time is not counted

stop() and resume() are idempotent.

Reset

$sw->reset(); // back to zero (timer and lap marker) and running

Requirements

Development

make build && make install
make ci   # audit, PHPStan, code style, tests

License

MIT — see LICENSE.

Author: René Halberstadt