Issues (13)

src/Timestamp.php (1 issue)

Labels
Severity
1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of the daikon-cqrs/value-object project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Daikon\ValueObject;
10
11
use Daikon\Interop\Assertion;
12
use Daikon\Interop\InvalidArgumentException;
13
use Daikon\Interop\MakeEmptyInterface;
14
use DateTimeImmutable;
15
use DateTimeZone;
16
17
final class Timestamp implements MakeEmptyInterface, ValueObjectInterface
18
{
19
    public const NATIVE_FORMAT = 'Y-m-d\TH:i:s.uP';
20
21
    private ?DateTimeImmutable $value;
22
23
    public static function now(): self
24
    {
25
        return new self(new DateTimeImmutable);
26
    }
27
28
    public static function epoch(): self
29
    {
30
        return new self((new DateTimeImmutable)->setTimestamp(0));
31
    }
32
33
    /** @param int|string $time */
34
    public static function fromTime($time): self
35
    {
36
        Assertion::integerish($time, 'Unix time must be an integer.');
37
        Assertion::greaterOrEqualThan((int)$time, 0, 'Unix time must be greater or equal than 0.');
38
        return new self(new DateTimeImmutable("@$time"));
39
    }
40
41 4
    public static function makeEmpty(): self
42
    {
43 4
        return new self;
44
    }
45
46 1
    public function toTime(): int
47
    {
48 1
        Assertion::false($this->isEmpty(), 'Cannot convert empty timestamp.');
49
        /** @psalm-suppress PossiblyNullReference */
50 1
        return $this->value->getTimestamp();
0 ignored issues
show
The method getTimestamp() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

50
        return $this->value->/** @scrutinizer ignore-call */ getTimestamp();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
51
    }
52
53 8
    public static function fromString(string $date, string $format = self::NATIVE_FORMAT): self
54
    {
55 8
        if ($date === 'now') {
56
            return self::now();
57
        }
58
59 8
        if ($date === 'epoch') {
60
            return self::epoch();
61
        }
62
63 8
        if (is_numeric($date)) {
64 1
            $format = strpos($date, '.') ? 'U.u' : 'U';
65
        }
66
67 8
        if (!$dateTime = DateTimeImmutable::createFromFormat($format, $date)) {
68 1
            $time = strtotime($date);
69 1
            if ($time === false || !$dateTime = new DateTimeImmutable('@'.$time)) {
70
                throw new InvalidArgumentException('Invalid timestamp.');
71
            }
72
        }
73
74 8
        return new self($dateTime);
75
    }
76
77
    /** @param null|int|string $value */
78 8
    public static function fromNative($value): self
79
    {
80 8
        Assertion::nullOrSatisfy(
81 8
            $value,
82
            /** @param mixed $value */
83 8
            fn($value): bool => is_string($value) || is_numeric($value),
84 8
            'Invalid timestamp.'
85
        );
86
87 8
        return empty($value) || $value === 'null' ? new self : self::fromString((string)$value);
88
    }
89
90 6
    public function toNative(): ?string
91
    {
92 6
        return is_null($this->value) ? null : $this->value->format(self::NATIVE_FORMAT);
93
    }
94
95
    /** @param self $comparator */
96 1
    public function equals($comparator): bool
97
    {
98 1
        Assertion::isInstanceOf($comparator, self::class);
99 1
        return $this->toNative() === $comparator->toNative();
100
    }
101
102 5
    public function isEmpty(): bool
103
    {
104 5
        return $this->value === null;
105
    }
106
107 1
    public function isBefore(self $comparand): bool
108
    {
109 1
        if ($this->isEmpty()) {
110 1
            return true;
111 1
        } elseif ($comparand->isEmpty()) {
112 1
            return false;
113
        } else {
114 1
            return $this->value < DateTimeImmutable::createFromFormat(self::NATIVE_FORMAT, (string)$comparand);
115
        }
116
    }
117
118 1
    public function isAfter(self $comparand): bool
119
    {
120 1
        if ($this->isEmpty()) {
121 1
            return false;
122 1
        } elseif ($comparand->isEmpty()) {
123 1
            return true;
124
        } else {
125 1
            return $this->value > DateTimeImmutable::createFromFormat(self::NATIVE_FORMAT, (string)$comparand);
126
        }
127
    }
128
129
    /** @param string $interval */
130 1
    public function modify($interval): self
131
    {
132 1
        Assertion::false($this->isEmpty(), 'Cannot modify empty Timestamp.');
133 1
        Assertion::string($interval);
134 1
        Assertion::notEmpty($interval);
135
        /** @psalm-suppress PossiblyNullReference */
136 1
        $modified = $this->value->modify($interval);
137 1
        Assertion::isInstanceOf($modified, DateTimeImmutable::class, 'Invalid modification interval.');
138
139 1
        return new self($modified);
140
    }
141
142 4
    public function __toString(): string
143
    {
144 4
        return $this->toNative() ?? 'null';
145
    }
146
147 8
    private function __construct(DateTimeImmutable $value = null)
148
    {
149 8
        $this->value = $value ? $value->setTimezone(new DateTimeZone('UTC')) : $value;
150 8
    }
151
}
152