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

ICanBoogie / DateTime / 11645339926

02 Nov 2024 07:41PM UTC coverage: 97.034%. Remained the same
11645339926

push

github

olvlvl
Date::from() accepts DateTimeInterface

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

4 existing lines in 1 file now uncovered.

229 of 236 relevant lines covered (97.03%)

41.51 hits per line

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

97.1
/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
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 self|\DateTime|string $source
202
         * @param DateTimeZone|string|null $timezone The time zone to use to create the time.
203
         * The value is ignored if the source is an instance of {@see \DateTime}.
204
         *
205
         * @throws \DateInvalidTimeZoneException
206
         * @throws \DateMalformedStringException
207
         */
208
        static public function from(
209
                self|\DateTimeInterface|string $source,
210
                DateTimeZone|string|null $timezone = null
211
        ): static
212
        {
213
                if ($source instanceof static)
2✔
214
                {
215
                        return clone $source;
1✔
216
                }
217

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

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

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

240
                if (!$now)
89✔
241
                {
242
                        $now = empty($_SERVER['REQUEST_TIME'])
1✔
243
                                ? new static()
×
244
                                : (new static('@' . $_SERVER['REQUEST_TIME']))->local;
1✔
245
                }
246

247
                return clone $now;
89✔
248
        }
249

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

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

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

312
                parent::__construct($time, $timezone);
179✔
313
        }
314

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

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

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

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

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

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

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

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

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

439
                $time->setTime(0, 0, 0);
84✔
440

441
                return $time;
84✔
442
        }
443

444
        /**
445
         * Returns Tuesday of the week.
446
         *
447
         * @throws \DateMalformedStringException
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
         * @throws \DateMalformedStringException
458
         */
459
        private function get_wednesday(): self
460
        {
461
                return $this->monday->modify('+2 day');
14✔
462
        }
463

464
        /**
465
         * Returns Thursday of the week.
466
         *
467
         * @throws \DateMalformedStringException
468
         */
469
        private function get_thursday(): self
470
        {
471
                return $this->monday->modify('+3 day');
14✔
472
        }
473

474
        /**
475
         * Returns Friday of the week.
476
         *
477
         * @throws \DateMalformedStringException
478
         */
479
        private function get_friday(): self
480
        {
481
                return $this->monday->modify('+4 day');
14✔
482
        }
483

484
        /**
485
         * Returns Saturday of the week.
486
         *
487
         * @throws \DateMalformedStringException
488
         */
489
        private function get_saturday(): self
490
        {
491
                return $this->monday->modify('+5 day');
14✔
492
        }
493

494
        /**
495
         * Returns Sunday of the week.
496
         *
497
         * @throws \DateMalformedStringException
498
         */
499
        private function get_sunday(): self
500
        {
501
                $time = clone $this;
14✔
502
                $day = $time->weekday;
14✔
503

504
                if ($day != 7)
14✔
505
                {
506
                        $time->modify('+' . (7 - $day) . ' day');
12✔
507
                }
508

509
                $time->setTime(0, 0, 0);
14✔
510

511
                return $time;
14✔
512
        }
513

514
        private const READONLY_PROPERTIES = [
515
                'quarter', 'week', 'year_day', 'weekday',
516
                'tomorrow', 'yesterday', 'utc', 'local'
517
        ];
518

519
        /**
520
         * Sets the {@see $year}, {@see $month}, {@see $day}, {@see $hour}, {@see $minute},
521
         * {@see $second}, {@see $timestamp} and {@see $zone} properties.
522
         *
523
         * @throws \DateInvalidTimeZoneException
524
         */
525
        public function __set($property, $value): void
