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

codeigniter4 / CodeIgniter4 / 12673986434

08 Jan 2025 03:42PM UTC coverage: 84.455% (+0.001%) from 84.454%
12673986434

Pull #9385

github

web-flow
Merge 06e47f0ee into e475fd8fa
Pull Request #9385: refactor: Fix phpstan expr.resultUnused

20699 of 24509 relevant lines covered (84.45%)

190.57 hits per line

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

97.35
/system/I18n/TimeTrait.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\I18n;
15

16
use CodeIgniter\I18n\Exceptions\I18nException;
17
use DateInterval;
18
use DateTime;
19
use DateTimeImmutable;
20
use DateTimeInterface;
21
use DateTimeZone;
22
use Exception;
23
use IntlCalendar;
24
use IntlDateFormatter;
25
use Locale;
26
use ReturnTypeWillChange;
27

28
/**
29
 * This trait has properties and methods for Time and TimeLegacy.
30
 * When TimeLegacy is removed, this will be in Time.
31
 */
32
trait TimeTrait
33
{
34
    /**
35
     * @var DateTimeZone
36
     */
37
    protected $timezone;
38

39
    /**
40
     * @var string
41
     */
42
    protected $locale;
43

44
    /**
45
     * Format to use when displaying datetime through __toString
46
     *
47
     * @var string
48
     */
49
    protected $toStringFormat = 'yyyy-MM-dd HH:mm:ss';
50

51
    /**
52
     * Used to check time string to determine if it is relative time or not....
53
     *
54
     * @var string
55
     */
56
    protected static $relativePattern = '/this|next|last|tomorrow|yesterday|midnight|today|[+-]|first|last|ago/i';
57

58
    /**
59
     * @var DateTimeInterface|static|null
60
     */
61
    protected static $testNow;
62

63
    // --------------------------------------------------------------------
64
    // Constructors
65
    // --------------------------------------------------------------------
66

67
    /**
68
     * Time constructor.
69
     *
70
     * @param DateTimeZone|string|null $timezone
71
     *
72
     * @throws Exception
73
     */
74
    public function __construct(?string $time = null, $timezone = null, ?string $locale = null)
75
    {
76
        $this->locale = $locale !== null && $locale !== '' && $locale !== '0' ? $locale : Locale::getDefault();
6,557✔
77

78
        $time ??= '';
6,557✔
79

80
        // If a test instance has been provided, use it instead.
81
        if ($time === '' && static::$testNow instanceof static) {
6,557✔
82
            if ($timezone !== null) {
84✔
83
                $testNow = static::$testNow->setTimezone($timezone);
44✔
84
                $time    = $testNow->format('Y-m-d H:i:s.u');
44✔
85
            } else {
86
                $timezone = static::$testNow->getTimezone();
40✔
87
                $time     = static::$testNow->format('Y-m-d H:i:s.u');
40✔
88
            }
89
        }
90

91
        $timezone       = $timezone ?: date_default_timezone_get();
6,557✔
92
        $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
6,557✔
93

94
        // If the time string was a relative string (i.e. 'next Tuesday')
95
        // then we need to adjust the time going in so that we have a current
96
        // timezone to work with.
97
        if ($time !== '' && static::hasRelativeKeywords($time)) {
6,557✔
98
            $instance = new DateTime('now', $this->timezone);
4✔
99
            $instance->modify($time);
4✔
100
            $time = $instance->format('Y-m-d H:i:s.u');
4✔
101
        }
102

103
        parent::__construct($time, $this->timezone);
6,557✔
104
    }
105

106
    /**
107
     * Returns a new Time instance with the timezone set.
108
     *
109
     * @param DateTimeZone|string|null $timezone
110
     *
111
     * @return static
112
     *
113
     * @throws Exception
114
     */
115
    public static function now($timezone = null, ?string $locale = null)
116
    {
117
        return new static(null, $timezone, $locale);
6,557✔
118
    }
119

120
    /**
121
     * Returns a new Time instance while parsing a datetime string.
122
     *
123
     * Example:
124
     *  $time = Time::parse('first day of December 2008');
125
     *
126
     * @param DateTimeZone|string|null $timezone
127
     *
128
     * @return static
129
     *
130
     * @throws Exception
131
     */
132
    public static function parse(string $datetime, $timezone = null, ?string $locale = null)
133
    {
134
        return new static($datetime, $timezone, $locale);
229✔
135
    }
136

137
    /**
138
     * Return a new time with the time set to midnight.
139
     *
140
     * @param DateTimeZone|string|null $timezone
141
     *
142
     * @return static
143
     *
144
     * @throws Exception
145
     */
146
    public static function today($timezone = null, ?string $locale = null)
147
    {
148
        return new static(date('Y-m-d 00:00:00'), $timezone, $locale);
4✔
149
    }
150

151
    /**
152
     * Returns an instance set to midnight yesterday morning.
153
     *
154
     * @param DateTimeZone|string|null $timezone
155
     *
156
     * @return static
157
     *
158
     * @throws Exception
159
     */
160
    public static function yesterday($timezone = null, ?string $locale = null)
161
    {
162
        return new static(date('Y-m-d 00:00:00', strtotime('-1 day')), $timezone, $locale);
2✔
163
    }
164

165
    /**
166
     * Returns an instance set to midnight tomorrow morning.
167
     *
168
     * @param DateTimeZone|string|null $timezone
169
     *
170
     * @return static
171
     *
172
     * @throws Exception
173
     */
174
    public static function tomorrow($timezone = null, ?string $locale = null)
175
    {
176
        return new static(date('Y-m-d 00:00:00', strtotime('+1 day')), $timezone, $locale);
2✔
177
    }
178

179
    /**
180
     * Returns a new instance based on the year, month and day. If any of those three
181
     * are left empty, will default to the current value.
182
     *
183
     * @param DateTimeZone|string|null $timezone
184
     *
185
     * @return static
186
     *
187
     * @throws Exception
188
     */
189
    public static function createFromDate(?int $year = null, ?int $month = null, ?int $day = null, $timezone = null, ?string $locale = null)
190
    {
191
        return static::create($year, $month, $day, null, null, null, $timezone, $locale);
8✔
192
    }
193

194
    /**
195
     * Returns a new instance with the date set to today, and the time set to the values passed in.
196
     *
197
     * @param DateTimeZone|string|null $timezone
198
     *
199
     * @return static
200
     *
201
     * @throws Exception
202
     */
203
    public static function createFromTime(?int $hour = null, ?int $minutes = null, ?int $seconds = null, $timezone = null, ?string $locale = null)
204
    {
205
        return static::create(null, null, null, $hour, $minutes, $seconds, $timezone, $locale);
6✔
206
    }
207

208
    /**
209
     * Returns a new instance with the date time values individually set.
210
     *
211
     * @param DateTimeZone|string|null $timezone
212
     *
213
     * @return static
214
     *
215
     * @throws Exception
216
     */
217
    public static function create(
218
        ?int $year = null,
219
        ?int $month = null,
220
        ?int $day = null,
221
        ?int $hour = null,
222
        ?int $minutes = null,
223
        ?int $seconds = null,
224
        $timezone = null,
225
        ?string $locale = null
226
    ) {
227
        $year ??= date('Y');
32✔
228
        $month ??= date('m');
32✔
229
        $day ??= date('d');
32✔
230
        $hour ??= 0;
32✔
231
        $minutes ??= 0;
32✔
232
        $seconds ??= 0;
32✔
233

234
        return new static(date('Y-m-d H:i:s', strtotime("{$year}-{$month}-{$day} {$hour}:{$minutes}:{$seconds}")), $timezone, $locale);
32✔
235
    }
236

237
    /**
238
     * Provides a replacement for DateTime's own createFromFormat function, that provides
239
     * more flexible timeZone handling
240
     *
241
     * @param string                   $format
242
     * @param string                   $datetime
243
     * @param DateTimeZone|string|null $timezone
244
     *
245
     * @return static
246
     *
247
     * @throws Exception
248
     */
249
    #[ReturnTypeWillChange]
250
    public static function createFromFormat($format, $datetime, $timezone = null)
251
    {
252
        if (! $date = parent::createFromFormat($format, $datetime)) {
38✔
253
            throw I18nException::forInvalidFormat($format);
2✔
254
        }
255

256
        return new static($date->format('Y-m-d H:i:s.u'), $timezone);
36✔
257
    }
258

259
    /**
260
     * Returns a new instance with the datetime set based on the provided UNIX timestamp.
261
     *
262
     * @param DateTimeZone|string|null $timezone
263
     *
264
     * @throws Exception
265
     */
266
    public static function createFromTimestamp(float|int $timestamp, $timezone = null, ?string $locale = null): static
267
    {
268
        $time = new static(sprintf('@%.6f', $timestamp), 'UTC', $locale);
16✔
269

270
        $timezone ??= 'UTC';
16✔
271

272
        return $time->setTimezone($timezone);
16✔
273
    }
274

275
    /**
276
     * Takes an instance of DateTimeInterface and returns an instance of Time with it's same values.
277
     *
278
     * @return static
279
     *
280
     * @throws Exception
281
     */
282
    public static function createFromInstance(DateTimeInterface $dateTime, ?string $locale = null)
283
    {
284
        $date     = $dateTime->format('Y-m-d H:i:s.u');
68✔
285
        $timezone = $dateTime->getTimezone();
68✔
286

287
        return new static($date, $timezone, $locale);
68✔
288
    }
289

290
    /**
291
     * Takes an instance of DateTime and returns an instance of Time with it's same values.
292
     *
293
     * @return static
294
     *
295
     * @throws Exception
296
     *
297
     * @deprecated         Use createFromInstance() instead
298
     *
299
     * @codeCoverageIgnore
300
     */
301
    public static function instance(DateTime $dateTime, ?string $locale = null)
302
    {
303
        return static::createFromInstance($dateTime, $locale);
×
304
    }
305

306
    /**
307
     * Converts the current instance to a mutable DateTime object.
308
     *
309
     * @return DateTime
310
     *
311
     * @throws Exception
312
     */
313
    public function toDateTime()
314
    {
315
        return DateTime::createFromFormat(
239✔
316
            'Y-m-d H:i:s.u',
239✔
317
            $this->format('Y-m-d H:i:s.u'),
239✔
318
            $this->getTimezone()
239✔
319
        );
239✔
320
    }
321

322
    // --------------------------------------------------------------------
323
    // For Testing
324
    // --------------------------------------------------------------------
325

326
    /**
327
     * Creates an instance of Time that will be returned during testing
328
     * when calling 'Time::now()' instead of the current time.
329
     *
330
     * @param DateTimeInterface|self|string|null $datetime
331
     * @param DateTimeZone|string|null           $timezone
332
     *
333
     * @return void
334
     *
335
     * @throws Exception
336
     */
337
    public static function setTestNow($datetime = null, $timezone = null, ?string $locale = null)
338
    {
339
        // Reset the test instance
340
        if ($datetime === null) {
312✔
341
            static::$testNow = null;
312✔
342

343
            return;
312✔
344
        }
345

346
        // Convert to a Time instance
347
        if (is_string($datetime)) {
86✔
348
            $datetime = new static($datetime, $timezone, $locale);
82✔
349
        } elseif ($datetime instanceof DateTimeInterface && ! $datetime instanceof static) {
4✔
350
            $datetime = new static($datetime->format('Y-m-d H:i:s.u'), $timezone);
2✔
351
        }
352

353
        static::$testNow = $datetime;
86✔
354
    }
355

356
    /**
357
     * Returns whether we have a testNow instance saved.
358
     */
359
    public static function hasTestNow(): bool
360
    {
361
        return static::$testNow !== null;
2✔
362
    }
363

364
    // --------------------------------------------------------------------
365
    // Getters
366
    // --------------------------------------------------------------------
367

368
    /**
369
     * Returns the localized Year
370
     *
371
     * @throws Exception
372
     */
373
    public function getYear(): string
374
    {
375
        return $this->toLocalizedString('y');
8✔
376
    }
377

378
    /**
379
     * Returns the localized Month
380
     *
381
     * @throws Exception
382
     */
383
    public function getMonth(): string
384
    {
385
        return $this->toLocalizedString('M');
8✔
386
    }
387

388
    /**
389
     * Return the localized day of the month.
390
     *
391
     * @throws Exception
392
     */
393
    public function getDay(): string
394
    {
395
        return $this->toLocalizedString('d');
4✔
396
    }
397

398
    /**
399
     * Return the localized hour (in 24-hour format).
400
     *
401
     * @throws Exception
402
     */
403
    public function getHour(): string
404
    {
405
        return $this->toLocalizedString('H');
2✔
406
    }
407

408
    /**
409
     * Return the localized minutes in the hour.
410
     *
411
     * @throws Exception
412
     */
413
    public function getMinute(): string
414
    {
415
        return $this->toLocalizedString('m');
2✔
416
    }
417

418
    /**
419
     * Return the localized seconds
420
     *
421
     * @throws Exception
422
     */
423
    public function getSecond(): string
424
    {
425
        return $this->toLocalizedString('s');
2✔
426
    }
427

428
    /**
429
     * Return the index of the day of the week
430
     *
431
     * @throws Exception
432
     */
433
    public function getDayOfWeek(): string
434
    {
435
        return $this->toLocalizedString('c');
2✔
436
    }
437

438
    /**
439
     * Return the index of the day of the year
440
     *
441
     * @throws Exception
442
     */
443
    public function getDayOfYear(): string
444
    {
445
        return $this->toLocalizedString('D');
2✔
446
    }
447

448
    /**
449
     * Return the index of the week in the month
450
     *
451
     * @throws Exception
452
     */
453
    public function getWeekOfMonth(): string
454
    {
455
        return $this->toLocalizedString('W');
2✔
456
    }
457

458
    /**
459
     * Return the index of the week in the year
460
     *
461
     * @throws Exception
462
     */
463
    public function getWeekOfYear(): string
464
    {
465
        return $this->toLocalizedString('w');
2✔
466
    }
467

468
    /**
469
     * Returns the age in years from the date and 'now'
470
     *
471
     * @return int
472
     *
473
     * @throws Exception
474
     */
475
    public function getAge()
476
    {
477
        // future dates have no age
478
        return max(0, $this->difference(static::now())->getYears());
12✔
479
    }
480

481
    /**
482
     * Returns the number of the current quarter for the year.
483
     *
484
     * @throws Exception
485
     */
486
    public function getQuarter(): string
487
    {
488
        return $this->toLocalizedString('Q');
2✔
489
    }
490

491
    /**
492
     * Are we in daylight savings time currently?
493
     */
494
    public function getDst(): bool
495
    {
496
        return $this->format('I') === '1'; // 1 if Daylight Saving Time, 0 otherwise.
4✔
497
    }
498

499
    /**
500
     * Returns boolean whether the passed timezone is the same as
501
     * the local timezone.
502
     */
503
    public function getLocal(): bool
504
    {
505
        $local = date_default_timezone_get();
2✔
506

507
        return $local === $this->timezone->getName();
2✔
508
    }
509

510
    /**
511
     * Returns boolean whether object is in UTC.
512
     */
513
    public function getUtc(): bool
514
    {
515
        return $this->getOffset() === 0;
2✔
516
    }
517

518
    /**
519
     * Returns the name of the current timezone.
520
     */
521
    public function getTimezoneName(): string
522
    {
523
        return $this->timezone->getName();
24✔
524
    }
525

526
    // --------------------------------------------------------------------
527
    // Setters
528
    // --------------------------------------------------------------------
529

530
    /**
531
     * Sets the current year for this instance.
532
     *
533
     * @param int|string $value
534
     *
535
     * @return static
536
     *
537
     * @throws Exception
538
     */
539
    public function setYear($value)
540
    {
541
        return $this->setValue('year', $value);
2✔
542
    }
543

544
    /**
545
     * Sets the month of the year.
546
     *
547
     * @param int|string $value
548
     *
549
     * @return static
550
     *
551
     * @throws Exception
552
     */
553
    public function setMonth($value)
554
    {
555
        if (is_numeric($value) && ($value < 1 || $value > 12)) {
10✔
556
            throw I18nException::forInvalidMonth((string) $value);
4✔
557
        }
558

559
        if (is_string($value) && ! is_numeric($value)) {
6✔
560
            $value = date('m', strtotime("{$value} 1 2017"));
4✔
561
        }
562

563
        return $this->setValue('month', $value);
6✔
564
    }
565

566
    /**
567
     * Sets the day of the month.
568
     *
569
     * @param int|string $value
570
     *
571
     * @return static
572
     *
573
     * @throws Exception
574
     */
575
    public function setDay($value)
576
    {
577
        if ($value < 1 || $value > 31) {
10✔
578
            throw I18nException::forInvalidDay((string) $value);
4✔
579
        }
580

581
        $date    = $this->getYear() . '-' . $this->getMonth();
6✔
582
        $lastDay = date('t', strtotime($date));
6✔
583
        if ($value > $lastDay) {
6✔
584
            throw I18nException::forInvalidOverDay($lastDay, (string) $value);
2✔
585
        }
586

587
        return $this->setValue('day', $value);
4✔
588
    }
589

590
    /**
591
     * Sets the hour of the day (24 hour cycle)
592
     *
593
     * @param int|string $value
594
     *
595
     * @return static
596
     *
597
     * @throws Exception
598
     */
599
    public function setHour($value)
600
    {
601
        if ($value < 0 || $value > 23) {
6✔
602
            throw I18nException::forInvalidHour((string) $value);
4✔
603
        }
604

605
        return $this->setValue('hour', $value);
2✔
606
    }
607

608
    /**
609
     * Sets the minute of the hour
610
     *
611
     * @param int|string $value
612
     *
613
     * @return static
614
     *
615
     * @throws Exception
616
     */
617
    public function setMinute($value)
618
    {
619
        if ($value < 0 || $value > 59) {
6✔
620
            throw I18nException::forInvalidMinutes((string) $value);
4✔
621
        }
622

623
        return $this->setValue('minute', $value);
2✔
624
    }
625

626
    /**
627
     * Sets the second of the minute.
628
     *
629
     * @param int|string $value
630
     *
631
     * @return static
632
     *
633
     * @throws Exception
634
     */
635
    public function setSecond($value)
636
    {
637
        if ($value < 0 || $value > 59) {
6✔
638
            throw I18nException::forInvalidSeconds((string) $value);
4✔
639
        }
640

641
        return $this->setValue('second', $value);
2✔
642
    }
643

644
    /**
645
     * Helper method to do the heavy lifting of the 'setX' methods.
646
     *
647
     * @param int $value
648
     *
649
     * @return static
650
     *
651
     * @throws Exception
652
     */
653
    protected function setValue(string $name, $value)
654
    {
655
        [$year, $month, $day, $hour, $minute, $second] = explode('-', $this->format('Y-n-j-G-i-s'));
18✔
656

657
        ${$name} = $value;
18✔
658

659
        return static::create(
18✔
660
            (int) $year,
18✔
661
            (int) $month,
18✔
662
            (int) $day,
18✔
663
            (int) $hour,
18✔
664
            (int) $minute,
18✔
665
            (int) $second,
18✔
666
            $this->getTimezoneName(),
18✔
667
            $this->locale
18✔
668
        );
18✔
669
    }
670

671
    /**
672
     * Returns a new instance with the revised timezone.
673
     *
674
     * @param DateTimeZone|string $timezone
675
     *
676
     * @return static
677
     *
678
     * @throws Exception
679
     */
680
    #[ReturnTypeWillChange]
681
    public function setTimezone($timezone)
682
    {
683
        $timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
64✔
684

685
        return static::createFromInstance($this->toDateTime()->setTimezone($timezone), $this->locale);
64✔
686
    }
687

688
    // --------------------------------------------------------------------
689
    // Add/Subtract
690
    // --------------------------------------------------------------------
691

692
    /**
693
     * Returns a new Time instance with $seconds added to the time.
694
     *
695
     * @return static
696
     */
697
    public function addSeconds(int $seconds)
698
    {
699
        $time = clone $this;
2✔
700

701
        return $time->add(DateInterval::createFromDateString("{$seconds} seconds"));
2✔
702
    }
703

704
    /**
705
     * Returns a new Time instance with $minutes added to the time.
706
     *
707
     * @return static
708
     */
709
    public function addMinutes(int $minutes)
710
    {
711
        $time = clone $this;
2✔
712

713
        return $time->add(DateInterval::createFromDateString("{$minutes} minutes"));
2✔
714
    }
715

716
    /**
717
     * Returns a new Time instance with $hours added to the time.
718
     *
719
     * @return static
720
     */
721
    public function addHours(int $hours)
722
    {
723
        $time = clone $this;
2✔
724

725
        return $time->add(DateInterval::createFromDateString("{$hours} hours"));
2✔
726
    }
727

728
    /**
729
     * Returns a new Time instance with $days added to the time.
730
     *
731
     * @return static
732
     */
733
    public function addDays(int $days)
734
    {
735
        $time = clone $this;
2✔
736

737
        return $time->add(DateInterval::createFromDateString("{$days} days"));
2✔
738
    }
739

740
    /**
741
     * Returns a new Time instance with $months added to the time.
742
     *
743
     * @return static
744
     */
745
    public function addMonths(int $months)
746
    {
747
        $time = clone $this;
4✔
748

749
        return $time->add(DateInterval::createFromDateString("{$months} months"));
4✔
750
    }
751

752
    /**
753
     * Returns a new Time instance with $years added to the time.
754
     *
755
     * @return static
756
     */
757
    public function addYears(int $years)
758
    {
759
        $time = clone $this;
2✔
760

761
        return $time->add(DateInterval::createFromDateString("{$years} years"));
2✔
762
    }
763

764
    /**
765
     * Returns a new Time instance with $seconds subtracted from the time.
766
     *
767
     * @return static
768
     */
769
    public function subSeconds(int $seconds)
770
    {
771
        $time = clone $this;
2✔
772

773
        return $time->sub(DateInterval::createFromDateString("{$seconds} seconds"));
2✔
774
    }
775

776
    /**
777
     * Returns a new Time instance with $minutes subtracted from the time.
778
     *
779
     * @return static
780
     */
781
    public function subMinutes(int $minutes)
782
    {
783
        $time = clone $this;
2✔
784

785
        return $time->sub(DateInterval::createFromDateString("{$minutes} minutes"));
2✔
786
    }
787

788
    /**
789
     * Returns a new Time instance with $hours subtracted from the time.
790
     *
791
     * @return static
792
     */
793
    public function subHours(int $hours)
794
    {
795
        $time = clone $this;
2✔
796

797
        return $time->sub(DateInterval::createFromDateString("{$hours} hours"));
2✔
798
    }
799

800
    /**
801
     * Returns a new Time instance with $days subtracted from the time.
802
     *
803
     * @return static
804
     */
805
    public function subDays(int $days)
806
    {
807
        $time = clone $this;
2✔
808

809
        return $time->sub(DateInterval::createFromDateString("{$days} days"));
2✔
810
    }
811

812
    /**
813
     * Returns a new Time instance with $months subtracted from the time.
814
     *
815
     * @return static
816
     */
817
    public function subMonths(int $months)
818
    {
819
        $time = clone $this;
2✔
820

821
        return $time->sub(DateInterval::createFromDateString("{$months} months"));
2✔
822
    }
823

824
    /**
825
     * Returns a new Time instance with $hours subtracted from the time.
826
     *
827
     * @return static
828
     */
829
    public function subYears(int $years)
830
    {
831
        $time = clone $this;
2✔
832

833
        return $time->sub(DateInterval::createFromDateString("{$years} years"));
2✔
834
    }
835

836
    // --------------------------------------------------------------------
837
    // Formatters
838
    // --------------------------------------------------------------------
839

840
    /**
841
     * Returns the localized value of the date in the format 'Y-m-d H:i:s'
842
     *
843
     * @return false|string
844
     *
845
     * @throws Exception
846
     */
847
    public function toDateTimeString()
848
    {
849
        return $this->toLocalizedString('yyyy-MM-dd HH:mm:ss');
89✔
850
    }
851

852
    /**
853
     * Returns a localized version of the date in Y-m-d format.
854
     *
855
     * @return string
856
     *
857
     * @throws Exception
858
     */
859
    public function toDateString()
860
    {
861
        return $this->toLocalizedString('yyyy-MM-dd');
2✔
862
    }
863

864
    /**
865
     * Returns a localized version of the date in nicer date format:
866
     *
867
     *  i.e. Apr 1, 2017
868
     *
869
     * @return string
870
     *
871
     * @throws Exception
872
     */
873
    public function toFormattedDateString()
874
    {
875
        return $this->toLocalizedString('MMM d, yyyy');
2✔
876
    }
877

878
    /**
879
     * Returns a localized version of the time in nicer date format:
880
     *
881
     *  i.e. 13:20:33
882
     *
883
     * @return string
884
     *
885
     * @throws Exception
886
     */
887
    public function toTimeString()
888
    {
889
        return $this->toLocalizedString('HH:mm:ss');
2✔
890
    }
891

892
    /**
893
     * Returns the localized value of this instance in $format.
894
     *
895
     * @return false|string
896
     *
897
     * @throws Exception
898
     */
899
    public function toLocalizedString(?string $format = null)
900
    {
901
        $format ??= $this->toStringFormat;
121✔
902

903
        return IntlDateFormatter::formatObject($this->toDateTime(), $format, $this->locale);
121✔
904
    }
905

906
    // --------------------------------------------------------------------
907
    // Comparison
908
    // --------------------------------------------------------------------
909

910
    /**
911
     * Determines if the datetime passed in is equal to the current instance.
912
     * Equal in this case means that they represent the same moment in time,
913
     * and are not required to be in the same timezone, as both times are
914
     * converted to UTC and compared that way.
915
     *
916
     * @param DateTimeInterface|self|string $testTime
917
     *
918
     * @throws Exception
919
     */
920
    public function equals($testTime, ?string $timezone = null): bool
921
    {
922
        $testTime = $this->getUTCObject($testTime, $timezone);
15✔
923

924
        $ourTime = $this->toDateTime()
15✔
925
            ->setTimezone(new DateTimeZone('UTC'))
15✔
926
            ->format('Y-m-d H:i:s.u');
15✔
927

928
        return $testTime->format('Y-m-d H:i:s.u') === $ourTime;
15✔
929
    }
930

931
    /**
932
     * Ensures that the times are identical, taking timezone into account.
933
     *
934
     * @param DateTimeInterface|self|string $testTime
935
     *
936
     * @throws Exception
937
     */
938
    public function sameAs($testTime, ?string $timezone = null): bool
939
    {
940
        if ($testTime instanceof DateTimeInterface) {
10✔
941
            $testTime = $testTime->format('Y-m-d H:i:s.u O');
6✔
942
        } elseif (is_string($testTime)) {
4✔
943
            $timezone = $timezone !== null && $timezone !== '' && $timezone !== '0' ? $timezone : $this->timezone;
4✔
944
            $timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
4✔
945
            $testTime = new DateTime($testTime, $timezone);
4✔
946
            $testTime = $testTime->format('Y-m-d H:i:s.u O');
4✔
947
        }
948

949
        $ourTime = $this->format('Y-m-d H:i:s.u O');
10✔
950

951
        return $testTime === $ourTime;
10✔
952
    }
953

954
    /**
955
     * Determines if the current instance's time is before $testTime,
956
     * after converting to UTC.
957
     *
958
     * @param DateTimeInterface|self|string $testTime
959
     *
960
     * @throws Exception
961
     */
962
    public function isBefore($testTime, ?string $timezone = null): bool
963
    {
964
        $testTime = $this->getUTCObject($testTime, $timezone);
4✔
965

966
        $testTimestamp = $testTime->getTimestamp();
4✔
967
        $ourTimestamp  = $this->getTimestamp();
4✔
968

969
        if ($ourTimestamp === $testTimestamp) {
4✔
970
            return $this->format('u') < $testTime->format('u');
2✔
971
        }
972

973
        return $ourTimestamp < $testTimestamp;
2✔
974
    }
975

976
    /**
977
     * Determines if the current instance's time is after $testTime,
978
     * after converting in UTC.
979
     *
980
     * @param DateTimeInterface|self|string $testTime
981
     *
982
     * @throws Exception
983
     */
984
    public function isAfter($testTime, ?string $timezone = null): bool
985
    {
986
        $testTime = $this->getUTCObject($testTime, $timezone);
4✔
987

988
        $testTimestamp = $testTime->getTimestamp();
4✔
989
        $ourTimestamp  = $this->getTimestamp();
4✔
990

991
        if ($ourTimestamp === $testTimestamp) {
4✔
992
            return $this->format('u') > $testTime->format('u');
2✔
993
        }
994

995
        return $ourTimestamp > $testTimestamp;
2✔
996
    }
997

998
    // --------------------------------------------------------------------
999
    // Differences
1000
    // --------------------------------------------------------------------
1001

1002
    /**
1003
     * Returns a text string that is easily readable that describes
1004
     * how long ago, or how long from now, a date is, like:
1005
     *
1006
     *  - 3 weeks ago
1007
     *  - in 4 days
1008
     *  - 6 hours ago
1009
     *
1010
     * @return string
1011
     *
1012
     * @throws Exception
1013
     */
1014
    public function humanize()
1015
    {
1016
        $now  = IntlCalendar::fromDateTime(self::now($this->timezone)->toDateTime());
42✔
1017
        $time = $this->getCalendar()->getTime();
42✔
1018

1019
        $years   = $now->fieldDifference($time, IntlCalendar::FIELD_YEAR);
42✔
1020
        $months  = $now->fieldDifference($time, IntlCalendar::FIELD_MONTH);
42✔
1021
        $days    = $now->fieldDifference($time, IntlCalendar::FIELD_DAY_OF_YEAR);
42✔
1022
        $hours   = $now->fieldDifference($time, IntlCalendar::FIELD_HOUR_OF_DAY);
42✔
1023
        $minutes = $now->fieldDifference($time, IntlCalendar::FIELD_MINUTE);
42✔
1024

1025
        $phrase = null;
42✔
1026

1027
        if ($years !== 0) {
42✔
1028
            $phrase = lang('Time.years', [abs($years)]);
6✔
1029
            $before = $years < 0;
6✔
1030
        } elseif ($months !== 0) {
36✔
1031
            $phrase = lang('Time.months', [abs($months)]);
6✔
1032
            $before = $months < 0;
6✔
1033
        } elseif ($days !== 0 && (abs($days) >= 7)) {
30✔
1034
            $weeks  = ceil($days / 7);
8✔
1035
            $phrase = lang('Time.weeks', [abs($weeks)]);
8✔
1036
            $before = $days < 0;
8✔
1037
        } elseif ($days !== 0) {
22✔
1038
            $before = $days < 0;
10✔
1039

1040
            // Yesterday/Tomorrow special cases
1041
            if (abs($days) === 1) {
10✔
1042
                return $before ? lang('Time.yesterday') : lang('Time.tomorrow');
4✔
1043
            }
1044

1045
            $phrase = lang('Time.days', [abs($days)]);
6✔
1046
        } elseif ($hours !== 0) {
12✔
1047
            $phrase = lang('Time.hours', [abs($hours)]);
4✔
1048
            $before = $hours < 0;
4✔
1049
        } elseif ($minutes !== 0) {
8✔
1050
            $phrase = lang('Time.minutes', [abs($minutes)]);
6✔
1051
            $before = $minutes < 0;
6✔
1052
        } else {
1053
            return lang('Time.now');
2✔
1054
        }
1055

1056
        return $before ? lang('Time.ago', [$phrase]) : lang('Time.inFuture', [$phrase]);
36✔
1057
    }
1058

1059
    /**
1060
     * @param DateTimeInterface|self|string $testTime
1061
     *
1062
     * @return TimeDifference
1063
     *
1064
     * @throws Exception
1065
     */
1066
    public function difference($testTime, ?string $timezone = null)
1067
    {
1068
        if (is_string($testTime)) {
37✔
1069
            $timezone = ($timezone !== null) ? new DateTimeZone($timezone) : $this->timezone;
24✔
1070
            $testTime = new DateTime($testTime, $timezone);
24✔
1071
        } elseif ($testTime instanceof static) {
13✔
1072
            $testTime = $testTime->toDateTime();
13✔
1073
        }
1074

1075
        assert($testTime instanceof DateTime);
1076

1077
        if ($this->timezone->getOffset($this) !== $testTime->getTimezone()->getOffset($this)) {
37✔
1078
            $testTime = $this->getUTCObject($testTime, $timezone);
×
1079
            $ourTime  = $this->getUTCObject($this);
×
1080
        } else {
1081
            $ourTime = $this->toDateTime();
37✔
1082
        }
1083

1084
        return new TimeDifference($ourTime, $testTime);
37✔
1085
    }
1086

1087
    // --------------------------------------------------------------------
1088
    // Utilities
1089
    // --------------------------------------------------------------------
1090

1091
    /**
1092
     * Returns a Time instance with the timezone converted to UTC.
1093
     *
1094
     * @param DateTimeInterface|self|string $time
1095
     *
1096
     * @return DateTime|static
1097
     *
1098
     * @throws Exception
1099
     */
1100
    public function getUTCObject($time, ?string $timezone = null)
1101
    {
1102
        if ($time instanceof static) {
23✔
1103
            $time = $time->toDateTime();
15✔
1104
        } elseif (is_string($time)) {
8✔
1105
            $timezone = $timezone !== null && $timezone !== '' && $timezone !== '0' ? $timezone : $this->timezone;
4✔
1106
            $timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
4✔
1107
            $time     = new DateTime($time, $timezone);
4✔
1108
        }
1109

1110
        if ($time instanceof DateTime || $time instanceof DateTimeImmutable) {
23✔
1111
            $time = $time->setTimezone(new DateTimeZone('UTC'));
23✔
1112
        }
1113

1114
        return $time;
23✔
1115
    }
1116

1117
    /**
1118
     * Returns the IntlCalendar object used for this object,
1119
     * taking into account the locale, date, etc.
1120
     *
1121
     * Primarily used internally to provide the difference and comparison functions,
1122
     * but available for public consumption if they need it.
1123
     *
1124
     * @return IntlCalendar
1125
     *
1126
     * @throws Exception
1127
     */
1128
    public function getCalendar()
1129
    {
1130
        return IntlCalendar::fromDateTime($this->toDateTime());
42✔
1131
    }
1132

1133
    /**
1134
     * Check a time string to see if it includes a relative date (like 'next Tuesday').
1135
     */
1136
    protected static function hasRelativeKeywords(string $time): bool
1137
    {
1138
        // skip common format with a '-' in it
1139
        if (preg_match('/\d{4}-\d{1,2}-\d{1,2}/', $time) !== 1) {
368✔
1140
            return preg_match(static::$relativePattern, $time) > 0;
250✔
1141
        }
1142

1143
        return false;
210✔
1144
    }
1145

1146
    /**
1147
     * Outputs a short format version of the datetime.
1148
     * The output is NOT localized intentionally.
1149
     */
1150
    public function __toString(): string
1151
    {
1152
        return $this->format('Y-m-d H:i:s');
18✔
1153
    }
1154

1155
    /**
1156
     * Allow for property-type access to any getX method...
1157
     *
1158
     * Note that we cannot use this for any of our setX methods,
1159
     * as they return new Time objects, but the __set ignores
1160
     * return values.
1161
     * See http://php.net/manual/en/language.oop5.overloading.php
1162
     *
1163
     * @param string $name
1164
     *
1165
     * @return array|bool|DateTimeInterface|DateTimeZone|int|IntlCalendar|self|string|null
1166
     */
1167
    public function __get($name)
1168
    {
1169
        $method = 'get' . ucfirst($name);
38✔
1170

1171
        if (method_exists($this, $method)) {
38✔
1172
            return $this->{$method}();
36✔
1173
        }
1174

1175
        return null;
2✔
1176
    }
1177

1178
    /**
1179
     * Allow for property-type checking to any getX method...
1180
     *
1181
     * @param string $name
1182
     */
1183
    public function __isset($name): bool
1184
    {
1185
        $method = 'get' . ucfirst($name);
4✔
1186

1187
        return method_exists($this, $method);
4✔
1188
    }
1189

1190
    /**
1191
     * This is called when we unserialize the Time object.
1192
     */
1193
    public function __wakeup(): void
1194
    {
1195
        /**
1196
         * Prior to unserialization, this is a string.
1197
         *
1198
         * @var string $timezone
1199
         */
1200
        $timezone = $this->timezone;
×
1201

1202
        $this->timezone = new DateTimeZone($timezone);
×
1203

1204
        // @phpstan-ignore-next-line `$this->date` is a special property for PHP internal use.
1205
        parent::__construct($this->date, $this->timezone);
×
1206
    }
1207
}
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