• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / date-time / 20679683854

03 Jan 2026 04:05PM UTC coverage: 99.079% (-0.05%) from 99.132%
20679683854

push

github

BenMorel
Use PHP 8.2.0 with lowest deps

1829 of 1846 relevant lines covered (99.08%)

183.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.18
/src/Instant.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\DateTime;
6

7
use JsonSerializable;
8
use Override;
9
use Stringable;
10

11
use function intdiv;
12
use function rtrim;
13
use function str_pad;
14

15
use const PHP_INT_MAX;
16
use const PHP_INT_MIN;
17
use const STR_PAD_LEFT;
18

19
/**
20
 * Represents a point in time, with a nanosecond precision.
21
 *
22
 * Instant represents the computer view of the timeline. It unambiguously represents a point in time,
23
 * without any calendar concept of date, time or time zone. It is not very meaningful to humans,
24
 * but can be converted to a `ZonedDateTime` by providing a time zone.
25
 */
26
final class Instant implements JsonSerializable, Stringable
27
{
28
    /**
29
     * Private constructor. Use of() to obtain an Instant.
30
     *
31
     * @param int $epochSecond The number of seconds since the epoch of 1970-01-01T00:00:00Z.
32
     * @param int $nano        The nanosecond adjustment to the epoch second, validated in the range 0 to 999,999,999.
33
     */
34
    private function __construct(
35
        private readonly int $epochSecond,
36
        private readonly int $nano,
37
    ) {
38
    }
1,622✔
39

40
    /**
41
     * Returns an Instant representing a number of seconds and an adjustment in nanoseconds.
42
     *
43
     * This method allows an arbitrary number of nanoseconds to be passed in.
44
     * The factory will alter the values of the second and nanosecond in order
45
     * to ensure that the stored nanosecond is in the range 0 to 999,999,999.
46
     * For example, the following will result in exactly the same instant:
47
     *
48
     * * Instant::of(3, 1);
49
     * * Instant::of(4, -999_999_999);
50
     * * Instant::of(2, 1_000_000_001);
51
     *
52
     * @param int $epochSecond    The number of seconds since the UNIX epoch of 1970-01-01T00:00:00Z.
53
     * @param int $nanoAdjustment The adjustment to the epoch second in nanoseconds.
54
     */
55
    public static function of(int $epochSecond, int $nanoAdjustment = 0): Instant
56
    {
57
        $nanos = $nanoAdjustment % LocalTime::NANOS_PER_SECOND;
1,619✔
58
        $epochSecond += intdiv($nanoAdjustment - $nanos, LocalTime::NANOS_PER_SECOND);
1,619✔
59

60
        if ($nanos < 0) {
1,619✔
61
            $nanos += LocalTime::NANOS_PER_SECOND;
386✔
62
            $epochSecond--;
386✔
63
        }
64

65
        return new Instant($epochSecond, $nanos);
1,619✔
66
    }
67

68
    public static function epoch(): Instant
69
    {
70
        /** @var Instant|null $epoch */
71
        static $epoch = null;
1✔
72

73
        return $epoch ??= new Instant(0, 0);
1✔
74
    }
75

76
    public static function now(?Clock $clock = null): Instant
77
    {
78
        if ($clock === null) {
240✔
79
            $clock = DefaultClock::get();
4✔
80
        }
81

82
        return $clock->getTime();
240✔
83
    }
84

85
    /**
86
     * Returns the minimum supported instant.
87
     *
88
     * This could be used by an application as a "far past" instant.
89
     */
90
    public static function min(): Instant
91
    {
92
        /** @var Instant|null $min */
93
        static $min = null;
1✔
94

95
        return $min ??= new Instant(PHP_INT_MIN, 0);
1✔
96
    }
97

98
    /**
99
     * Returns the maximum supported instant.
100
     *
101
     * This could be used by an application as a "far future" instant.
102
     */
103
    public static function max(): Instant
104
    {
105
        /** @var Instant|null $max */
106
        static $max = null;
1✔
107

108
        return $max ??= new Instant(PHP_INT_MAX, 999_999_999);
1✔
109
    }
110

111
    public function plus(Duration $duration): Instant
112
    {
113
        if ($duration->isZero()) {
126✔
114
            return $this;
14✔
115
        }
116

117
        $seconds = $this->epochSecond + $duration->getSeconds();
113✔
118
        $nanos = $this->nano + $duration->getNanos();
113✔
119

120
        return Instant::of($seconds, $nanos);
113✔
121
    }
122

123
    public function minus(Duration $duration): Instant
124
    {
125
        if ($duration->isZero()) {
5✔
126
            return $this;
1✔
127
        }
128

129
        return $this->plus($duration->negated());
4✔
130
    }
131

132
    public function plusSeconds(int $seconds): Instant
133
    {
134
        if ($seconds === 0) {
148✔
135
            return $this;
20✔
136
        }
137

138
        return new Instant($this->epochSecond + $seconds, $this->nano);
128✔
139
    }
140

141
    public function minusSeconds(int $seconds): Instant
142
    {
143
        return $this->plusSeconds(-$seconds);
7✔
144
    }
145

146
    public function plusMinutes(int $minutes): Instant
147
    {
148
        return $this->plusSeconds($minutes * LocalTime::SECONDS_PER_MINUTE);
44✔
149
    }
150

151
    public function minusMinutes(int $minutes): Instant
152
    {
153
        return $this->plusMinutes(-$minutes);
7✔
154
    }
155

156
    public function plusHours(int $hours): Instant
157
    {
158
        return $this->plusSeconds($hours * LocalTime::SECONDS_PER_HOUR);
48✔
159
    }
160

161
    public function minusHours(int $hours): Instant
162
    {
163
        return $this->plusHours(-$hours);
7✔
164
    }
165

166
    public function plusDays(int $days): Instant
167
    {
168
        return $this->plusSeconds($days * LocalTime::SECONDS_PER_DAY);
14✔
169
    }
170

171
    /**
172
     * Returns a copy of this Instant with the epoch second altered.
173
     */
174
    public function withEpochSecond(int $epochSecond): Instant
175
    {
176
        if ($epochSecond === $this->epochSecond) {
1✔
177
            return $this;
×
178
        }
179

180
        return new Instant($epochSecond, $this->nano);
1✔
181
    }
182

183
    /**
184
     * Returns a copy of this Instant with the nano-of-second altered.
185
     *
186
     * @throws DateTimeException If the nano-of-second if not valid.
187
     */
188
    public function withNano(int $nano): Instant
189
    {
190
        if ($nano === $this->nano) {
3✔
191
            return $this;
×
192
        }
193

194
        Field\NanoOfSecond::check($nano);
3✔
195

196
        return new Instant($this->epochSecond, $nano);
1✔
197
    }
198

199
    public function minusDays(int $days): Instant
200
    {
201
        return $this->plusDays(-$days);
7✔
202
    }
203

204
    public function getEpochSecond(): int
205
    {
206
        return $this->epochSecond;
1,333✔
207
    }
208

209
    public function getNano(): int
210
    {
211
        return $this->nano;
834✔
212
    }
213

214
    /**
215
     * Compares this instant with another.
216
     *
217
     * @return int [-1,0,1] If this instant is before, on, or after the given instant.
218
     *
219
     * @psalm-return -1|0|1
220
     */
221
    public function compareTo(Instant $that): int
222
    {
223
        $seconds = $this->getEpochSecond() - $that->getEpochSecond();
730✔
224

225
        if ($seconds !== 0) {
730✔
226
            return $seconds > 0 ? 1 : -1;
530✔
227
        }
228

229
        $nanos = $this->getNano() - $that->getNano();
219✔
230

231
        if ($nanos !== 0) {
219✔
232
            return $nanos > 0 ? 1 : -1;
121✔
233
        }
234

235
        return 0;
98✔
236
    }
237

238
    /**
239
     * Returns whether this instant equals another.
240
     */
241
    public function isEqualTo(Instant $that): bool
242
    {
243
        return $this->compareTo($that) === 0;
90✔
244
    }
245

246
    /**
247
     * Returns whether this instant is after another.
248
     */
249
    public function isAfter(Instant $that): bool
250
    {
251
        return $this->compareTo($that) === 1;
182✔
252
    }
253

254
    /**
255
     * Returns whether this instant is after or equal to another.
256
     */
257
    public function isAfterOrEqualTo(Instant $that): bool
258
    {
259
        return $this->compareTo($that) >= 0;
95✔
260
    }
261

262
    /**
263
     * Returns whether this instant is before another.
264
     */
265
    public function isBefore(Instant $that): bool
266
    {
267
        return $this->compareTo($that) === -1;
212✔
268
    }
269

270
    /**
271
     * Returns whether this instant is before or equal to another.
272
     */
273
    public function isBeforeOrEqualTo(Instant $that): bool
274
    {
275
        return $this->compareTo($that) <= 0;
91✔
276
    }
277

278
    public function isBetweenInclusive(Instant $from, Instant $to): bool
279
    {
280
        return $this->isAfterOrEqualTo($from) && $this->isBeforeOrEqualTo($to);
11✔
281
    }
282

283
    public function isBetweenExclusive(Instant $from, Instant $to): bool
284
    {
285
        return $this->isAfter($from) && $this->isBefore($to);
11✔
286
    }
287

288
    /**
289
     * Returns whether this instant is in the future, according to the given clock.
290
     *
291
     * If no clock is provided, the system clock is used.
292
     */
293
    public function isFuture(?Clock $clock = null): bool
294
    {
295
        return $this->isAfter(Instant::now($clock));
85✔
296
    }
297

298
    /**
299
     * Returns whether this instant is in the past, according to the given clock.
300
     *
301
     * If no clock is provided, the system clock is used.
302
     */
303
    public function isPast(?Clock $clock = null): bool
304
    {
305
        return $this->isBefore(Instant::now($clock));
85✔
306
    }
307

308
    /**
309
     * Returns a ZonedDateTime formed from this instant and the specified time-zone.
310
     */
311
    public function atTimeZone(TimeZone $timeZone): ZonedDateTime
312
    {
313
        return ZonedDateTime::ofInstant($this, $timeZone);
1✔
314
    }
315

316
    /**
317
     * Returns an Interval from this Instant (inclusive) to the given one (exclusive).
318
     *
319
     * @throws DateTimeException If the given Instant is before this Instant.
320
     */
321
    public function getIntervalTo(Instant $that): Interval
322
    {
323
        return Interval::of($this, $that);
12✔
324
    }
325

326
    /**
327
     * Returns a decimal representation of the timestamp represented by this instant.
328
     *
329
     * The output does not have trailing decimal zeros.
330
     *
331
     * Examples: `123456789`, `123456789.5`, `123456789.000000001`.
332
     */
333
    public function toDecimal(): string
334
    {
335
        $result = (string) $this->epochSecond;
15✔
336

337
        if ($this->nano !== 0) {
15✔
338
            $nano = (string) $this->nano;
13✔
339
            $nano = str_pad($nano, 9, '0', STR_PAD_LEFT);
13✔
340
            $nano = rtrim($nano, '0');
13✔
341

342
            $result .= '.' . $nano;
13✔
343
        }
344

345
        return $result;
15✔
346
    }
347

348
    /**
349
     * Serializes as a string using {@see Instant::toISOString()}.
350
     *
351
     * @psalm-return non-empty-string
352
     */
353
    #[Override]
354
    public function jsonSerialize(): string
355
    {
356
        return $this->toISOString();
8✔
357
    }
358

359
    /**
360
     * Returns the ISO 8601 representation of this instant.
361
     *
362
     * @psalm-return non-empty-string
363
     */
364
    public function toISOString(): string
365
    {
366
        return (string) ZonedDateTime::ofInstant($this, TimeZone::utc());
43✔
367
    }
368

369
    /**
370
     * {@see Instant::toISOString()}.
371
     *
372
     * @psalm-return non-empty-string
373
     */
374
    #[Override]
375
    public function __toString(): string
376
    {
377
        return $this->toISOString();
27✔
378
    }
379
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc