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

ICanBoogie / DateTime / 15263478573

26 May 2025 11:32PM UTC coverage: 96.624%. Remained the same
15263478573

push

github

olvlvl
Add static analysis

14 of 17 new or added lines in 2 files covered. (82.35%)

4 existing lines in 1 file now uncovered.

229 of 237 relevant lines covered (96.62%)

41.27 hits per line

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

97.09
/lib/DateTime.php
1
<?php
2

3
namespace ICanBoogie;
4

5
use DateTimeZone;
6

7
/**
8
 * Representation of a date and time.
9
 *
10
 * <pre>
11
 * <?php
12
 *
13
 * // Let's say that _now_ is 2013-02-03 21:03:45 in Paris
14
 *
15
 * use ICanBoogie\DateTime;
16
 *
17
 * date_default_timezone_set('EST'); // set local time zone to Eastern Standard Time
18
 *
19
 * $time = new DateTime('now', 'Europe/Paris');
20
 *
21
 * echo $time;                             // 2013-02-03T21:03:45+0100
22
 * echo $time->utc;                        // 2013-02-03T20:03:45Z
23
 * echo $time->local;                      // 2013-02-03T15:03:45-0500
24
 * echo $time->utc->local;                 // 2013-02-03T15:03:45-0500
25
 * echo $time->utc->is_utc;                // true
26
 * echo $time->utc->is_local;              // false
27
 * echo $time->local->is_utc;              // false
28
 * echo $time->local->is_local;            // true
29
 * echo $time->is_dst;                     // false
30
 *
31
 * echo $time->as_rss;                     // Sun, 03 Feb 2013 21:03:45 +0100
32
 * echo $time->as_db;                      // 2013-02-03 21:03:45
33
 *
34
 * echo $time->as_time;                    // 21:03:45
35
 * echo $time->utc->as_time;               // 20:03:45
36
 * echo $time->local->as_time;             // 15:03:45
37
 * echo $time->utc->local->as_time;        // 15:03:45
38
 *
39
 * echo $time->quarter;                    // 1
40
 * echo $time->week;                       // 5
41
 * echo $time->day;                        // 3
42
 * echo $time->minute;                     // 3
43
 * echo $time->is_monday;                  // false
44
 * echo $time->is_saturday;                // true
45
 * echo $time->is_today;                   // true
46
 * echo $time->tomorrow;                   // 2013-02-04T00:00:00+0100
47
 * echo $time->tomorrow->is_future         // true
48
 * echo $time->yesterday;                  // 2013-02-02T00:00:00+0100
49
 * echo $time->yesterday->is_past          // true
50
 * echo $time->monday;                     // 2013-01-28T00:00:00+0100
51
 * echo $time->sunday;                     // 2013-02-03T00:00:00+0100
52
 *
53
 * echo $time->timestamp;                  // 1359921825
54
 * echo $time;                             // 2013-02-03T21:03:45+0100
55
 * $time->timestamp += 3600 * 4;
56
 * echo $time;                             // 2013-02-04T01:03:45+0100
57
 *
58
 * echo $time->zone;                       // Europe/Paris
59
 * echo $time->zone->offset;               // 3600
60
 * echo $time->zone->location;             // FR,48.86667,2.33333
61
 * echo $time->zone->location->latitude;   // 48.86667
62
 * $time->zone = 'Asia/Tokyo';
63
 * echo $time;                             // 2013-02-04T09:03:45+0900
64
 *
65
 * $time->hour += 72;
66
 * echo "Rendez-vous in 72 hours: $time";  // Rendez-vous in 72 hours: 2013-02-07T05:03:45+0900
67
 * </pre>
68
 *
69
 * Empty dates are also supported:
70
 *
71
 * <pre>
72
 * <?php
73
 *
74
 * use ICanBoogie\DateTime;
75
 *
76
 * $time = new DateTime('0000-00-00', 'utc');
77
 * // or
78
 * $time = DateTime::none();
79
 *
80
 * echo $time->is_empty;                   // true
81
 * echo $time->as_date;                    // 0000-00-00
82
 * echo $time->as_db;                      // 0000-00-00 00:00:00
83
 * echo $time;                             // ""
84
 * </pre>
85
 *
86
 * @property int $timestamp Unix timestamp.
87
 * @property int $day Day of the month.
88
 * @property int $hour Hour of the day.
89
 * @property int $minute Minute of the hour.
90
 * @property int $month Month of the year.
91
 * @property-read int $quarter Quarter of the year.
92
 * @property int $second Second of the minute.
93
 * @property-read int $week Week of the year.
94
 * @property-read int $weekday Day of the week.
95
 * @property int $year Year.
96
 * @property-read int $year_day Day of the year.
97
 * @property-read bool $is_monday `true` if the instance represents Monday.
98
 * @property-read bool $is_tuesday `true` if the instance represents Tuesday.
99
 * @property-read bool $is_wednesday `true` if the instance represents Wednesday.
100
 * @property-read bool $is_thursday `true` if the instance represents Thursday.
101
 * @property-read bool $is_friday `true` if the instance represents Friday.
102
 * @property-read bool $is_saturday `true` if the instance represents Saturday.
103
 * @property-read bool $is_sunday `true` if the instance represents Sunday.
104
 * @property-read bool $is_today `true` if the instance is today.
105
 * @property-read bool $is_past `true` if the instance lies in the past.
106
 * @property-read bool $is_future `true` if the instance lies in the future.
107
 * @property-read bool $is_empty `true` if the instance represents an empty date such as "0000-00-00" or "0000-00-00 00:00:00".
108
 * @property-read DateTime $tomorrow A new instance representing the next day. Time is reset to 00:00:00.
109
 * @property-read DateTime $yesterday A new instance representing the previous day. Time is reset to 00:00:00.
110
 * @property-read DateTime $monday A new instance representing Monday of the week. Time is reset to 00:00:00.
111
 * @property-read DateTime $tuesday A new instance representing Tuesday of the week. Time is reset to 00:00:00.
112
 * @property-read DateTime $wednesday A new instance representing Wednesday of the week. Time is reset to 00:00:00.
113
 * @property-read DateTime $thursday A new instance representing Thursday of the week. Time is reset to 00:00:00.
114
 * @property-read DateTime $friday A new instance representing Friday of the week. Time is reset to 00:00:00.
115
 * @property-read DateTime $saturday A new instance representing Saturday of the week. Time is reset to 00:00:00.
116
 * @property-read DateTime $sunday A new instance representing Sunday of the week. Time is reset to 00:00:00.
117
 *
118
 * @property-read string $as_atom The instance formatted according to {@see ATOM}.
119
 * @property-read string $as_cookie The instance formatted according to {@see COOKIE}.
120
 * @property-read string $as_iso8601 The instance formatted according to {@see ISO8601}.
121
 * @property-read string $as_rfc822 The instance formatted according to {@see RFC822}.
122
 * @property-read string $as_rfc850 The instance formatted according to {@see RFC850}.
123
 * @property-read string $as_rfc1036 The instance formatted according to {@see RFC1036}.
124
 * @property-read string $as_rfc1123 The instance formatted according to {@see RFC1123}.
125
 * @property-read string $as_rfc2822 The instance formatted according to {@see RFC2822}.
126
 * @property-read string $as_rfc3339 The instance formatted according to {@see RFC3339}.
127
 * @property-read string $as_rss The instance formatted according to {@see RSS}.
128
 * @property-read string $as_w3c The instance formatted according to {@see W3C}.
129
 * @property-read string $as_db The instance formatted according to {@see DB}.
130
 * @property-read string $as_number The instance formatted according to {@see NUMBER}.
131
 * @property-read string $as_date The instance formatted according to {@see DATE}.
132
 * @property-read string $as_time The instance formatted according to {@see TIME}.
133
 *
134
 * @property TimeZone $zone The timezone of the instance.
135
 * @property-read DateTime $utc A new instance in the UTC timezone.
136
 * @property-read DateTime $local A new instance in the local timezone.
137
 * @property-read bool $is_utc `true` if the instance is in the UTC timezone.
138
 * @property-read bool $is_local `true` if the instance is in the local timezone.
139
 * @property-read bool $is_dst `true` if time occurs during Daylight Saving Time in its time zone.
140
 *
141
 * @method string format_as_atom() Formats the instance according to {@see ATOM}.
142
 * @method string format_as_cookie() Formats the instance according to {@see COOKIE}.
143
 * @method string format_as_iso8601() Formats the instance according to {@see ISO8601}.
144
 * @method string format_as_rfc822() Formats the instance according to {@see RFC822}.
145
 * @method string format_as_rfc850() Formats the instance according to {@see RFC850}.
146
 * @method string format_as_rfc1036() Formats the instance according to {@see RFC1036}.
147
 * @method string format_as_rfc1123() Formats the instance according to {@see RFC1123}.
148
 * @method string format_as_rfc2822() Formats the instance according to {@see RFC2822}.
149
 * @method string format_as_rfc3339() Formats the instance according to {@see RFC3339}.
150
 * @method string format_as_rss() Formats the instance according to {@see RSS}.
151
 * @method string format_as_w3c() Formats the instance according to {@see W3C}.
152
 * @method string format_as_db() Formats the instance according to {@see DB}.
153
 * @method string format_as_number() Formats the instance according to {@see NUMBER}.
154
 * @method string format_as_date() Formats the instance according to {@see DATE}.
155
 * @method string format_as_time() Formats the instance according to {@see TIME}.
156
 *
157
 * @link http://en.wikipedia.org/wiki/ISO_8601
158
 */
