Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
OffsetClock
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
9 / 9
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 now
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 advance
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 rewind
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 advanceSeconds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rewindSeconds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 reset
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 intervalToMonths
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 intervalToSeconds
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace Phexium\Plugin\Clock\Adapter;
11
12use DateInterval;
13use DateTimeImmutable;
14use Override;
15use Phexium\Plugin\Clock\Port\ClockInterface;
16
17final class OffsetClock implements ClockInterface
18{
19    private int $offsetMonths = 0;
20
21    private int $offsetSeconds = 0;
22
23    public function __construct(
24        private readonly ClockInterface $inner,
25    ) {}
26
27    #[Override]
28    public function now(): DateTimeImmutable
29    {
30        return $this->inner->now()
31            ->modify(sprintf('%+d months', $this->offsetMonths))
32            ->modify(sprintf('%+d seconds', $this->offsetSeconds))
33        ;
34    }
35
36    public function advance(DateInterval $interval): void
37    {
38        $this->offsetMonths += $this->intervalToMonths($interval);
39        $this->advanceSeconds($this->intervalToSeconds($interval));
40    }
41
42    public function rewind(DateInterval $interval): void
43    {
44        $inverted = clone $interval;
45        $inverted->invert = $interval->invert ^ 1;
46
47        $this->advance($inverted);
48    }
49
50    public function advanceSeconds(int $seconds): void
51    {
52        $this->offsetSeconds += $seconds;
53    }
54
55    public function rewindSeconds(int $seconds): void
56    {
57        $this->advanceSeconds(-$seconds);
58    }
59
60    public function reset(): void
61    {
62        $this->offsetMonths = 0;
63        $this->offsetSeconds = 0;
64    }
65
66    private function intervalToMonths(DateInterval $interval): int
67    {
68        $months = ($interval->y * 12) + $interval->m;
69
70        return $interval->invert === 1 ? -$months : $months;
71    }
72
73    private function intervalToSeconds(DateInterval $interval): int
74    {
75        $seconds = ($interval->d * 86400)
76            + ($interval->h * 3600)
77            + ($interval->i * 60)
78            + $interval->s;
79
80        return $interval->invert === 1 ? -$seconds : $seconds;
81    }
82}