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
3declare(strict_types=1);
4
5namespace Stewart\Testing\Time;
6
7use DateTimeImmutable;
8use DateTimeZone;
9use Stewart\Contracts\Schedule\TimeOfDay;
10use Stewart\Contracts\Time\Clock;
11use Stewart\Contracts\Time\Duration;
12use Stewart\Contracts\Time\Instant;
13use Stewart\Contracts\Time\MonotonicTime;
14use Stewart\Support\Schedule\TimeWindow;
15
16final 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}