This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```php
|
||||
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.
|
||||
|
||||
```php
|
||||
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.
|
||||
|
||||
```php
|
||||
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
|
||||
|
||||
```php
|
||||
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.
|
||||
|
||||
```php
|
||||
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.
|
||||
|
||||
```php
|
||||
$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
|
||||
|
||||
```php
|
||||
$sw->reset(); // resets both the total timer and the lap marker to now
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP 8.3+
|
||||
- No runtime dependencies
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user