Lines
100.00%
11 / 11
Functions and Methods
100.00%
9 / 9
Classes and Traits
100.00%
1 / 1
| Name | Lines | Functions and Methods | CRAP | Classes and Traits | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| VirtualClock | 100.00% | 11 / 11 | 100.00% | 9 / 9 | 9 | 100.00% | 1 / 1 | |||
| __construct | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | |||||
| getNow | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| getMonotonicTime | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| getTimeZone | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| isWithin | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| getWallTime | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| moveTo | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| skip | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| rewind | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | |||||
| 1 | <?php | |
| 2 | ||
| 3 | declare(strict_types=1); | |
| 4 | ||
| 5 | namespace Stewart\Testing\Time; | |
| 6 | ||
| 7 | use DateTimeImmutable; | |
| 8 | use DateTimeZone; | |
| 9 | use Stewart\Contracts\Schedule\TimeOfDay; | |
| 10 | use Stewart\Contracts\Time\Clock; | |
| 11 | use Stewart\Contracts\Time\Duration; | |
| 12 | use Stewart\Contracts\Time\Instant; | |
| 13 | use Stewart\Contracts\Time\MonotonicTime; | |
| 14 | use Stewart\Support\Schedule\TimeWindow; | |
| 15 | ||
| 16 | final class VirtualClock implements Clock | |
| 17 | { | |
| 18 | private const string DEFAULT_START = '2026-01-01 00:00:00 UTC'; | |
| 19 | ||
| 20 | private readonly int $startMicroseconds; | |
| 21 | ||
| 22 | private readonly DateTimeZone $zone; | |
| 23 | ||
| 24 | private int $elapsedMicroseconds = 0; | |
| 25 | ||
| 26 | private int $skewMicroseconds = 0; | |
| 27 | ||
| 28 | public function __construct(?DateTimeImmutable $start = null) | |
| 29 | { | |
| 30 | $start ??= new DateTimeImmutable(self::DEFAULT_START); | |
| 31 | ||
| 32 | $this->startMicroseconds = Instant::fromDateTime($start)->toEpochMicroseconds(); | |
| 33 | $this->zone = $start->getTimezone(); | |
| 34 | } | |
| 35 | ||
| 36 | public function getNow(): Instant | |
| 37 | { | |
| 38 | return Instant::fromEpochMicroseconds($this->startMicroseconds + $this->elapsedMicroseconds + $this->skewMicroseconds); | |
| 39 | } | |
| 40 | ||
| 41 | public function getMonotonicTime(): MonotonicTime | |
| 42 | { | |
| 43 | return MonotonicTime::fromMicroseconds($this->elapsedMicroseconds); | |
| 44 | } | |
| 45 | ||
| 46 | public function getTimeZone(): DateTimeZone | |
| 47 | { | |
| 48 | return $this->zone; | |
| 49 | } | |
| 50 | ||
| 51 | public function isWithin(TimeOfDay|string $start, TimeOfDay|string $end): bool | |
| 52 | { | |
| 53 | return TimeWindow::between($start, $end)->includes($this->getNow()->toDateTime($this->getTimeZone())); | |
| 54 | } | |
| 55 | ||
| 56 | public function getWallTime(): DateTimeImmutable | |
| 57 | { | |
| 58 | return $this->getNow()->toDateTime($this->zone); | |
| 59 | } | |
| 60 | ||
| 61 | public function moveTo(MonotonicTime $moment): void | |
| 62 | { | |
| 63 | $this->elapsedMicroseconds = max($this->elapsedMicroseconds, $moment->toMicroseconds()); | |
| 64 | } | |
| 65 | ||
| 66 | public function skip(Duration $by): void | |
| 67 | { | |
| 68 | $this->skewMicroseconds += $by->toMicroseconds(); | |
| 69 | } | |
| 70 | ||
| 71 | public function rewind(Duration $by): void | |
| 72 | { | |
| 73 | $this->skewMicroseconds -= $by->toMicroseconds(); | |
| 74 | } | |
| 75 | } |