159
class DateTime extends \DateTime implements \JsonSerializable
160
{
161
        /**
162
         * DB (example: 2013-02-03 20:59:03)
163
         */
164
        public const DB = 'Y-m-d H:i:s';
165

166
        /**
167
         * Number (example: 20130203205903)
168
         */
169
        public const NUMBER = 'YmdHis';
170

171
        /**
172
         * Date (example: 2013-02-03)
173
         */
174
        public const DATE = 'Y-m-d';
175

176
        /**
177
         * Time (example: 20:59:03)
178
         */
179
        public const TIME = 'H:i:s';
180

181
        /**
182
         * Callable used to create localized instances.
183
         *
184
         * @var callable|null
185
         */
186
        static public $localizer = null;
187

188
        /**
189
         * Creates a {@see DateTime} instance from a source.
190
         *
191
         * <pre>
192
         * <?php
193
         *
194
         * use ICanBoogie\DateTime;
195
         *
196
         * DateTime::from(new \DateTime('2001-01-01 01:01:01', new \DateTimeZone('Europe/Paris')));
197
         * DateTime::from('2001-01-01 01:01:01', 'Europe/Paris');
198
         * DateTime::from('now');
199
         * </pre>
200
         *
201
         * @param DateTimeZone|string|null $timezone The time zone to use to create the time.
202
         * The value is ignored if the source is an instance of {@see \DateTime}.
203
         *
204
         * @throws \DateInvalidTimeZoneException
205
         * @throws \DateMalformedStringException
206
         */
207
        static public function from(
208
                self|\DateTimeInterface|string $source,
209
                DateTimeZone|string|null $timezone = null
210
        ): static
211
        {
212
                if ($source instanceof static)
2✔
213
                {
214
                        return clone $source;
1✔
215
                }
216

217
                if ($source instanceof \DateTimeInterface)
2✔
218
                {
219
                        return new static($source->format('Y-m-d\TH:i:s.u'), $source->getTimezone());
2✔
220
                }
221

222
                return new static($source, $timezone);
1✔
223
        }
224

225
        /**
226
         * Returns an instance with the current local time and the local time zone.
227
         *
228
         * **Note:** Subsequent calls return equal times, event if they're minutes apart. _now_
229
         * actually refers to the `REQUEST_TIME` or, if it is now available, to the first time
230
         * the method was invoked.
231
         *
232
         * @throws \DateInvalidTimeZoneException
233
         * @throws \DateMalformedStringException
234
         */
235
        static public function now(): static
236
        {
237
                static $now;
89✔
238

239
                if (!$now)
89✔
240
                {
241
                        /** @var int|null $time */
242
                        $time = $_SERVER['REQUEST_TIME'] ?? null;
1✔
243

244
                        $now = $time
1✔
245
                                ? (new static("@$time"))->local
1✔
NEW
246
                                : new static();
×
247
                }
248

249
                return clone $now;
89✔
250
        }
251

252
        /**
253
         * Returns an instance with the current local time and the local time zone.
254
         *
255
         * **Note:** Subsequent calls may return different times.
256
         */
257
        static public function right_now(): static
258
        {
259
                return new static();
1✔
260
        }
261

262
        /**
263
         * Returns an instance representing an empty date ("0000-00-00").
264
         *
265
         * <pre>
266
         * <?php
267
         *
268
         * use ICanBoogie\DateTime;
269
         *
270
         * $d = DateTime::none();
271
         * $d->is_empty;                      // true
272
         * $d->zone->name;                    // "UTC"
273
         *
274
         * $d = DateTime::none('Asia/Tokyo');
275
         * $d->is_empty;                      // true
276
         * $d->zone->name;                    // "Asia/Tokio"
277
         * </pre>
278
         *
279
         * @param DateTimeZone|string $timezone The time zone in which the empty date is created.
280
         * Defaults to "UTC".
281
         *
282
         * @throws \DateInvalidTimeZoneException
283
         * @throws \DateMalformedStringException
284
         */
285
        static public function none(DateTimeZone|string $timezone = 'utc'): static
286
        {
287
                return new static('0000-00-00', $timezone);
7✔
288
        }
289

290
        /**
291
         * If the time zone is specified as a string a {@see \DateTimeZone} instance is created and
292
         * used instead.
293
         *
294
         * <pre>
295
         * <?php
296
         *
297
         * use ICanBoogie\DateTime;
298
         *
299
         * new DateTime('2001-01-01 01:01:01', new \DateTimeZone('Europe/Paris')));
300
         * new DateTime('2001-01-01 01:01:01', 'Europe/Paris');
301
         * new DateTime;
302
         * </pre>
303
         *
304
         * @throws \DateInvalidTimeZoneException
305
         * @throws \DateMalformedStringException
306
         */
307
        public function __construct(string $time = 'now', DateTimeZone|string|null $timezone = null)
308
        {
309
                if (is_string($timezone))
179✔
310
                {
311
                        $timezone = new DateTimeZone($timezone);
19✔
312
                }
313

314
                parent::__construct($time, $timezone);
179✔
315
        }
316

317
        public function __get(string $property): mixed
318
        {
319
                if (str_starts_with($property, 'as_'))
199✔
320
                {
321
                        return $this->{ 'format_' . $property }();
141✔
322
                }
323

324
                switch ($property)
325
                {
326
                        case 'timestamp':
186✔
327
                                return $this->getTimestamp();
3✔
328

329
                        case 'year':
186✔
330
                                return (int) $this->format('Y');
134✔
331
                        case 'quarter':
183✔
332
                                return floor(($this->month - 1) / 3) + 1;
12✔
333
                        case 'month':
183✔
334
                                return (int) $this->format('m');
26✔
335
                        case 'week':
168✔
336
                                return (int) $this->format('W');
2✔
337
                        case 'year_day':
166✔
338
                                return (int) $this->format('z') + 1;
2✔
339
                        case 'weekday':
164✔
340
                                return (int) $this->format('w') ?: 7;
112✔
341
                        case 'day':
157✔
342
                                return (int) $this->format('d');
14✔
343
                        case 'hour':
154✔
344
                                return (int) $this->format('H');
9✔
345
                        case 'minute':
153✔
346
                                return (int) $this->format('i');
6✔
347
                        case 'second':
152✔
348
                                return (int) $this->format('s');
6✔
349
                        case 'is_monday':
151✔
350
                                return $this->weekday == 1;
7✔
351
                        case 'is_tuesday':
151✔
352
                                return $this->weekday == 2;
7✔
353
                        case 'is_wednesday':
151✔
354
                                return $this->weekday == 3;
7✔
355
                        case 'is_thursday':
151✔
356
                                return $this->weekday == 4;
7✔
357
                        case 'is_friday':
151✔
358
                                return $this->weekday == 5;
7✔
359
                        case 'is_saturday':
151✔
360
                                return $this->weekday == 6;
7✔
361
                        case 'is_sunday':
151✔
362
                                return $this->weekday == 7;
7✔
363
                        case 'is_today':
144✔
364
                                $now = new static('now', $this->zone);
1✔
365
                                return $this->as_date === $now->as_date;
1✔
366
                        case 'is_past':
144✔
367
                                return $this < new static('now', $this->zone);
1✔
368
                        case 'is_future':
144✔
369
                                return $this > new static('now', $this->zone);
1✔
370
                        case 'is_empty':
144✔
371
                                return $this->year == -1 && $this->month == 11 && $this->day == 30;
130✔
372
                        case 'tomorrow':
120✔
373
                                $time = clone $this;
3✔
374
                                $time->modify('+1 day');
3✔
375
                                $time->setTime(0, 0, 0);
3✔
376
                                return $time;
3✔
377
                        case 'yesterday':
119✔
378
                                $time = clone $this;
3✔
379
                                $time->modify('-1 day');
3✔
380
                                $time->setTime(0, 0, 0);
3✔
381
                                return $time;
3✔
382

383
                        /**
384
                         * days
385
                         *
386
                         * @uses get_monday
387
                         * @uses get_tuesday
388
                         * @uses get_wednesday
389
                         * @uses get_thursday
390
                         * @uses get_friday
391
                         * @uses get_saturday
392
                         * @uses get_sunday
393
                         */
394
                        case 'monday':
117✔
395
                        case 'tuesday':
103✔
396
                        case 'wednesday':
89✔
397
                        case 'thursday':
75✔
398
                        case 'friday':
61✔
399
                        case 'saturday':
47✔
400
                        case 'sunday':
33✔
401

402
                                return $this->{ 'get_' . $property }();
98✔
403

404
                        case 'zone':
19✔
405
                                return TimeZone::from($this->getTimezone());
11✔
406
                        case 'utc':
13✔
407
                        case 'local':
6✔
408
                                $time = clone $this;
9✔
409
                                $time->setTimezone($property);
9✔
410
                                return $time;
9✔
411
                        case 'is_utc':
4✔
412
                                return $this->zone->name == 'UTC';
1✔
413
                        case 'is_local':
3✔
414
                                return $this->zone->name == date_default_timezone_get();
1✔
415
                        case 'is_dst':
2✔
416
                                $timestamp = $this->timestamp;
1✔
417
                                $transitions = $this->zone->getTransitions($timestamp, $timestamp);
1✔
418
                                return $transitions[0]['isdst'];
1✔
419
                }
420

421
                throw new \LogicException(
1✔
422
                        sprintf("Undefined property: %s::%s", $this::class, $property)
1✔
423
                );
1✔
424
        }
425

426
        /**
427
         * Returns Monday of the week.
428
         *
429
         * @throws \DateMalformedStringException
430
         */
431
        private function get_monday(): self
432
        {
433
                $time = clone $this;
84✔
434
                $day = $time->weekday;
84✔
435

436
                if ($day != 1)
84✔
437
                {
438
                        $time->modify('-' . ($day - 1) . ' day');
72✔
439
                }
440

441
                $time->setTime(0, 0, 0);
84✔
442

443
                return $time;
84✔
444
        }
445

446
        /**
447
         * Returns Tuesday of the week.
448
         */
449
        private function get_tuesday(): self
450
        {
451
                return $this->monday->modify('+1 day');
14✔
452
        }
453

454
        /**
455
         * Returns Wednesday of the week.
456
         */
457
        private function get_wednesday(): self
458
        {
459
                return $this->monday->modify('+2 day');
14✔
460
        }
461

462
        /**
463
         * Returns Thursday of the week.
464
         */
465
        private function get_thursday(): self
466
        {
467
                return $this->monday->modify('+3 day');
14✔
468
        }
469

470
        /**
471
         * Returns Friday of the week.
472
         */
473
        private function get_friday(): self
474
        {
475
                return $this->monday->modify('+4 day');
14✔
476
        }
477

478
        /**
479
         * Returns Saturday of the week.
480
         */
481
        private function get_saturday(): self
482
        {
483
                return $this->monday->modify('+5 day');
14✔
484
        }
485

486
        /**
487
         * Returns Sunday of the week.
488
         */
489
        private function get_sunday(): self
490
        {
491
                $time = clone $this;
14✔
492
                $day = $time->weekday;
14✔
493

494
                if ($day != 7)
14✔
495
                {
496
                        $time->modify('+' . (7 - $day) . ' day');
12✔
497
                }
498

499
                $time->setTime(0, 0, 0);
14✔
500

501
                return $time;
14✔
502
        }
503

504
        private const READONLY_PROPERTIES = [
505
                'quarter', 'week', 'year_day', 'weekday',
506
                'tomorrow', 'yesterday', 'utc', 'local'
507
        ];
508

509
        /**
510
         * Sets the {@see $year}, {@see $month}, {@see $day}, {@see $hour}, {@see $minute},
511
         * {@see $second}, {@see $timestamp} and {@see $zone} properties.
512
         *
513
         * @throws \DateInvalidTimeZoneException
514
         */
515
        public function __set(string $property, mixed $value): void
516
        {
517
                switch ($property)
518
                {
519
                        case 'year':
56✔
520
                        case 'month':
55✔
521
                        case 'day':
54✔
522
                        case 'hour':
53✔
523
                        case 'minute':
52✔
524
                        case 'second':
51✔
525
                                /** @phpstan-ignore-next-line */
526
                                $this->change([ $property => $value ]);
7✔
527
                                return;
7✔
528

529
                        case 'timestamp':
49✔
530
                                /** @phpstan-ignore-next-line */
531
                                $this->setTimestamp($value);
2✔
532
                                return;
2✔
533

534
                        case 'zone':
49✔
535
                                /** @phpstan-ignore-next-line */
536
                                $this->setTimezone($value);
5✔
537
                                return;
5✔
538
                }
539

540
                if (str_starts_with($property, 'is_')
44✔
541
                        || str_starts_with($property, 'as_')
30✔
542
                        || in_array($property, self::READONLY_PROPERTIES)
15✔
543
                        || method_exists($this, 'get_' . $property)
44✔
544
                )
545
                {
546
                        throw new \LogicException(
44✔
547
                                sprintf("Readonly property: %s::%s", $this::class, $property)
44✔
548
                        );
44✔
549
                }
550

UNCOV
551
                throw new \LogicException(
×
UNCOV
552
                        sprintf("Undefined property: %s::%s", $this::class, $property),
×
UNCOV
553
                );
×
554
        }
555

556
        /**
557
         * Handles the `format_as_*` methods.
558
         *
559
         * If the format is {@see RFC822} or {@see RFC1123} and the time zone is equivalent to GMT,
560
         * the offset `+0000` is replaced by `GMT` according to the specs.
561
         *
562
         * If the format is {@see ISO8601} and the time zone is equivalent to UTC, the offset `+0000`
563
         * is replaced by `Z` according to the specs.
564
         *
565
         * @throws \BadMethodCallException in attempt to call an unsupported method.
566
         *
567
         * @phpstan-ignore-next-line
568
         */
569
        public function __call($method, $arguments)
570
        {
571
                if (!str_starts_with($method, 'format_as_'))
159✔
572
                {
UNCOV
573
                        throw new \BadMethodCallException("Unsupported method: $method.");
×
574
                }
575

576
                $as = strtoupper(substr($method, strlen('format_as_')));
159✔
577
                $format = constant(__CLASS__ . '::' . $as);
159✔
578
                assert(is_string($format));
579
                $value = $this->format($format);
159✔
580

581
                return match ($as)
159✔
582
                {
159✔
583
                        'RFC822', 'RFC1123' => str_replace('+0000', 'GMT', $value),
8✔
584
                        'ISO8601' => str_replace('+0000', 'Z', $value),
6✔
585
                        default => $value,
159✔
586
                };
159✔
587
        }
588

589
        /**
590
         * Returns the datetime formatted as {@see ISO8601}.
591
         *
592
         * @return string The instance rendered as an {@see ISO8601} string, or an empty string if the
593
         * datetime is empty.
594
         */
595
        public function __toString(): string
596
        {
597
                return $this->is_empty ? "" : $this->as_iso8601;
3✔
598
        }
599

600
        /**
601
         * Returns a {@see ISO8601} representation of the instance.
602
         */
603
        public function jsonSerialize(): string
604
        {
605
                return (string) $this;
1✔
606
        }
607

608
        /**
609
         * @inheritdoc
610
         *
611
         * The timezone can be specified as a string.
612
         *
613
         * If the timezone is `local` the timezone returned by {@see date_default_timezone_get()} is
614
         * used instead.
615
         *
616
         * @param DateTimeZone|string $timezone
617
         *
618
         * @throws \DateInvalidTimeZoneException
619
         */
620
        public function setTimezone($timezone): self
621
        {
622
                if ($timezone === 'local')
16✔
623
                {
624
                        $timezone = date_default_timezone_get();
2✔
625
                }
626

627
                if (!$timezone instanceof DateTimeZone)
16✔
628
                {
629
                        $timezone = new DateTimeZone($timezone);
15✔
630
                }
631

632
                return parent::setTimezone($timezone);
16✔
633
        }
634

635
        /**
636
         * Modifies the properties of the instance according to the options.
637
         *
638
         * The following properties can be updated: {@see $year}, {@see $month}, {@see $day},
639
         * {@see $hour}, {@see $minute} and {@see $second}.
640
         *
641
         * Note: Values exceeding ranges are added to their parent values.
642
         *
643
         * <pre>
644
         * <?php
645
         *
646
         * use ICanBoogie\DateTime;
647
         *
648
         * $time = new DateTime('now');
649
         * $time->change([ 'year' => 2000, 'second' => 0 ]);
650
         * </pre>
651
         *
652
         * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int,
653
         *     timezone: string } $options
654
         * @param bool $cascade If `true`, time options (`hour`, `minute`, `second`) reset
655
         * cascading, so if only the hour is passed, then minute and second are set to 0. If the hour
656
         * and minute are passed, the second is set to 0.
657
         *
658
         * @throws \DateInvalidTimeZoneException
659
         */
660
        public function change(array $options, bool $cascade = false): self
661
        {
662
                static $default_options = [
17✔
663

664
                        'year' => null,
17✔
665
                        'month' => null,
17✔
666
                        'day' => null,
17✔
667
                        'hour' => null,
17✔
668
                        'minute' => null,
17✔
669
                        'second' => null,
17✔
670
                        'timezone' => null,
17✔
671

672
                ];
17✔
673

674
                $options = array_intersect_key($options + $default_options, $default_options);
17✔
675

676
                $year = $options['year'] ?? null;
17✔
677
                $month = $options['month'] ?? null;
17✔
678
                $day = $options['day'] ?? null;
17✔
679
                $hour = $options['hour'] ?? null;
17✔
680
                $minute = $options['minute'] ?? null;
17✔
681
                $second = $options['second'] ?? null;
17✔
682
                $timezone = $options['timezone'] ?? null;
17✔
683

684
                if ($timezone !== null)
17✔
685
                {
686
                        $this->setTimezone($timezone);
1✔
687
                }
688

689
                if ($cascade)
17✔
690
                {
691
                        if ($hour !== null && $minute === null)
7✔
692
                        {
693
                                $minute = 0;
2✔
694
                        }
695

696
                        if ($minute !== null && $second === null)
7✔
697
                        {
698
                                $second = 0;
5✔
699
                        }
700
                }
701

702
                if ($year !== null || $month !== null || $day !== null)
17✔
703
                {
704
                        $this->setDate
5✔
705
                        (
5✔
706
                                $year === null ? $this->year : $year,
5✔
707
                                $month === null ? $this->month : $month,
5✔
708
                                $day === null ? $this->day : $day
5✔
709
                        );
5✔
710
                }
711

712
                if ($hour !== null || $minute !== null || $second !== null)
17✔
713
                {
714
                        $this->setTime
12✔
715
                        (
12✔
716
                                $hour === null ? $this->hour : $hour,
12✔
717
                                $minute === null ? $this->minute : $minute,
12✔
718
                                $second === null ? $this->second : $second
12✔
719
                        );
12✔
720
                }
721

722
                return $this;
17✔
723
        }
724

725
        /**
726
         * Instantiate a new instance with changes properties.
727
         *
728
         * @param array{ year: int, month: int, day: int, hour: int, minute: int, second: int,
729
         *     timezone: string } $options
730
         * @param bool $cascade If `true`, time options (`hour`, `minute`, `second`) reset
731
         * cascading, so if only the hour is passed, then minute and second are set to 0. If the hour
732
         * and minute are passed, the second is set to 0.
733
         *
734
         * @throws \DateInvalidTimeZoneException
735
         */
736
        public function with(array $options, bool $cascade = false): self
737
        {
738
                $dt = clone $this;
1✔
739

740
                return $dt->change($options, $cascade);
1✔
741
        }
742

743
        /**
744
         * If the instance represents an empty date and the format is {@see DATE} or {@see DB},
745
         * an empty date is returned, respectively "0000-00-00" and "0000-00-00 00:00:00". Note that
746
         * the time information is discarded for {@see DB}. This only applies to {@see DATE} and
747
         * {@see DB} formats. For instance {@see RSS} will return the following string:
748
         * "Wed, 30 Nov -0001 00:00:00 +0000".
749
         *
750
         * @inheritdoc
751
         */
752
        public function format($format): string
753
        {
754
                if (($format == self::DATE || $format == self::DB) && $this->is_empty)
206✔
755
                {
756
                        return $format == self::DATE ? '0000-00-00' : '0000-00-00 00:00:00';
5✔
757
                }
758

759
                return parent::format($format);
206✔
760
        }
761

762
        /**
763
         * Returns a localized instance.
764
         *
765
         * @return mixed
766
         *
767
         * @throws \RuntimeException if {@see $localizer} is not defined.
768
         */
769
        public function localize(string $locale = 'en')
770
        {
771
                $localizer = self::$localizer
1✔
NEW
772
                        ?? throw new \RuntimeException("Localizer is not defined yet.");
×
773

774
                return $localizer($this, $locale);
1✔
775
        }
776
}
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

© 2025 Coveralls, Inc