Lines
100.00%
28 / 28
Methods
100.00%
16 / 16
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| fromEpochMicroseconds | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| fromDateTime | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| fromIso | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 3 | ||
| tryFromIso | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 4 | ||
| toEpochMicroseconds | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| toDateTime | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 1 | ||
| toIso8601 | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| plus | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| minus | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| elapsedSince | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| isBefore | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| isAfter | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| equals | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| __toString | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| floorDiv | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 15 | final readonly class Instant implements Stringable | |
| 16 | { | |
| 17 | private const int MICROS_PER_SECOND = 1_000_000; | |
| 18 | private const string ISO_SHAPE = '/\A\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}(:?\d{2})?)?\z/i'; | |
| 19 | ||
| 20 | private function __construct(private int $epochMicroseconds) {} | |
| 21 | ||
| 22 | public static function fromEpochMicroseconds(int $epochMicroseconds): self | |
| 23 | { | |
| 24 | return new self($epochMicroseconds); | |
| 25 | } | |
| 26 | ||
| 27 | public static function fromDateTime(DateTimeInterface $moment): self | |
| 28 | { | |
| 29 | return new self($moment->getTimestamp() * self::MICROS_PER_SECOND + (int) $moment->format('u')); | |
| 30 | } | |
| 31 | ||
| 32 | /** @throws TimeException */ | |
| 33 | public static function fromIso(string $text): self | |
| 34 | { | |
| 35 | if (preg_match(self::ISO_SHAPE, $text) !== 1) { | |
| 36 | throw TimeException::createForReason(TimeError::InstantUnparsable, text: $text); | |
| 37 | } | |
| 38 | ||
| 39 | try { | |
| 40 | return self::fromDateTime(new DateTimeImmutable($text, new DateTimeZone('UTC'))); | |
| 41 | } catch (Exception) { | |
| 42 | throw TimeException::createForReason(TimeError::InstantUnparsable, text: $text); | |
| 43 | } | |
| 44 | } | |
| 45 | ||
| 46 | public static function tryFromIso(?string $text): ?self | |
| 47 | { | |
| 48 | if ($text === null || $text === '') { | |
| 49 | return null; | |
| 50 | } | |
| 51 | ||
| 52 | try { | |
| 53 | return self::fromIso($text); | |
| 54 | } catch (TimeException) { | |
| 55 | return null; | |
| 56 | } | |
| 57 | } | |
| 58 | ||
| 59 | public function toEpochMicroseconds(): int | |
| 60 | { | |
| 61 | return $this->epochMicroseconds; | |
| 62 | } | |
| 63 | ||
| 64 | public function toDateTime(?DateTimeZone $zone = null): DateTimeImmutable | |
| 65 | { | |
| 66 | $seconds = self::floorDiv($this->epochMicroseconds, self::MICROS_PER_SECOND); | |
| 67 | $fraction = $this->epochMicroseconds - $seconds * self::MICROS_PER_SECOND; | |
| 68 | ||
| 69 | $utc = DateTimeImmutable::createFromFormat('U.u', \sprintf('%d.%06d', $seconds, $fraction), new DateTimeZone('UTC')); | |
| 70 | \assert($utc !== false); | |
| 71 | ||
| 72 | return $utc->setTimezone($zone ?? new DateTimeZone(date_default_timezone_get())); | |
| 73 | } | |
| 74 | ||
| 75 | public function toIso8601(): string | |
| 76 | { | |
| 77 | return $this->toDateTime(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.u\Z'); | |
| 78 | } | |
| 79 | ||
| 80 | public function plus(Duration $duration): self | |
| 81 | { | |
| 82 | return new self($this->epochMicroseconds + $duration->toMicroseconds()); | |
| 83 | } | |
| 84 | ||
| 85 | public function minus(Duration $duration): self | |
| 86 | { | |
| 87 | return new self($this->epochMicroseconds - $duration->toMicroseconds()); | |
| 88 | } | |
| 89 | ||
| 90 | public function elapsedSince(self $earlier): Duration | |
| 91 | { | |
| 92 | return Duration::microseconds(max(0, $this->epochMicroseconds - $earlier->epochMicroseconds)); | |
| 93 | } | |
| 94 | ||
| 95 | public function isBefore(self $other): bool | |
| 96 | { | |
| 97 | return $this->epochMicroseconds < $other->epochMicroseconds; | |
| 98 | } | |
| 99 | ||
| 100 | public function isAfter(self $other): bool | |
| 101 | { | |
| 102 | return $this->epochMicroseconds > $other->epochMicroseconds; | |
| 103 | } | |
| 104 | ||
| 105 | public function equals(self $other): bool | |
| 106 | { | |
| 107 | return $this->epochMicroseconds === $other->epochMicroseconds; | |
| 108 | } | |
| 109 | ||
| 110 | public function __toString(): string | |
| 111 | { | |
| 112 | return $this->toIso8601(); | |
| 113 | } | |
| 114 | ||
| 115 | private static function floorDiv(int $dividend, int $divisor): int | |
| 116 | { | |
| 117 | return intdiv($dividend - (($dividend % $divisor) + $divisor) % $divisor, $divisor); | |
| 118 | } | |
| 119 | } |