PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / vendor / wpfluent / framework / src / WPFluent / Support / DateTimeImmutable.php

DateTimeImmutable.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at vendor/wpfluent/framework/src/WPFluent/Support/DateTimeImmutable.php

882 lines 22.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Framework\Support;
4
5 use Exception;
6 use DateTimeZone;
7 use DateInterval;
8 use DateTimeInterface;
9 use InvalidArgumentException;
10 use DateTimeImmutable as PHPDateTimeImmutable;
11
12 /**
13 * @mixin \DateTimeImmutable
14 * @method self addYear(int $value = 1)
15 * @method self addYears(int $value = 1)
16 * @method self addMonth(int $value = 1)
17 * @method self addMonths(int $value = 1)
18 * @method self addWeek(int $value = 1)
19 * @method self addWeeks(int $value = 1)
20 * @method self addDay(int $value = 1)
21 * @method self addDays(int $value = 1)
22 * @method self addHour(int $value = 1)
23 * @method self addHours(int $value = 1)
24 * @method self addMinute(int $value = 1)
25 * @method self addMinutes(int $value = 1)
26 * @method self addSecond(int $value = 1)
27 * @method self addSeconds(int $value = 1)
28 * @method self addQuarter(int $value = 1)
29 * @method self addQuarters(int $value = 1)
30 * @method self addDecade(int $value = 1)
31 * @method self addDecades(int $value = 1)
32 *
33 * @method self subYear(int $value = 1)
34 * @method self subYears(int $value = 1)
35 * @method self subMonth(int $value = 1)
36 * @method self subMonths(int $value = 1)
37 * @method self subWeek(int $value = 1)
38 * @method self subWeeks(int $value = 1)
39 * @method self subDay(int $value = 1)
40 * @method self subDays(int $value = 1)
41 * @method self subHour(int $value = 1)
42 * @method self subHours(int $value = 1)
43 * @method self subMinute(int $value = 1)
44 * @method self subMinutes(int $value = 1)
45 * @method self subSecond(int $value = 1)
46 * @method self subSeconds(int $value = 1)
47 * @method self subQuarter(int $value = 1)
48 * @method self subQuarters(int $value = 1)
49 * @method self subDecade(int $value = 1)
50 * @method self subDecades(int $value = 1)
51 *
52 * @method int getYear()
53 * @method int getMonth()
54 * @method int getDay()
55 * @method int getHour()
56 * @method int getMinute()
57 * @method int getSecond()
58 * @method int getQuarter()
59 * @method int getDecade()
60 *
61 * @method self setYear(int $value)
62 * @method self setMonth(int $value)
63 * @method self setDay(int $value)
64 * @method self setHour(int $value)
65 * @method self setMinute(int $value)
66 * @method self setSecond(int $value)
67 */
68 class DateTimeImmutable extends PHPDateTimeImmutable
69 {
70 /** @var string[] Singular time units */
71 protected const SINGULAR_UNITS = [
72 'year', 'month', 'week', 'day', 'hour',
73 'minute', 'second', 'quarter', 'decade'
74 ];
75
76 /** @var string[] Plural time units */
77 protected const PLURAL_UNITS = [
78 'years', 'months', 'weeks', 'days', 'hours',
79 'minutes', 'seconds', 'quarters', 'decades'
80 ];
81
82 /**
83 * Construct the object.
84 *
85 * @param string|int|DateTimeInterface $datetime
86 * @param DateTimeZone|string|null $timezone
87 */
88 public function __construct($datetime = 'now', $timezone = null)
89 {
90 if (is_string($timezone)) {
91 $timezone = new DateTimeZone($timezone);
92 }
93
94 $timezone ??= static::getDefaultTimezone();
95
96 if ($datetime instanceof DateTimeInterface) {
97 $datetime = $datetime->format('Y-m-d H:i:s.u');
98 } elseif (
99 is_numeric($datetime)
100 || str_starts_with((string) $datetime, '@')
101 ) {
102 $datetime = '@' . ltrim((string) $datetime, '@');
103 }
104
105 parent::__construct($datetime, $timezone);
106 }
107
108 /**
109 * Gets the WordPress default timezone.
110 *
111 * @return DateTimeZone
112 */
113 public static function getDefaultTimezone()
114 {
115 $tz = wp_timezone();
116
117 return $tz instanceof DateTimeZone ? $tz : new DateTimeZone($tz);
118 }
119
120 /**
121 * Creates a DateTime object from various inputs.
122 *
123 * @param string|int|DateTimeInterface $time
124 * @param string|DateTimeZone|null $tz
125 *
126 * @return static
127 * @throws \InvalidArgumentException
128 */
129 public static function create($time = null, $tz = null)
130 {
131 if (is_null($tz)) {
132 $timezone = (new static)->getDefaultTimezone();
133 } else {
134 $timezone = is_string($tz) ? new DateTimeZone($tz) : $tz;
135 if (!$timezone instanceof DateTimeZone) {
136 throw new InvalidArgumentException('Invalid timezone.');
137 }
138 }
139
140 // Handle DateTimeInterface directly
141 if ($time instanceof DateTimeInterface) {
142 $dateTime = new static(
143 $time->format('Y-m-d H:i:s.u'), $time->getTimezone()
144 );
145
146 // Override timezone if explicitly provided
147 if ($tz !== null) {
148 $dateTime = $dateTime->setTimezone($timezone);
149 }
150
151 return $dateTime;
152 }
153
154 // Handle numeric timestamps
155 if (is_numeric($time)) {
156 // Treat small numbers as offsets from now (optional)
157 if ($time <= 31556952) { // ~seconds in a year
158 $time += time();
159 }
160
161 // create from timestamp
162 $dateTime = new static('@' . $time);
163 return $dateTime->setTimezone($timezone);
164 }
165
166 // Fallback: treat as string or null ('now')
167 $dateTime = new static($time ?: 'now', $timezone);
168
169 return $dateTime;
170 }
171
172 /**
173 * Creates a new instance for the current time.
174 *
175 * @param string|DateTimeZone|null $tz
176 * @return static
177 */
178 public static function now($tz = null)
179 {
180 return static::create('now', $tz);
181 }
182
183 /**
184 * Creates a new instance for the beginning of today.
185 *
186 * @param string|DateTimeZone|null $tz
187 * @return static
188 */
189 public static function today($tz = null)
190 {
191 return static::create('today', $tz)->startOfDay();
192 }
193
194 /**
195 * Creates a new instance for the beginning of yesterday.
196 *
197 * @param string|DateTimeZone|null $tz
198 * @return static
199 */
200 public static function yesterday($tz = null)
201 {
202 return static::now($tz)->modify('-1 day')->startOfDay();
203 }
204
205 /**
206 * Creates a new instance for the beginning of tomorrow.
207 *
208 * @param string|DateTimeZone|null $tz
209 * @return static
210 */
211 public static function tomorrow($tz = null)
212 {
213 return static::now($tz)->modify('+1 day')->startOfDay();
214 }
215
216 /**
217 * Creates a new instance for the beginning of the current week.
218 *
219 * @param string|DateTimeZone|null $tz
220 * @return static
221 */
222 public static function currentWeek($tz = null)
223 {
224 return static::now($tz)->startOfWeek();
225 }
226
227 /**
228 * Creates a new instance for the beginning of last week.
229 *
230 * @param string|DateTimeZone|null $tz
231 * @return static
232 */
233 public static function lastWeek($tz = null)
234 {
235 return static::now($tz)->subWeek()->startOfWeek();
236 }
237
238 /**
239 * Creates a new instance for the beginning of next week.
240 *
241 * @param string|DateTimeZone|null $tz
242 * @return static
243 */
244 public static function nextWeek($tz = null)
245 {
246 return static::now($tz)->addWeek()->startOfWeek();
247 }
248
249 /**
250 * Creates a new instance for the beginning of the current month.
251 *
252 * @param string|DateTimeZone|null $tz
253 * @return static
254 */
255 public static function currentMonth($tz = null)
256 {
257 return static::now($tz)->startOfMonth();
258 }
259
260 /**
261 * Creates a new instance for the beginning of last month.
262 *
263 * @param string|DateTimeZone|null $tz
264 * @return static
265 */
266 public static function lastMonth($tz = null)
267 {
268 return static::now($tz)->subMonth()->startOfMonth();
269 }
270
271 /**
272 * Creates a new instance for the beginning of next month.
273 *
274 * @param string|DateTimeZone|null $tz
275 * @return static
276 */
277 public static function nextMonth($tz = null)
278 {
279 return static::now($tz)->addMonth()->startOfMonth();
280 }
281
282 /**
283 * Creates a new instance for the beginning of the current year.
284 *
285 * @param string|DateTimeZone|null $tz
286 * @return static
287 */
288 public static function currentYear($tz = null)
289 {
290 return static::now($tz)->startOfYear();
291 }
292
293 /**
294 * Creates a new instance for the beginning of last year.
295 *
296 * @param string|DateTimeZone|null $tz
297 * @return static
298 */
299 public static function lastYear($tz = null)
300 {
301 return static::now($tz)->subYear()->startOfYear();
302 }
303
304 /**
305 * Creates a new instance for the beginning of next year.
306 *
307 * @param string|DateTimeZone|null $tz
308 * @return static
309 */
310 public static function nextYear($tz = null)
311 {
312 return static::now($tz)->addYear()->startOfYear();
313 }
314
315 /**
316 * Sets the timezone for the instance.
317 *
318 * @param string|DateTimeZone $tz
319 * @return self
320 */
321 public function timezone($tz)
322 {
323 if (is_string($tz)) {
324 $tz = new DateTimeZone($tz);
325 }
326 return $this->setTimezone($tz);
327 }
328
329 /**
330 * Checks if the current instance is between two dates.
331 *
332 * @param string|DateTimeInterface $date1
333 * @param string|DateTimeInterface $date2
334 * @return bool
335 */
336 public function between($date1, $date2)
337 {
338 $date1 = $date1 instanceof DateTimeInterface ? $date1 : new static($date1);
339 $date2 = $date2 instanceof DateTimeInterface ? $date2 : new static($date2);
340
341 if ($date1 > $date2) {
342 [$date1, $date2] = [$date2, $date1];
343 }
344
345 return ($this >= $date1 && $this <= $date2);
346 }
347
348 /**
349 * Creates a new instance from a formatted string.
350 *
351 * @param string $format
352 * @param string $datetimeString
353 * @param string|DateTimeZone|null $timezone
354 *
355 * @return static
356 * @throws \InvalidArgumentException
357 */
358 public static function createFromFormat(
359 $format,
360 $datetimeString,
361 $timezone = null
362 )
363 {
364 if (is_null($timezone)) {
365 $timezone = (new static)->getDefaultTimezone();
366 } else {
367 $timezone = is_string($timezone)
368 ? new DateTimeZone($timezone)
369 : $timezone;
370 }
371
372 if (!$timezone instanceof DateTimeZone) {
373 throw new InvalidArgumentException('Invalid timezone.');
374 }
375
376 $dateTime = PHPDateTimeImmutable::createFromFormat(
377 $format, $datetimeString
378 );
379
380 if ($dateTime !== false) {
381
382 if (!$dateTime instanceof static) {
383 return new static(
384 $dateTime->format(ltrim($format, '!')), $timezone
385 );
386 }
387
388 $dateTime->setTimezone($timezone);
389
390 return $dateTime;
391 }
392
393 throw new InvalidArgumentException(
394 "Unable to create datetime from: {$datetimeString}."
395 );
396 }
397
398 /**
399 * Creates a new instance from date parts.
400 *
401 * @param int $year
402 * @param int $month
403 * @param int $day
404 * @param int $hour
405 * @param int $minute
406 * @param int $second
407 * @param string|DateTimeZone|null $tz
408 *
409 * @return static
410 * @throws \InvalidArgumentException
411 */
412 public static function createFromDate(
413 int $year,
414 int $month,
415 int $day,
416 int $hour = 0,
417 int $minute = 0,
418 int $second = 0,
419 $tz = null
420 ) {
421 if (
422 !checkdate($month, $day, $year)
423 || $hour < 0 || $hour > 23
424 || $minute < 0 || $minute > 59
425 || $second < 0 || $second > 59
426 ) {
427 throw new InvalidArgumentException(
428 sprintf("Invalid date '%04d-%02d-%02d %02d:%02d:%02d'", $year, $month, $day, $hour, $minute, $second)
429 );
430 }
431
432 return new static(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $year, $month, $day, $hour, $minute, $second), $tz);
433 }
434
435 /**
436 * Creates a new instance from a UTC date string.
437 *
438 * @param string $dateString
439 * @param string $format
440 * @return static
441 */
442 public static function createFromUTC(
443 string $dateString,
444 string $format = 'Y-m-d H:i:s'
445 )
446 {
447 $gmtDate = get_date_from_gmt($dateString, $format);
448 return new static($gmtDate, static::getDefaultTimezone());
449 }
450
451 /**
452 * Parses a datetime string and returns a new instance.
453 *
454 * @param string $datetimeString
455 * @param string|DateTimeZone|null $timezone
456 *
457 * @return static
458 * @throws \InvalidArgumentException
459 */
460 public static function parse($datetimeString, $timezone = null)
461 {
462 try {
463 return new static($datetimeString, $timezone);
464 } catch (Exception $e) {
465 throw new InvalidArgumentException(
466 'Unable to handle datetime.', 0, $e
467 );
468 }
469 }
470
471 /**
472 * Adds an interval to the instance.
473 *
474 * @param DateInterval|string $interval
475 * @return self
476 * @throws \InvalidArgumentException
477 */
478 public function add($interval)
479 {
480 if ($interval instanceof DateInterval) {
481 return parent::add($interval);
482 }
483
484 if (!is_string($interval) || trim($interval) === '') {
485 throw new InvalidArgumentException('Invalid interval string.');
486 }
487
488 return $this->modify('+' . $interval);
489 }
490
491 /**
492 * Subtracts an interval from the instance.
493 *
494 * @param DateInterval|string $interval
495 * @return self
496 * @throws \InvalidArgumentException
497 */
498 public function sub($interval)
499 {
500 if ($interval instanceof DateInterval) {
501 return parent::sub($interval);
502 }
503
504 if (!is_string($interval) || trim($interval) === '') {
505 throw new InvalidArgumentException('Invalid interval string.');
506 }
507
508 return $this->modify('-' . $interval);
509 }
510
511 /**
512 * Creates a copy of the instance.
513 *
514 * @return self
515 */
516 public function copy()
517 {
518 return clone $this;
519 }
520
521 /**
522 * Converts the instance to a string.
523 *
524 * @return string
525 */
526 public function __toString()
527 {
528 return $this->format('Y-m-d H:i:s');
529 }
530
531 /**
532 * Checks if the instance has a timezone.
533 *
534 * @return bool
535 */
536 public function hasTimezone()
537 {
538 return $this->getTimezone() instanceof DateTimeZone;
539 }
540
541 /**
542 * Sets the time to the beginning of the day.
543 *
544 * @return self
545 */
546 public function startOfDay()
547 {
548 return $this->setTime(0, 0, 0, 0);
549 }
550
551 /**
552 * Sets the time to the end of the day.
553 *
554 * @return self
555 */
556 public function endOfDay()
557 {
558 return $this->setTime(23, 59, 59, 999999);
559 }
560
561 /**
562 * Sets the time to the beginning of the hour.
563 *
564 * @return self
565 */
566 public function startOfHour()
567 {
568 return $this->setTime((int)$this->format('H'), 0, 0, 0);
569 }
570
571 /**
572 * Sets the time to the end of the hour.
573 *
574 * @return self
575 */
576 public function endOfHour()
577 {
578 return $this->setTime((int)$this->format('H'), 59, 59, 999999);
579 }
580
581 /**
582 * Sets the time to the beginning of the minute.
583 *
584 * @return self
585 */
586 public function startOfMinute()
587 {
588 return $this->setTime(
589 (int)$this->format('H'), (int)$this->format('i'), 0, 0
590 );
591 }
592
593 /**
594 * Sets the time to the end of the minute.
595 *
596 * @return self
597 */
598 public function endOfMinute()
599 {
600 return $this->setTime(
601 (int)$this->format('H'),
602 (int)$this->format('i'), 59, 999999
603 );
604 }
605
606 /**
607 * Sets the date to the beginning of the week.
608 *
609 * @return self
610 */
611 public function startOfWeek()
612 {
613 $start = max(0, min(6, (int)get_option('start_of_week')));
614
615 $day = (int)$this->format('w');
616
617 $diff = $day - $start;
618
619 if ($diff < 0) $diff += 7;
620
621 return $this->sub(new DateInterval("P{$diff}D"))->startOfDay();
622 }
623
624 /**
625 * Sets the date to the end of the week.
626 *
627 * @return self
628 */
629 public function endOfWeek()
630 {
631 $start = max(0, min(6, (int)get_option('start_of_week')));
632
633 $day = (int)$this->format('w');
634
635 $end = ($start + 6) % 7;
636
637 $diff = $end - $day;
638
639 if ($diff < 0) $diff += 7;
640
641 return $this->add(new DateInterval("P{$diff}D"))->endOfDay();
642 }
643
644 /**
645 * Sets the date to the beginning of the month.
646 *
647 * @return self
648 */
649 public function startOfMonth()
650 {
651 return $this->setDate(
652 (int)$this->format('Y'), (int)$this->format('m'), 1
653 )->startOfDay();
654 }
655
656 /**
657 * Sets the date to the end of the month.
658 *
659 * @return self
660 */
661 public function endOfMonth()
662 {
663 return $this->modify('last day of this month')->endOfDay();
664 }
665
666 /**
667 * Sets the date to the beginning of the quarter.
668 *
669 * @return self
670 */
671 public function startOfQuarter()
672 {
673 $month = (int)$this->format('m');
674
675 $startMonth = $month <= 3 ? 1 : (
676 $month <= 6 ? 4 : ($month <= 9 ? 7 : 10)
677 );
678
679 return $this->setDate(
680 (int)$this->format('Y'), $startMonth, 1
681 )->startOfDay();
682 }
683
684 /**
685 * Sets the date to the end of the quarter.
686 *
687 * @return self
688 */
689 public function endOfQuarter()
690 {
691 $month = (int)$this->format('m');
692
693 $endMonth = $month <= 3 ? 3 : ($month <= 6 ? 6 : ($month <= 9 ? 9 : 12));
694
695 $lastDay = (int)date('t', strtotime(
696 "{$this->format('Y')}-{$endMonth}-01"
697 ));
698
699 return $this->setDate(
700 (int)$this->format('Y'), $endMonth, $lastDay
701 )->endOfDay();
702 }
703
704 /**
705 * Sets the date to the beginning of the year.
706 *
707 * @return self
708 */
709 public function startOfYear()
710 {
711 return $this->setDate(
712 (int)$this->format('Y'), 1, 1
713 )->startOfDay();
714 }
715
716 /**
717 * Sets the date to the end of the year.
718 *
719 * @return self
720 */
721 public function endOfYear()
722 {
723 return $this->setDate(
724 (int)$this->format('Y'), 12, 31
725 )->endOfDay();
726 }
727
728 /**
729 * Sets the date to the beginning of the decade.
730 *
731 * @return self
732 */
733 public function startOfDecade()
734 {
735 $year = (int)$this->format('Y');
736 $start = $year - ($year % 10);
737 return $this->setDate($start, 1, 1)->startOfDay();
738 }
739
740 /**
741 * Sets the date to the end of the decade.
742 *
743 * @return self
744 */
745 public function endOfDecade()
746 {
747 $year = (int)$this->format('Y');
748 $end = $year + (9 - ($year % 10));
749 return $this->setDate($end, 12, 31)->endOfDay();
750 }
751
752 /**
753 * Magic setter for time units.
754 *
755 * @param string $key
756 * @param mixed $value
757 * @return self
758 * @throws \InvalidArgumentException
759 */
760 public function __set($key, $value)
761 {
762 $new = clone $this;
763 switch ($key) {
764 case 'year': return $new->setDate(
765 (int)$value,
766 (int)$this->format('m'),
767 (int)$this->format('d')
768 );
769
770 case 'month': return $new->setDate(
771 (int)$this->format('Y'),
772 (int)$value,
773 (int)$this->format('d')
774 );
775
776 case 'day': return $new->setDate(
777 (int)$this->format('Y'),
778 (int)$this->format('m'),
779 (int)$value
780 );
781
782 case 'hour': return $new->setTime(
783 (int)$value,
784 (int)$this->format('i'),
785 (int)$this->format('s')
786 );
787
788 case 'minute': return $new->setTime(
789 (int)$this->format('H'),
790 (int)$value,
791 (int)$this->format('s')
792 );
793
794 case 'second': return $new->setTime(
795 (int)$this->format('H'),
796 (int)$this->format('i'),
797 (int)$value
798 );
799
800 default: throw new InvalidArgumentException(
801 "Cannot set unknown property '{$key}'."
802 );
803 }
804 }
805
806 /**
807 * Magic method for dynamic calls (e.g., `addDay`, `getYear`).
808 *
809 * @param string $method
810 * @param array $params
811 * @return mixed|self
812 * @throws \InvalidArgumentException
813 */
814 public function __call($method, $params)
815 {
816 // set* methods
817 if (strpos($method, 'set') === 0) {
818 $unit = lcfirst(substr($method, 3));
819 if ($params && in_array($unit, static::SINGULAR_UNITS, true)) {
820 return $this->__set($unit, reset($params));
821 }
822 }
823
824 // get* methods
825 if (strpos($method, 'get') === 0) {
826 $unit = lcfirst(substr($method, 3));
827 switch ($unit) {
828 case 'year': return (int)$this->format('Y');
829 case 'month': return (int)$this->format('m');
830 case 'day': return (int)$this->format('d');
831 case 'hour': return (int)$this->format('H');
832 case 'minute': return (int)$this->format('i');
833 case 'second': return (int)$this->format('s');
834 case 'quarter': return (int)(ceil((int)$this->format('m') / 3));
835 case 'decade': return (int)floor((int)$this->format('Y') / 10) * 10;
836 default: throw new InvalidArgumentException(
837 "Call to undefined method {$method}."
838 );
839 }
840 }
841
842 // add*/sub* methods
843 $action = null;
844 if (strpos($method, 'add') === 0) $action = '+';
845 elseif (strpos($method, 'sub') === 0) $action = '-';
846
847 if ($action) {
848 $duration = $params[0] ?? 1;
849 $unit = lcfirst(substr($method, 3));
850
851 if (
852 !in_array(
853 $unit,
854 array_merge(
855 static::SINGULAR_UNITS,
856 static::PLURAL_UNITS
857 ), true
858 )
859 ) {
860 throw new InvalidArgumentException(
861 "Call to undefined method {$method}."
862 );
863 }
864
865 // Special handling for quarter (3 months)
866 if ($unit === 'quarter' || $unit === 'quarters') $unit = 'month';
867 // Special handling for decade (10 years)
868 if ($unit === 'decade' || $unit === 'decades') $unit = 'year';
869
870 $multiplier = 1;
871 if (stripos($method, 'quarter') !== false) $multiplier = 3;
872 if (stripos($method, 'decade') !== false) $multiplier = 10;
873
874 return $this->modify(
875 "{$action}" . ($duration * $multiplier) . " {$unit}"
876 );
877 }
878
879 throw new InvalidArgumentException("Call to undefined method {$method}.");
880 }
881 }
882