526
        {
527
                switch ($property)
528
                {
529
                        case 'year':
56✔
530
                        case 'month':
55✔
531
                        case 'day':
54✔
532
                        case 'hour':
53✔
533
                        case 'minute':
52✔
534
                        case 'second':
51✔
535
                                $this->change([ $property => $value ]);
7✔
536
                                return;
7✔
537

538
                        case 'timestamp':
49✔
539
                                $this->setTimestamp($value);
2✔
540
                                return;
2✔
541

542
                        case 'zone':
49✔
543
                                $this->setTimezone($value);
5✔
544
                                return;
5✔
545
                }
546

547
                if (str_starts_with($property, 'is_')
44✔
548
                        || str_starts_with($property, 'as_')
30✔
549
                        || in_array($property, self::READONLY_PROPERTIES)
15✔
550
                        || method_exists($this, 'get_' . $property)
44✔
551
                )
552
                {
553
                        throw new \LogicException(
44✔
554
                                sprintf("Readonly property: %s::%s", $this::class, $property)
44✔
555
                        );
44✔
556
                }
557

UNCOV
558
                throw new \LogicException(
×
UNCOV
559
                        sprintf("Undefined property: %s::%s", $this::class, $property),
×
560
                );
×
561
        }
562

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

581
                $as = strtoupper(substr($method, strlen('format_as_')));
159✔
582
                $format = constant(__CLASS__ . '::' . $as);
159✔
583
                $value = $this->format($format);
159✔
584

585
                return match ($as)
159✔
586
                {
159✔
587
                        'RFC822', 'RFC1123' => str_replace('+0000', 'GMT', $value),
8✔
588
                        'ISO8601' => str_replace('+0000', 'Z', $value),
6✔
589
                        default => $value,
159✔
590
                };
159✔
591
        }
592

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

604
        /**
605
         * Returns a {@see ISO8601} representation of the instance.
606
         */
607
        public function jsonSerialize(): string
608
        {
609
                return (string) $this;
1✔
610
        }
611

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

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

634
                return parent::setTimezone($timezone);
16✔
635
        }
636

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

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

674
                ];
17✔
675

676
                $options = array_intersect_key($options + $default_options, $default_options);
17✔
677

678
                $year = null;
17✔
679
                $month = null;
17✔
680
                $day = null;
17✔
681
                $hour = null;
17✔
682
                $minute = null;
17✔
683
                $second = null;
17✔
684
                $timezone = null;
17✔
685

686
                extract($options);
17✔
687

688
                if ($timezone !== null)
17✔
689
                {
690
                        $this->setTimezone($timezone);
1✔
691
                }
692

693
                if ($cascade)
17✔
694
                {
695
                        if ($hour !== null && $minute === null)
7✔
696
                        {
697
                                $minute = 0;
2✔
698
                        }
699

700
                        if ($minute !== null && $second === null)
7✔
701
                        {
702
                                $second = 0;
5✔
703
                        }
704
                }
705

706
                if ($year !== null || $month !== null || $day !== null)
17✔
707
                {
708
                        $this->setDate
5✔
709
                        (
5✔
710
                                $year === null ? $this->year : $year,
5✔
711
                                $month === null ? $this->month : $month,
5✔
712
                                $day === null ? $this->day : $day
5✔
713
                        );
5✔
714
                }
715

716
                if ($hour !== null || $minute !== null || $second !== null)
17✔
717
                {
718
                        $this->setTime
12✔
719
                        (
12✔
720
                                $hour === null ? $this->hour : $hour,
12✔
721
                                $minute === null ? $this->minute : $minute,
12✔
722
                                $second === null ? $this->second : $second
12✔
723
                        );
12✔
724
                }
725

726
                return $this;
17✔
727
        }
728

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

744
                return $dt->change($options, $cascade);
1✔
745
        }
746

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

763
                return parent::format($format);
206✔
764
        }
765

766
        /**
767
         * Returns a localized instance.
768
         *
769
         * @return mixed
770
         *
771
         * @throws \RuntimeException if {@see $localizer} is not defined.
772
         */
773
        public function localize(string $locale = 'en')
774
        {
775
                $localizer = self::$localizer;
1✔
776

777
                if (!$localizer)
1✔
778
                {
UNCOV
779
                        throw new \RuntimeException("Localizer is not defined yet.");
×
780
                }
781

782
                return $localizer($this, $locale);
1✔
783
        }
784
}
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