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

brick / date-time / 20679568953

03 Jan 2026 04:00PM UTC coverage: 99.132% (+0.05%) from 99.08%
20679568953

push

github

BenMorel
Clean up TZ fix, require PHP 8.2.7

1 of 1 new or added line in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

1827 of 1843 relevant lines covered (99.13%)

184.17 hits per line

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

98.85
/src/LocalDateTime.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\DateTime;
6

7
use Brick\DateTime\Parser\DateTimeParseException;
8
use Brick\DateTime\Parser\DateTimeParser;
9
use Brick\DateTime\Parser\DateTimeParseResult;
10
use Brick\DateTime\Parser\IsoParsers;
11
use Brick\DateTime\Utility\Math;
12
use DateTime;
13
use DateTimeImmutable;
14
use DateTimeInterface;
15
use JsonSerializable;
16
use Override;
17
use Stringable;
18

19
use function intdiv;
20

21
/**
22
 * A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.
23
 *
24
 * This class is immutable.
25
 */
26
final class LocalDateTime implements JsonSerializable, Stringable
27
{
28
    public function __construct(
29
        private readonly LocalDate $date,
30
        private readonly LocalTime $time,
31
    ) {
32
    }
1,014✔
33

34
    /**
35
     * @param int       $year   The year, from MIN_YEAR to MAX_YEAR.
36
     * @param int|Month $month  The month-of-year, from 1 (January) to 12 (December).
37
     * @param int       $day    The day-of-month, from 1 to 31.
38
     * @param int       $hour   The hour-of-day, from 0 to 23.
39
     * @param int       $minute The minute-of-hour, from 0 to 59.
40
     * @param int       $second The second-of-minute, from 0 to 59.
41
     * @param int       $nano   The nano-of-second, from 0 to 999,999,999.
42
     *
43
     * @throws DateTimeException If the date or time is not valid.
44
     */
45
    public static function of(int $year, int|Month $month, int $day, int $hour = 0, int $minute = 0, int $second = 0, int $nano = 0): LocalDateTime
46
    {
47
        $date = LocalDate::of($year, $month, $day);
57✔
48
        $time = LocalTime::of($hour, $minute, $second, $nano);
57✔
49

50
        return new LocalDateTime($date, $time);
57✔
51
    }
52

53
    /**
54
     * Returns the current local date-time in the given time-zone, according to the given clock.
55
     *
56
     * If no clock is provided, the system clock is used.
57
     */
58
    public static function now(TimeZone $timeZone, ?Clock $clock = null): LocalDateTime
59
    {
60
        return ZonedDateTime::now($timeZone, $clock)->getDateTime();
12✔
61
    }
62

63
    /**
64
     * @throws DateTimeException      If the date-time is not valid.
65
     * @throws DateTimeParseException If required fields are missing from the result.
66
     */
67
    public static function from(DateTimeParseResult $result): LocalDateTime
68
    {
69
        return new LocalDateTime(
877✔
70
            LocalDate::from($result),
877✔
71
            LocalTime::from($result),
877✔
72
        );
877✔
73
    }
74

75
    /**
76
     * Obtains an instance of `LocalDateTime` from a text string.
77
     *
78
     * @param string              $text   The text to parse, such as `2007-12-03T10:15:30`.
79
     * @param DateTimeParser|null $parser The parser to use, defaults to the ISO 8601 parser.
80
     *
81
     * @throws DateTimeException      If the date-time is not valid.
82
     * @throws DateTimeParseException If the text string does not follow the expected format.
83
     */
84
    public static function parse(string $text, ?DateTimeParser $parser = null): LocalDateTime
85
    {
86
        if ($parser === null) {
898✔
87
            $parser = IsoParsers::localDateTime();
898✔
88
        }
89

90
        return LocalDateTime::from($parser->parse($text));
898✔
91
    }
92

93
    /**
94
     * Creates a LocalDateTime from a native DateTime or DateTimeImmutable object.
95
     */
96
    public static function fromNativeDateTime(DateTimeInterface $dateTime): LocalDateTime
97
    {
98
        return new LocalDateTime(
4✔
99
            LocalDate::fromNativeDateTime($dateTime),
4✔
100
            LocalTime::fromNativeDateTime($dateTime),
4✔
101
        );
4✔
102
    }
103

104
    /**
105
     * Returns the smallest possible value for LocalDateTime.
106
     */
107
    public static function min(): LocalDateTime
108
    {
109
        /** @var LocalDateTime|null $min */
110
        static $min = null;
1✔
111

112
        return $min ??= new LocalDateTime(LocalDate::min(), LocalTime::min());
1✔
113
    }
114

115
    /**
116
     * Returns the highest possible value for LocalDateTime.
117
     */
118
    public static function max(): LocalDateTime
119
    {
120
        /** @var LocalDateTime|null $max */
121
        static $max = null;
1✔
122

123
        return $max ??= new LocalDateTime(LocalDate::max(), LocalTime::max());
1✔
124
    }
125

126
    /**
127
     * Returns the smallest LocalDateTime among the given values.
128
     *
129
     * @param LocalDateTime ...$times The LocalDateTime objects to compare.
130
     *
131
     * @return LocalDateTime The earliest LocalDateTime object.
132
     *
133
     * @throws DateTimeException If the array is empty.
134
     */
135
    public static function minOf(LocalDateTime ...$times): LocalDateTime
136
    {
137
        if ($times === []) {
2✔
138
            throw new DateTimeException(__METHOD__ . ' does not accept less than 1 parameter.');
1✔
139
        }
140

141
        $min = null;
1✔
142

143
        foreach ($times as $time) {
1✔
144
            if ($min === null || $time->isBefore($min)) {
1✔
145
                $min = $time;
1✔
146
            }
147
        }
148

149
        return $min;
1✔
150
    }
151

152
    /**
153
     * Returns the highest LocalDateTime among the given values.
154
     *
155
     * @param LocalDateTime ...$times The LocalDateTime objects to compare.
156
     *
157
     * @return LocalDateTime The latest LocalDateTime object.
158
     *
159
     * @throws DateTimeException If the array is empty.
160
     */
161
    public static function maxOf(LocalDateTime ...$times): LocalDateTime
162
    {
163
        if ($times === []) {
2✔
164
            throw new DateTimeException(__METHOD__ . ' does not accept less than 1 parameter.');
1✔
165
        }
166

167
        $max = null;
1✔
168

169
        foreach ($times as $time) {
1✔
170
            if ($max === null || $time->isAfter($max)) {
1✔
171
                $max = $time;
1✔
172
            }
173
        }
174

175
        return $max;
1✔
176
    }
177

178
    public function getDate(): LocalDate
179
    {
180
        return $this->date;
60✔
181
    }
182

183
    public function getTime(): LocalTime
184
    {
185
        return $this->time;
15✔
186
    }
187

188
    public function getYear(): int
189
    {
190
        return $this->date->getYear();
83✔
191
    }
192

193
    /**
194
     * Returns the month-of-year as a Month enum.
195
     */
196
    public function getMonth(): Month
197
    {
198
        return $this->date->getMonth();
24✔
199
    }
200

201
    /**
202
     * Returns the month-of-year value from 1 to 12.
203
     *
204
     * @return int<1, 12>
205
     */
206
    public function getMonthValue(): int
207
    {
208
        return $this->date->getMonthValue();
83✔
209
    }
210

211
    /**
212
     * @return int<1, 31>
213
     */
214
    public function getDayOfMonth(): int
215
    {
216
        return $this->date->getDayOfMonth();
83✔
217
    }
218

219
    public function getDayOfWeek(): DayOfWeek
220
    {
221
        return $this->date->getDayOfWeek();
15✔
222
    }
223

224
    /**
225
     * @return int<1, 366>
226
     */
227
    public function getDayOfYear(): int
228
    {
229
        return $this->date->getDayOfYear();
6✔
230
    }
231

232
    public function getHour(): int
233
    {
234
        return $this->time->getHour();
83✔
235
    }
236

237
    public function getMinute(): int
238
    {
239
        return $this->time->getMinute();
83✔
240
    }
241

242
    public function getSecond(): int
243
    {
244
        return $this->time->getSecond();
134✔
245
    }
246

247
    public function getNano(): int
248
    {
249
        return $this->time->getNano();
780✔
250
    }
251

252
    /**
253
     * Returns a copy of this LocalDateTime with the date altered.
254
     */
255
    public function withDate(LocalDate $date): LocalDateTime
256
    {
257
        if ($date->isEqualTo($this->date)) {
3✔
258
            return $this;
1✔
259
        }
260

261
        return new LocalDateTime($date, $this->time);
2✔
262
    }
263

264
    /**
265
     * Returns a copy of this LocalDateTime with the time altered.
266
     */
267
    public function withTime(LocalTime $time): LocalDateTime
268
    {
269
        if ($time->isEqualTo($this->time)) {
3✔
270
            return $this;
1✔
271
        }
272

273
        return new LocalDateTime($this->date, $time);
2✔
274
    }
275

276
    /**
277
     * Returns a copy of this LocalDateTime with the year altered.
278
     *
279
     * If the day-of-month is invalid for the year, it will be changed to the last valid day of the month.
280
     *
281
     * @throws DateTimeException If the year is outside the valid range.
282
     */
283
    public function withYear(int $year): LocalDateTime
284
    {
285
        $date = $this->date->withYear($year);
9✔
286

287
        if ($date === $this->date) {
7✔
288
            return $this;
1✔
289
        }
290

291
        return new LocalDateTime($date, $this->time);
6✔
292
    }
293

294
    /**
295
     * Returns a copy of this LocalDateTime with the month-of-year altered.
296
     *
297
     * If the day-of-month is invalid for the month and year, it will be changed to the last valid day of the month.
298
     *
299
     * @throws DateTimeException If the month is invalid.
300
     */
301
    public function withMonth(int|Month $month): LocalDateTime
302
    {
303
        $date = $this->date->withMonth($month);
19✔
304

305
        if ($date === $this->date) {
15✔
306
            return $this;
1✔
307
        }
308

309
        return new LocalDateTime($date, $this->time);
14✔
310
    }
311

312
    /**
313
     * Returns a copy of this LocalDateTime with the day-of-month altered.
314
     *
315
     * If the resulting date is invalid, an exception is thrown.
316
     *
317
     * @throws DateTimeException If the day is invalid for the current year and month.
318
     */
319
    public function withDay(int $day): LocalDateTime
320
    {
321
        $date = $this->date->withDay($day);
10✔
322

323
        if ($date === $this->date) {
5✔
324
            return $this;
1✔
325
        }
326

327
        return new LocalDateTime($date, $this->time);
4✔
328
    }
329

330
    /**
331
     * Returns a copy of this LocalDateTime with the hour-of-day altered.
332
     *
333
     * @throws DateTimeException If the hour is invalid.
334
     */
335
    public function withHour(int $hour): LocalDateTime
336
    {
337
        $time = $this->time->withHour($hour);
5✔
338

339
        if ($time === $this->time) {
3✔
340
            return $this;
1✔
341
        }
342

343
        return new LocalDateTime($this->date, $time);
2✔
344
    }
345

346
    /**
347
     * Returns a copy of this LocalDateTime with the minute-of-hour altered.
348
     *
349
     * @throws DateTimeException If the minute-of-hour if not valid.
350
     */
351
    public function withMinute(int $minute): LocalDateTime
352
    {
353
        $time = $this->time->withMinute($minute);
5✔
354

355
        if ($time === $this->time) {
3✔
356
            return $this;
1✔
357
        }
358

359
        return new LocalDateTime($this->date, $time);
2✔
360
    }
361

362
    /**
363
     * Returns a copy of this LocalDateTime with the second-of-minute altered.
364
     *
365
     * @throws DateTimeException If the second-of-minute if not valid.
366
     */
367
    public function withSecond(int $second): LocalDateTime
368
    {
369
        $time = $this->time->withSecond($second);
5✔
370

371
        if ($time === $this->time) {
3✔
372
            return $this;
1✔
373
        }
374

375
        return new LocalDateTime($this->date, $time);
2✔
376
    }
377

378
    /**
379
     * Returns a copy of this LocalDateTime with the nano-of-second altered.
380
     *
381
     * @throws DateTimeException If the nano-of-second if not valid.
382
     */
383
    public function withNano(int $nano): LocalDateTime
384
    {
385
        $time = $this->time->withNano($nano);
803✔
386

387
        if ($time === $this->time) {
801✔
388
            return $this;
336✔
389
        }
390

391
        return new LocalDateTime($this->date, $time);
489✔
392
    }
393

394
    /**
395
     * Returns a zoned date-time formed from this date-time and the specified time-zone.
396
     *
397
     * @param TimeZone $zone The zime-zone to use.
398
     *
399
     * @return ZonedDateTime The zoned date-time formed from this date-time.
400
     */
401
    public function atTimeZone(TimeZone $zone): ZonedDateTime
402
    {
403
        return ZonedDateTime::of($this, $zone);
39✔
404
    }
405

406
    /**
407
     * Returns a copy of this LocalDateTime with the specified Period added.
408
     */
409
    public function plusPeriod(Period $period): LocalDateTime
410
    {
411
        $date = $this->date->plusPeriod($period);
122✔
412

413
        if ($date === $this->date) {
122✔
UNCOV
414
            return $this;
×
415
        }
416

417
        return new LocalDateTime($date, $this->time);
122✔
418
    }
419

420
    /**
421
     * Returns a copy of this LocalDateTime with the specific Duration added.
422
     */
423
    public function plusDuration(Duration $duration): LocalDateTime
424
    {
425
        if ($duration->isZero()) {
10✔
UNCOV
426
            return $this;
×
427
        }
428

429
        $days = Math::floorDiv($this->time->toSecondOfDay() + $duration->getSeconds(), LocalTime::SECONDS_PER_DAY);
10✔
430

431
        return new LocalDateTime($this->date->plusDays($days), $this->time->plusDuration($duration));
10✔
432
    }
433

434
    /**
435
     * Returns a copy of this LocalDateTime with the specified period in years added.
436
     *
437
     * @throws DateTimeException If the resulting year is out of range.
438
     */
439
    public function plusYears(int $years): LocalDateTime
440
    {
441
        if ($years === 0) {
13✔
442
            return $this;
3✔
443
        }
444

445
        return new LocalDateTime($this->date->plusYears($years), $this->time);
10✔
446
    }
447

448
    /**
449
     * Returns a copy of this LocalDateTime with the specified period in months added.
450
     */
451
    public function plusMonths(int $months): LocalDateTime
452
    {
453
        if ($months === 0) {
21✔
454
            return $this;
3✔
455
        }
456

457
        return new LocalDateTime($this->date->plusMonths($months), $this->time);
18✔
458
    }
459

460
    /**
461
     * Returns a copy of this LocalDateTime with the specified period in weeks added.
462
     */
463
    public function plusWeeks(int $weeks): LocalDateTime
464
    {
465
        if ($weeks === 0) {
25✔
466
            return $this;
3✔
467
        }
468

469
        return new LocalDateTime($this->date->plusWeeks($weeks), $this->time);
22✔
470
    }
471

472
    /**
473
     * Returns a copy of this LocalDateTime with the specified period in days added.
474
     */
475
    public function plusDays(int $days): LocalDateTime
476
    {
477
        if ($days === 0) {
29✔
478
            return $this;
3✔
479
        }
480

481
        return new LocalDateTime($this->date->plusDays($days), $this->time);
26✔
482
    }
483

484
    /**
485
     * Returns a copy of this LocalDateTime with the specified period in hours added.
486
     */
487
    public function plusHours(int $hours): LocalDateTime
488
    {
489
        if ($hours === 0) {
3✔
490
            return $this;
1✔
491
        }
492

493
        return $this->plusWithOverflow($hours, 0, 0, 0, 1);
2✔
494
    }
495

496
    /**
497
     * Returns a copy of this LocalDateTime with the specified period in minutes added.
498
     */
499
    public function plusMinutes(int $minutes): LocalDateTime
500
    {
501
        if ($minutes === 0) {
3✔
502
            return $this;
1✔
503
        }
504

505
        return $this->plusWithOverflow(0, $minutes, 0, 0, 1);
2✔
506
    }
507

508
    /**
509
     * Returns a copy of this LocalDateTime with the specified period in seconds added.
510
     */
511
    public function plusSeconds(int $seconds): LocalDateTime
512
    {
513
        if ($seconds === 0) {
188✔
514
            return $this;
164✔
515
        }
516

517
        return $this->plusWithOverflow(0, 0, $seconds, 0, 1);
24✔
518
    }
519

520
    /**
521
     * Returns a copy of this LocalDateTime with the specified period in nanoseconds added.
522
     */
523
    public function plusNanos(int $nanos): LocalDateTime
524
    {
525
        if ($nanos === 0) {
4✔
526
            return $this;
1✔
527
        }
528

529
        return $this->plusWithOverflow(0, 0, 0, $nanos, 1);
3✔
530
    }
531

532
    /**
533
     * Returns a copy of this LocalDateTime with the specified Period subtracted.
534
     */
535
    public function minusPeriod(Period $period): LocalDateTime
536
    {
537
        return $this->plusPeriod($period->negated());
11✔
538
    }
539

540
    /**
541
     * Returns a copy of this LocalDateTime with the specific Duration subtracted.
542
     */
543
    public function minusDuration(Duration $duration): LocalDateTime
544
    {
545
        return $this->plusDuration($duration->negated());
5✔
546
    }
547

548
    /**
549
     * Returns a copy of this LocalDateTime with the specified period in years subtracted.
550
     */
551
    public function minusYears(int $years): LocalDateTime
552
    {
553
        if ($years === 0) {
3✔
554
            return $this;
1✔
555
        }
556

557
        return new LocalDateTime($this->date->minusYears($years), $this->time);
2✔
558
    }
559

560
    /**
561
     * Returns a copy of this LocalDateTime with the specified period in months subtracted.
562
     */
563
    public function minusMonths(int $months): LocalDateTime
564
    {
565
        if ($months === 0) {
3✔
566
            return $this;
1✔
567
        }
568

569
        return new LocalDateTime($this->date->minusMonths($months), $this->time);
2✔
570
    }
571

572
    /**
573
     * Returns a copy of this LocalDateTime with the specified period in weeks subtracted.
574
     */
575
    public function minusWeeks(int $weeks): LocalDateTime
576
    {
577
        if ($weeks === 0) {
3✔
578
            return $this;
1✔
579
        }
580

581
        return new LocalDateTime($this->date->minusWeeks($weeks), $this->time);
2✔
582
    }
583

584
    /**
585
     * Returns a copy of this LocalDateTime with the specified period in days subtracted.
586
     */
587
    public function minusDays(int $days): LocalDateTime
588
    {
589
        if ($days === 0) {
3✔
590
            return $this;
1✔
591
        }
592

593
        return new LocalDateTime($this->date->minusDays($days), $this->time);
2✔
594
    }
595

596
    /**
597
     * Returns a copy of this LocalDateTime with the specified period in hours subtracted.
598
     */
599
    public function minusHours(int $hours): LocalDateTime
600
    {
601
        if ($hours === 0) {
3✔
602
            return $this;
1✔
603
        }
604

605
        return $this->plusWithOverflow($hours, 0, 0, 0, -1);
2✔
606
    }
607

608
    /**
609
     * Returns a copy of this LocalDateTime with the specified period in minutes subtracted.
610
     */
611
    public function minusMinutes(int $minutes): LocalDateTime
612
    {
613
        if ($minutes === 0) {
3✔
614
            return $this;
1✔
615
        }
616

617
        return $this->plusWithOverflow(0, $minutes, 0, 0, -1);
2✔
618
    }
619

620
    /**
621
     * Returns a copy of this LocalDateTime with the specified period in seconds subtracted.
622
     */
623
    public function minusSeconds(int $seconds): LocalDateTime
624
    {
625
        if ($seconds === 0) {
3✔
626
            return $this;
1✔
627
        }
628

629
        return $this->plusWithOverflow(0, 0, $seconds, 0, -1);
2✔
630
    }
631

632
    /**
633
     * Returns a copy of this LocalDateTime with the specified period in nanoseconds subtracted.
634
     */
635
    public function minusNanos(int $nanos): LocalDateTime
636
    {
637
        if ($nanos === 0) {
4✔
638
            return $this;
1✔
639
        }
640

641
        return $this->plusWithOverflow(0, 0, 0, $nanos, -1);
3✔
642
    }
643

644
    /**
645
     * Compares this date-time to another date-time.
646
     *
647
     * @param LocalDateTime $that The date-time to compare to.
648
     *
649
     * @return int [-1,0,1] If this date-time is before, on, or after the given date-time.
650
     *
651
     * @psalm-return -1|0|1
652
     */
653
    public function compareTo(LocalDateTime $that): int
654
    {
655
        $cmp = $this->date->compareTo($that->date);
209✔
656

657
        if ($cmp !== 0) {
209✔
658
            return $cmp;
15✔
659
        }
660

661
        return $this->time->compareTo($that->time);
194✔
662
    }
663

664
    public function isEqualTo(LocalDateTime $that): bool
665
    {
666
        return $this->compareTo($that) === 0;
200✔
667
    }
668

669
    public function isBefore(LocalDateTime $that): bool
670
    {
671
        return $this->compareTo($that) === -1;
20✔
672
    }
673

674
    public function isBeforeOrEqualTo(LocalDateTime $that): bool
675
    {
676
        return $this->compareTo($that) <= 0;
15✔
677
    }
678

679
    public function isAfter(LocalDateTime $that): bool
680
    {
681
        return $this->compareTo($that) === 1;
20✔
682
    }
683

684
    public function isAfterOrEqualTo(LocalDateTime $that): bool
685
    {
686
        return $this->compareTo($that) >= 0;
15✔
687
    }
688

689
    /**
690
     * Returns whether this LocalDateTime is in the future, in the given time-zone, according to the given clock.
691
     *
692
     * If no clock is provided, the system clock is used.
693
     */
694
    public function isFuture(TimeZone $timeZone, ?Clock $clock = null): bool
695
    {
696
        return $this->isAfter(LocalDateTime::now($timeZone, $clock));
4✔
697
    }
698

699
    /**
700
     * Returns whether this LocalDateTime is in the past, in the given time-zone, according to the given clock.
701
     *
702
     * If no clock is provided, the system clock is used.
703
     */
704
    public function isPast(TimeZone $timeZone, ?Clock $clock = null): bool
705
    {
706
        return $this->isBefore(LocalDateTime::now($timeZone, $clock));
4✔
707
    }
708

709
    /**
710
     * Converts this LocalDateTime to a native DateTime object.
711
     *
712
     * The result is a DateTime in the UTC time-zone.
713
     *
714
     * Note that the native DateTime object supports a precision up to the microsecond,
715
     * so the nanoseconds are rounded down to the nearest microsecond.
716
     */
717
    public function toNativeDateTime(): DateTime
718
    {
719
        return $this->atTimeZone(TimeZone::utc())->toNativeDateTime();
35✔
720
    }
721

722
    /**
723
     * Converts this LocalDateTime to a native DateTimeImmutable object.
724
     *
725
     * The result is a DateTimeImmutable in the UTC time-zone.
726
     *
727
     * Note that the native DateTimeImmutable object supports a precision up to the microsecond,
728
     * so the nanoseconds are rounded down to the nearest microsecond.
729
     */
730
    public function toNativeDateTimeImmutable(): DateTimeImmutable
731
    {
732
        return DateTimeImmutable::createFromMutable($this->toNativeDateTime());
7✔
733
    }
734

735
    /**
736
     * Serializes as a string using {@see LocalDateTime::toISOString()}.
737
     *
738
     * @psalm-return non-empty-string
739
     */
740
    #[Override]
741
    public function jsonSerialize(): string
742
    {
743
        return $this->toISOString();
6✔
744
    }
745

746
    /**
747
     * Returns the ISO 8601 representation of this date time.
748
     *
749
     * @psalm-return non-empty-string
750
     */
751
    public function toISOString(): string
752
    {
753
        return $this->date . 'T' . $this->time;
805✔
754
    }
755

756
    /**
757
     * {@see LocalDateTime::toISOString()}.
758
     *
759
     * @psalm-return non-empty-string
760
     */
761
    #[Override]
762
    public function __toString(): string
763
    {
764
        return $this->toISOString();
793✔
765
    }
766

767
    /**
768
     * Returns a copy of this `LocalDateTime` with the specified period added.
769
     *
770
     * @param int $hours   The hours to add. May be negative.
771
     * @param int $minutes The minutes to add. May be negative.
772
     * @param int $seconds The seconds to add. May be negative.
773
     * @param int $nanos   The nanos to add. May be negative.
774
     * @param int $sign    The sign, validated as `1` to add or `-1` to subtract.
775
     *
776
     * @return LocalDateTime The combined result.
777
     */
778
    private function plusWithOverflow(int $hours, int $minutes, int $seconds, int $nanos, int $sign): LocalDateTime
779
    {
780
        $totDays =
40✔
781
            intdiv($hours, LocalTime::HOURS_PER_DAY) +
40✔
782
            intdiv($minutes, LocalTime::MINUTES_PER_DAY) +
40✔
783
            intdiv($seconds, LocalTime::SECONDS_PER_DAY);
40✔
784
        $totDays *= $sign;
40✔
785

786
        $totSeconds =
40✔
787
            ($seconds % LocalTime::SECONDS_PER_DAY) +
40✔
788
            ($minutes % LocalTime::MINUTES_PER_DAY) * LocalTime::SECONDS_PER_MINUTE +
40✔
789
            ($hours % LocalTime::HOURS_PER_DAY) * LocalTime::SECONDS_PER_HOUR;
40✔
790

791
        $curSoD = $this->time->toSecondOfDay();
40✔
792
        $totSeconds = $totSeconds * $sign + $curSoD;
40✔
793

794
        $totNanos = $nanos * $sign + $this->time->getNano();
40✔
795
        $totSeconds += Math::floorDiv($totNanos, LocalTime::NANOS_PER_SECOND);
40✔
796
        $newNano = Math::floorMod($totNanos, LocalTime::NANOS_PER_SECOND);
40✔
797

798
        $totDays += Math::floorDiv($totSeconds, LocalTime::SECONDS_PER_DAY);
40✔
799
        $newSoD = Math::floorMod($totSeconds, LocalTime::SECONDS_PER_DAY);
40✔
800

801
        $newDate = $this->date->plusDays($totDays);
40✔
802
        $newTime = ($newSoD === $curSoD ? $this->time : LocalTime::ofSecondOfDay($newSoD, $newNano));
40✔
803

804
        return new LocalDateTime($newDate, $newTime);
40✔
805
    }
806
}
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