PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Support / DateTime.php

DateTime.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95, at vendor/wpfluent/framework/src/WPFluent/Support/DateTime.php

1,458 lines 37.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\Framework\Support;
4
5 use DateTimeZone;
6 use DateInterval;
7 use DateTimeInterface;
8 use DateTime as PHPDateTime;
9 use InvalidArgumentException;
10
11 class DateTime extends PHPDateTime
12 {
13 /**
14 * $singularUnits for checking during dynamic calls
15 * @var array
16 */
17 protected static $singularUnits = [
18 'year', 'month', 'week', 'day', 'hour', 'minute', 'second',
19 ];
20
21 /**
22 * $pluralUnits for checking during dynamic calls
23 * @var array
24 */
25 protected static $pluralUnits = [
26 'years','months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
27 ];
28
29 /**
30 * Construct the DateTime Object
31 *
32 * @param string $datetime
33 * @param \DateTimeZone $timezone|null
34 */
35 public function __construct($datetime = 'now', $timezone = null)
36 {
37 if (is_string($timezone)) {
38 $timezone = new DateTimeZone($timezone);
39 }
40
41 $timezone ??= static::getDefaultTimezone();
42
43 if ($datetime instanceof DateTimeInterface) {
44 $datetime = $datetime->format('Y-m-d H:i:s.u');
45 } elseif (
46 is_numeric($datetime)
47 || str_starts_with((string) $datetime, '@')
48 ) {
49 $datetime = '@' . ltrim((string) $datetime, '@');
50 }
51
52 parent::__construct($datetime, $timezone);
53 }
54
55 /**
56 * Create a new DateTime Object with current time
57 *
58 * @param string|null $tz
59 * @return static
60 */
61 public static function now($tz = null)
62 {
63 return static::create('now', $tz);
64 }
65
66 /**
67 * Create a new DateTime Object with today's time
68 *
69 * @param string|null $tz
70 * @return static
71 */
72 public static function today($tz = null)
73 {
74 return static::create('today', $tz)->startOfDay();
75 }
76
77 /**
78 * Create a new DateTime Object with yesterday's time
79 *
80 * @param string|null $tz
81 * @return static
82 */
83 public static function yesterday($tz = null)
84 {
85 return static::create('now', $tz)->modify('-1 day')->startOfDay();
86 }
87
88 /**
89 * Create a new DateTime Object with tomorrow's time
90 *
91 * @param string|null $tz
92 * @return static
93 */
94 public static function tomorrow($tz = null)
95 {
96 return static::create('now', $tz)->modify('+1 day')->startOfDay();
97 }
98
99 /**
100 * Create a new DateTime Object with the current week's starting time.
101 *
102 * @param string|null $tz
103 * @return static
104 */
105 public static function currentWeek($tz = null)
106 {
107 return static::create('now', $tz)->startOfWeek();
108 }
109
110 /**
111 * Create a new DateTime Object with last week's starting time
112 *
113 * @param string|null $tz
114 * @return static
115 */
116 public static function lastWeek($tz = null)
117 {
118 return static::create('now', $tz)->subWeek()->startOfWeek();
119 }
120
121 /**
122 * Create a new DateTime Object with next week's starting time
123 *
124 * @param string|null $tz
125 * @return static
126 */
127 public static function nextWeek($tz = null)
128 {
129 return static::create('now', $tz)->addWeek()->startOfWeek();
130 }
131
132 /**
133 * Create a new DateTime Object with current month's starting time
134 *
135 * @param string|null $tz
136 * @return static
137 */
138 public static function currentMonth($tz = null)
139 {
140 return static::create('now', $tz)->startOfMonth();
141 }
142
143 /**
144 * Create a new DateTime Object with last month's starting time
145 *
146 * @param string|null $tz
147 * @return static
148 */
149 public static function lastMonth($tz = null)
150 {
151 return static::create('now', $tz)->subMonth()->startOfMonth();
152 }
153
154 /**
155 * Create a new DateTime Object with next month's starting time
156 *
157 * @param string|null $tz
158 * @return static
159 */
160 public static function nextMonth($tz = null)
161 {
162 return static::create('now', $tz)->addMonth()->startOfMonth();
163 }
164
165 /**
166 * Create a new DateTime Object with current year's starting time
167 *
168 * @param string|null $tz
169 * @return static
170 */
171 public static function currentYear($tz = null)
172 {
173 return static::create('now', $tz)->startOfYear();
174 }
175
176 /**
177 * Create a new DateTime Object with last year's starting time
178 *
179 * @param string|null $tz
180 * @return static
181 */
182 public static function lastYear($tz = null)
183 {
184 return static::create('now', $tz)->subYear()->startOfYear();
185 }
186
187 /**
188 * Create a new DateTime Object with next year's starting time
189 *
190 * @param string|null $tz
191 * @return static
192 */
193 public static function nextYear($tz = null)
194 {
195 return static::create('now', $tz)->addYear()->startOfYear();
196 }
197
198 /**
199 * Get the default timezone
200 *
201 * @return \DateTimeZone
202 */
203 public function getDefaultTimezone()
204 {
205 return wp_timezone();
206 }
207
208 /**
209 * Set the timezone
210 *
211 * @return $this
212 */
213 public function timezone($tz)
214 {
215 if (is_string($tz)) {
216 $tz = new DateTimeZone($tz);
217 }
218
219 return $this->setTimezone($tz);
220 }
221
222 /**
223 * Get the default date format
224 *
225 * @return string
226 */
227 public function getDateFormat()
228 {
229 return 'Y-m-d H:i:s';
230 }
231
232 /**
233 * Check if the current instance is between two dates
234 *
235 * @param string|DateTimeInterface $date1
236 * @param string|DateTimeInterface $date2,
237 * @return bool
238 */
239 public function between($date1, $date2): bool
240 {
241 if (!$date1 instanceof DateTimeInterface) {
242 $date1 = new DateTime($date1);
243 }
244
245 if (!$date2 instanceof DateTimeInterface) {
246 $date2 = new DateTime($date2);
247 }
248
249 $current = $this->getTimestamp();
250 $start = min($date1->getTimestamp(), $date2->getTimestamp());
251 $end = max($date1->getTimestamp(), $date2->getTimestamp());
252
253 return $current >= $start && $current <= $end;
254 }
255
256 /**
257 * Create a DateTime object from a string, UNIX timestamp,
258 * or other DateTimeInterface object.
259 *
260 * @param string|int|\DateTimeInterface $time
261 * @return static
262 * @throws \Exception
263 */
264 public static function create($time = null, $tz = null)
265 {
266 if (func_num_args() > 2) {
267 return static::createFromDate(...func_get_args());
268 }
269
270 $time = $time ?: static::now();
271
272 if (is_null($tz)) {
273 $timezone = (new static)->getDefaultTimezone();
274 } else {
275 $timezone = is_string($tz) ? new DateTimeZone($tz) : $tz;
276 }
277
278 if (!$timezone instanceof DateTimeZone) {
279 throw new InvalidArgumentException('Invalid timezone.');
280 }
281
282 if ($time instanceof DateTimeInterface) {
283
284 $dateTime = new static(
285 $time->format((new static)->getDateFormat()), $time->getTimezone()
286 );
287
288 // Override the timezone if the timezone is explictly provided
289 // otherwise don't set the default timezone from $timezone.
290 !is_null($tz) && $dateTime->setTimezone($timezone);
291
292 } elseif (is_numeric($time)) {
293 if ($time <= YEAR_IN_SECONDS) {
294 $time += time();
295 }
296
297 $dateTime = new static('@' . $time);
298
299 $dateTime->setTimezone($timezone);
300
301 } else {
302 $dateTime = new static((string) $time);
303
304 // Set the timezone if timezone is explicitly provided
305 // otherwise set the default timezone if there was no
306 // timezne information available with the string.
307 if ($tz || !$dateTime->hasTimezone($time)) {
308 $dateTime->setTimezone($timezone);
309 }
310 }
311
312 return $dateTime;
313 }
314
315 /**
316 * Check if the given datetime string has the timezone
317 * information attached: Z or +/-00:00 or Asia\Dhaka.
318 *
319 * @param string $datetimeString
320 * @return boolean
321 */
322 public function hasTimezone($datetimeString)
323 {
324 // Regular expression to match timezone
325 // identifier, UTC, or timezone offset
326 $pattern = '/(?:[A-Z][a-zA-Z_]+\/[a-zA-Z_]+|Z|[-+]\d{2}:\d{2})/';
327
328 return preg_match($pattern, $datetimeString) === 1;
329 }
330
331 /**
332 * {@inheritdoc}
333 */
334 #[\ReturnTypeWillChange]
335 public static function createFromFormat($format, $datetimeString, $timezone = null)
336 {
337 if (is_null($timezone)) {
338 $timezone = (new static)->getDefaultTimezone();
339 } else {
340 $timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone;
341 }
342
343 if (!$timezone instanceof DateTimeZone) {
344 throw new InvalidArgumentException('Invalid timezone.');
345 }
346
347 $dateTime = PHPDateTime::createFromFormat($format, $datetimeString);
348
349 if ($dateTime !== false) {
350
351 if (!$dateTime instanceof static) {
352 return new static(
353 $dateTime->format(ltrim($format, '!')), $timezone
354 );
355 }
356
357 $dateTime->setTimezone($timezone);
358
359 return $dateTime;
360 }
361
362 throw new InvalidArgumentException(
363 "Unable to create datetime from: {$datetimeString}."
364 );
365 }
366
367 /**
368 * Create DateTime object.
369 *
370 * @return static
371 * @throws InvalidArgumentException
372 */
373 public static function createFromDate(
374 $year, $month, $day, $hour = 0, $minute = 0, $second = 0.0, $tz = null
375 ) {
376
377 $s = sprintf(
378 '%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second
379 );
380
381 if (
382 !checkdate($month, $day, $year)
383 || $hour < 0
384 || $hour > 23
385 || $minute < 0
386 || $minute > 59
387 || $second < 0
388 || $second >= 60
389 ) {
390 throw new InvalidArgumentException("Invalid date '$s'");
391 }
392
393 return new static($s, (is_string($tz) ? new DateTimeZone($tz) : $tz));
394 }
395
396 /**
397 * Given a date in UTC or GMT timezone, returns
398 * that date in the timezone of the site.
399 *
400 * Requires a date in the Y-m-d H:i:s format.
401 *
402 * Default return format of 'Y-m-d H:i:s' can be
403 * overridden using the `$format` parameter.
404 *
405 * @param string $dateString The date to be converted, in UTC or GMT timezone.
406 * @param string $format The format string for the returned date. Default 'Y-m-d H:i:s'.
407 * @see https://developer.wordpress.org/reference/functions/get_date_from_gmt/
408 *
409 * @return string Formatted version of the date, in the site's timezone.
410 */
411 public static function createFromUTC($dateString, $format = 'Y-m-d H:i:s')
412 {
413 $localString = get_date_from_gmt($dateString, $format);
414
415 $date = new static($localString);
416
417 $date->timezone($date->getDefaultTimezone());
418
419 return $date;
420 }
421
422 /**
423 * Parse a datetime string
424 * @param string $datetimeString
425 * @param string $timezone
426 * @return static
427 * @throws InvalidArgumentException
428 */
429 public static function parse($datetimeString, $timezone = null)
430 {
431 try {
432 return new static($datetimeString, $timezone);
433 } catch (Exception $e) {
434 throw new InvalidArgumentException(
435 'Unable to handle datetime.', 0, $e
436 );
437 }
438 }
439
440 /**
441 * Add inetrvals, for example:
442 *
443 * add(1, day)
444 * add('2 day 8 hours 22 minutes')
445 *
446 * @param \DateInterval|string $interval
447 * @return $this
448 */
449 #[\ReturnTypeWillChange]
450 public function add($interval)
451 {
452 if ($interval instanceof DateInterval) {
453 return parent::add($interval);
454 } elseif (func_num_args() === 1 && is_string($interval)) {
455 return $this->modify('+'.$interval);
456 }
457
458 return $this->addOrSub('add', func_get_args());
459 }
460
461 /**
462 * Substruct inetrvals, for example:
463 *
464 * sub(1, day)
465 * sub('2 day 8 hours 22 minutes')
466 *
467 * @param \DateInterval $interval (optional)
468 * @return $this
469 */
470 #[\ReturnTypeWillChange]
471 public function sub($interval)
472 {
473 if ($interval instanceof DateInterval) {
474 return parent::sub($interval);
475 } elseif (func_num_args() === 1 && is_string($interval)) {
476 return $this->modify('-'.$interval);
477 }
478
479 return $this->addOrSub('sub', func_get_args());
480 }
481
482 /**
483 * Add or sub intervals
484 * @param string $action add/sub
485 * @param array $args
486 */
487 protected function addOrSub($action, $args)
488 {
489 $value = reset($args);
490
491 $action = $action.end($args);
492
493 return $this->{$action}($value);
494 }
495
496 /**
497 * Adds the given number of seconds to the current date and time.
498 *
499 * @param int $seconds The number of seconds to add.
500 * @return $this The current instance for method chaining.
501 */
502 public function addSeconds(int $seconds)
503 {
504 return $this->add("{$seconds} seconds");
505 }
506
507 /**
508 * Adds exactly one second to the current date and time.
509 *
510 * @return $this
511 */
512 public function addSecond()
513 {
514 return $this->add("1 second");
515 }
516
517 /**
518 * Adds the given number of minutes to the current date and time.
519 *
520 * @param int $minutes The number of minutes to add.
521 * @return $this The current instance for method chaining.
522 */
523 public function addMinutes(int $minutes)
524 {
525 return $this->add("{$minutes} minutes");
526 }
527
528 /**
529 * Adds exactly one minute to the current date and time.
530 *
531 * @return $this
532 */
533 public function addMinute()
534 {
535 return $this->add("1 minute");
536 }
537
538 /**
539 * Adds the given number of hours to the current date and time.
540 *
541 * @param int $hours The number of hours to add.
542 * @return $this The current instance for method chaining.
543 */
544 public function addHours(int $hours)
545 {
546 return $this->add("{$hours} hours");
547 }
548
549 /**
550 * Adds exactly one hour to the current date and time.
551 *
552 * @return $this
553 */
554 public function addHour()
555 {
556 return $this->add("1 hour");
557 }
558
559 /**
560 * Adds the given number of days to the current date and time.
561 *
562 * @param int $days The number of days to add.
563 * @return $this The current instance for method chaining.
564 */
565 public function addDays(int $days)
566 {
567 return $this->add("{$days} days");
568 }
569
570
571 /**
572 * Adds exactly one day to the current date and time.
573 *
574 * @return $this
575 */
576 public function addDay()
577 {
578 return $this->add("1 day");
579 }
580
581 /**
582 * Adds the given number of weeks to the current date and time.
583 *
584 * @param int $weeks The number of weeks to add.
585 * @return $this The current instance for method chaining.
586 */
587 public function addWeeks(int $weeks)
588 {
589 return $this->add("{$weeks} weeks");
590 }
591
592 /**
593 * Adds exactly one week to the current date and time.
594 *
595 * @return $this
596 */
597 public function addWeek()
598 {
599 return $this->add("1 week");
600 }
601
602 /**
603 * Adds the given number of months to the current date and time.
604 *
605 * @param int $months The number of months to add.
606 * @return $this The current instance for method chaining.
607 */
608 public function addMonths(int $months)
609 {
610 return $this->add("{$months} months");
611 }
612
613 /**
614 * Adds exactly one month to the current date and time.
615 *
616 * @return $this
617 */
618 public function addMonth()
619 {
620 return $this->add("1 month");
621 }
622
623 /**
624 * Adds the given number of years to the current date and time.
625 *
626 * @param int $years The number of years to add.
627 * @return $this The current instance for method chaining.
628 */
629 public function addYears(int $years)
630 {
631 return $this->add("{$years} years");
632 }
633
634 /**
635 * Adds exactly one year to the current date and time.
636 *
637 * @return $this
638 */
639 public function addYear()
640 {
641 return $this->add("1 year");
642 }
643
644 /**
645 * Add a quarter (3 months) to the current date.
646 *
647 * @return $this
648 */
649 public function addQuarter()
650 {
651 return $this->add(new DateInterval('P3M'));
652 }
653
654 /**
655 * Add a decade (10 years) to the current date.
656 *
657 * @return $this
658 */
659 public function addDecade()
660 {
661 return $this->add(new DateInterval('P10Y'));
662 }
663
664 /**
665 * Subtracts the given number of seconds from the current date and time.
666 *
667 * @param int $seconds The number of seconds to subtract.
668 * @return $this The current instance for method chaining.
669 */
670 public function subSeconds(int $seconds)
671 {
672 return $this->sub("{$seconds} seconds");
673 }
674
675 /**
676 * Subtracts one second from the current date and time.
677 *
678 * @return $this The current instance for method chaining.
679 */
680 public function subSecond()
681 {
682 return $this->sub("1 second");
683 }
684
685 /**
686 * Subtracts the given number of minutes from the current date and time.
687 *
688 * @param int $minutes The number of minutes to subtract.
689 * @return $this The current instance for method chaining.
690 */
691 public function subMinutes(int $minutes)
692 {
693 return $this->sub("{$minutes} minutes");
694 }
695
696 /**
697 * Subtracts one minute from the current date and time.
698 *
699 * @return $this The current instance for method chaining.
700 */
701 public function subMinute()
702 {
703 return $this->sub("1 minute");
704 }
705
706 /**
707 * Subtracts the given number of hours from the current date and time.
708 *
709 * @param int $hours The number of hours to subtract.
710 * @return $this The current instance for method chaining.
711 */
712 public function subHours(int $hours)
713 {
714 return $this->sub("{$hours} hours");
715 }
716
717 /**
718 * Subtracts one hour from the current date and time.
719 *
720 * @return $this The current instance for method chaining.
721 */
722 public function subHour()
723 {
724 return $this->sub("1 hour");
725 }
726
727 /**
728 * Subtracts the given number of days from the current date and time.
729 *
730 * @param int $days The number of days to subtract.
731 * @return $this The current instance for method chaining.
732 */
733 public function subDays(int $days)
734 {
735 return $this->sub("{$days} days");
736 }
737
738 /**
739 * Subtracts one day from the current date and time.
740 *
741 * @return $this The current instance for method chaining.
742 */
743 public function subDay()
744 {
745 return $this->sub("1 day");
746 }
747
748 /**
749 * Subtracts the given number of weeks from the current date and time.
750 *
751 * @param int $weeks The number of weeks to subtract.
752 * @return $this The current instance for method chaining.
753 */
754 public function subWeeks(int $weeks)
755 {
756 return $this->sub("{$weeks} weeks");
757 }
758
759 /**
760 * Subtracts one week from the current date and time.
761 *
762 * @return $this The current instance for method chaining.
763 */
764 public function subWeek()
765 {
766 return $this->sub("1 week");
767 }
768
769 /**
770 * Subtracts the given number of months from the current date and time.
771 *
772 * @param int $months The number of months to subtract.
773 * @return $this The current instance for method chaining.
774 */
775 public function subMonths(int $months)
776 {
777 return $this->sub("{$months} months");
778 }
779
780 /**
781 * Subtracts one month from the current date and time.
782 *
783 * @return $this The current instance for method chaining.
784 */
785 public function subMonth()
786 {
787 return $this->sub("1 month");
788 }
789
790 /**
791 * Subtracts the given number of years from the current date and time.
792 *
793 * @param int $years The number of years to subtract.
794 * @return $this The current instance for method chaining.
795 */
796 public function subYears(int $years)
797 {
798 return $this->sub("{$years} years");
799 }
800
801 /**
802 * Subtracts one year from the current date and time.
803 *
804 * @return $this The current instance for method chaining.
805 */
806 public function subYear()
807 {
808 return $this->sub("1 year");
809 }
810
811 /**
812 * Subtract a quarter (3 months) from the current date.
813 *
814 * @return $this
815 */
816 public function subQuarter()
817 {
818 return $this->sub(new DateInterval('P3M'));
819 }
820
821 /**
822 * Subtract a decade (10 years) from the current date.
823 *
824 * @return $this
825 */
826 public function subDecade()
827 {
828 return $this->sub(new DateInterval('P10Y'));
829 }
830
831 /**
832 * Set the date to start of the decade.
833 * @return $this
834 */
835 public function startOfDecade()
836 {
837 $year = (int) $this->format('Y');
838 // Find the start of the decade by subtracting the remainder
839 // of the division by 10 from the current year.
840 $startOfDecadeYear = $year - ($year % 10);
841
842 // Set the date to the start of the decade (January 1st)
843 return $this->setDate($startOfDecadeYear, 1, 1)->setTime(0, 0);
844 }
845
846 /**
847 * Set the date to end of the decade.
848 * @return $this
849 */
850 public function endOfDecade()
851 {
852 $year = (int) $this->format('Y');
853 // Find the last year of the decade by adding 9 to the current
854 // year and subtracting the remainder of the division by 10.
855 $endOfDecadeYear = $year + (9 - ($year % 10));
856
857 // Set the date to December 31st of that year at 23:59:59
858 return $this->setDate($endOfDecadeYear, 12, 31)->setTime(23, 59, 59);
859 }
860
861 /**
862 * Sets start of the year in the current dateTime
863 *
864 * @return $this
865 */
866 public function startOfYear()
867 {
868 return $this->modify('first day of January')->startOfDay();
869 }
870
871 /**
872 * Sets end of the year in the current dateTime
873 *
874 * @return $this
875 */
876 public function endOfYear()
877 {
878 return $this->modify('last day of December')->endOfDay();
879 }
880
881 /**
882 * Sets the date to the first day of the current quarter at 00:00:00.
883 *
884 * @return $this
885 */
886 public function startOfQuarter()
887 {
888 $month = (int) $this->format('m');
889 // Determine the start month of the current quarter
890 if ($month <= 3) {
891 $startMonth = 1; // Q1 starts in January
892 } elseif ($month <= 6) {
893 $startMonth = 4; // Q2 starts in April
894 } elseif ($month <= 9) {
895 $startMonth = 7; // Q3 starts in July
896 } else {
897 $startMonth = 10; // Q4 starts in October
898 }
899
900 // Set the date to the first day of the quarter at 00:00:00
901 return $this->setDate((int) $this->format('Y'), $startMonth, 1)->setTime(0, 0, 0);
902 }
903
904 /**
905 * Sets the date to the last day of the current quarter at 23:59:59.
906 *
907 * @return $this
908 */
909 public function endOfQuarter()
910 {
911 $month = (int) $this->format('m');
912 // Determine the end month of the current quarter
913 if ($month <= 3) {
914 $endMonth = 3; // Q1 ends in March
915 } elseif ($month <= 6) {
916 $endMonth = 6; // Q2 ends in June
917 } elseif ($month <= 9) {
918 $endMonth = 9; // Q3 ends in September
919 } else {
920 $endMonth = 12; // Q4 ends in December
921 }
922
923 // Set the date to the last day of the quarter at 23:59:59
924 return $this->setDate((int) $this->format('Y'), $endMonth, cal_days_in_month(CAL_GREGORIAN, $endMonth, (int) $this->format('Y')))
925 ->setTime(23, 59, 59);
926 }
927
928 /**
929 * Sets start of the month in the current dateTime
930 *
931 * @return $this
932 */
933 public function startOfMonth()
934 {
935 return $this->modify('first day of this month')->startOfDay();
936 }
937
938 /**
939 * Sets end of the month in the current dateTime
940 *
941 * @return $this
942 */
943 public function endOfMonth()
944 {
945 return $this->modify('last day of this month')->endOfDay();
946 }
947
948 /**
949 * Sets start of the week in the current dateTime
950 *
951 * @return $this
952 */
953 public function startOfWeek()
954 {
955 $startOfWeek = intval(get_option('start_of_week'));
956
957 $this->modify('this week');
958
959 // If the start of the week is Sunday (0)
960 if ($startOfWeek === 0) {
961 return $this->modify('this Sunday')->startOfDay();
962 } else {
963 // If it's Monday (1), we need to subtract 1 day.
964 return $this->modify(
965 'this Sunday - ' . (7 - $startOfWeek) . ' days'
966 )->startOfDay();
967 }
968 }
969
970 /**
971 * Sets end of the week in the current dateTime
972 *
973 * @return $this
974 */
975 public function endOfWeek()
976 {
977 // 0 = Sunday, 1 = Monday, etc.
978 $startOfWeek = intval(get_option('start_of_week'));
979
980 // If the start of the week is Monday (1), the
981 // end of the week is the upcoming Sunday
982 if ($startOfWeek === 1) {
983 return $this->modify('next Sunday')->endOfDay();
984 }
985
986 // If the start of the week is Sunday (0), the
987 // end of the week is the upcoming Saturday
988 return $this->modify('next Saturday')->endOfDay();
989 }
990
991 /**
992 * Sets start of the day in the current dateTime
993 *
994 * @return $this
995 */
996 public function startOfDay()
997 {
998 return $this->setTime(0, 0, 0, 0);
999 }
1000
1001 /**
1002 * Sets end of the day in the current dateTime
1003 *
1004 * @return $this
1005 */
1006 public function endOfDay()
1007 {
1008 return $this->setTime(23, 59, 59);
1009 }
1010
1011 /**
1012 * Sets start of the hour in the current DateTime object
1013 *
1014 * @return $this
1015 */
1016 public function startOfHour()
1017 {
1018 return $this->setTime($this->format('H'), 0, 0, 0);
1019 }
1020
1021 /**
1022 * Sets end of the hour in the current DateTime object
1023 *
1024 * @return $this
1025 */
1026 public function endOfHour()
1027 {
1028 return $this->setTime($this->format('H'), 59, 59, 999999);
1029 }
1030
1031 /**
1032 * Sets start of the minute in the current DateTime object
1033 *
1034 * @return $this
1035 */
1036 public function startOfMinute()
1037 {
1038 $hour = $this->format('H');
1039 $minute = $this->format('i');
1040 return $this->setTime($hour, $minute, 0, 0);
1041 }
1042
1043 /**
1044 * Sets end of the minute in the current DateTime object
1045 *
1046 * @return $this
1047 */
1048 public function endOfMinute()
1049 {
1050 $hour = $this->format('H');
1051 $minute = $this->format('i');
1052 return $this->setTime($hour, $minute, 59, 999999);
1053 }
1054
1055 /**
1056 * Check if the current instance is a weekend.
1057 *
1058 * @return bool
1059 */
1060 public function isWeekend($startOfWeek = null): bool
1061 {
1062 if ($startOfWeek === null) {
1063 $startOfWeek = $startOfWeek = intval(get_option('start_of_week'));
1064 }
1065
1066 // Get the numeric representation of the current day of the week (0 - 6)
1067 $dayOfWeek = (int) $this->format('w');
1068
1069 // Adjust the day of the week based on the start of the week
1070 switch ($startOfWeek) {
1071 case 'monday':
1072 // If the week starts on Monday, adjust Sunday to 6
1073 return ($dayOfWeek === 0 || $dayOfWeek === 6);
1074 case 'saturday':
1075 // If the week starts on Saturday, adjust Friday to 6
1076 return ($dayOfWeek === 5 || $dayOfWeek === 6);
1077 case 'sunday':
1078 default:
1079 // Default behavior, week starts on Sunday
1080 return ($dayOfWeek === 0 || $dayOfWeek === 6);
1081 }
1082 }
1083
1084 /**
1085 * Check if the current instance is a weekday.
1086 *
1087 * @return bool
1088 */
1089 public function isWeekday()
1090 {
1091 return !$this->isWeekend();
1092 }
1093
1094 /**
1095 * Check if the current instance is in the past.
1096 *
1097 * @return bool
1098 */
1099 public function isPast()
1100 {
1101 // Compare with current date and time
1102 return $this < new static();
1103 }
1104
1105 /**
1106 * Check if the current instance is in the future.
1107 *
1108 * @return bool
1109 */
1110 public function isFuture()
1111 {
1112 return $this > new static();
1113 }
1114
1115 /**
1116 * Check if the year is a leap year.
1117 * @return boolean
1118 */
1119 public function isLeapYear(): bool
1120 {
1121 $year = (int) $this->format('Y');
1122 return ($year % 4 === 0 && $year % 100 !== 0) || ($year % 400 === 0);
1123 }
1124
1125 /**
1126 * Checks if the current time is midnight (00:00:00).
1127 *
1128 * @return bool
1129 */
1130 public function isMidnight()
1131 {
1132 return $this->format('H:i:s') === '00:00:00';
1133 }
1134
1135 /**
1136 * Check if the current instance is the same day as another DateTime instance.
1137 *
1138 * @param DateTime $other
1139 * @return bool
1140 */
1141 public function isSameDay(DateTime $other)
1142 {
1143 return $this->format('Y-m-d') === $other->format('Y-m-d');
1144 }
1145
1146 /**
1147 * Clone the current Object
1148 *
1149 * @return \FluentBoards\Framework\Support\DateTime
1150 */
1151 public function copy()
1152 {
1153 return clone $this;
1154 }
1155
1156 /**
1157 * Get the difference in years
1158 *
1159 * @param \FluentBoards\Framework\Support\DateTime $date
1160 * @return int
1161 */
1162 public function diffInYears($date)
1163 {
1164 return $this->diff($date)->y;
1165 }
1166
1167 /**
1168 * Get the difference in months
1169 *
1170 * @param \FluentBoards\Framework\Support\DateTime $date
1171 * @return int
1172 */
1173 public function diffInMonths($date)
1174 {
1175 $diff = $this->diff($date);
1176
1177 return $diff->y * 12 + $diff->m;
1178 }
1179
1180 /**
1181 * Get the difference in days
1182 *
1183 * @param \FluentBoards\Framework\Support\DateTime $date
1184 * @return int
1185 */
1186 public function diffInDays($date)
1187 {
1188 $diff = $this->diff($date);
1189
1190 return $diff->days;
1191 }
1192
1193 /**
1194 * Get the difference in hours
1195 *
1196 * @param \FluentBoards\Framework\Support\DateTime $date
1197 * @return int
1198 */
1199 public function diffInHours($date)
1200 {
1201 $diff = $this->diff($date);
1202
1203 $diffInHours = $diff->h;
1204
1205 return $diffInHours + $diff->days * 24;
1206 }
1207
1208 /**
1209 * Get the difference in minutes
1210 *
1211 * @param \FluentBoards\Framework\Support\DateTime $date
1212 * @return int
1213 */
1214 public function diffInMinutes($date)
1215 {
1216 $diff = $this->diff($date);
1217
1218 $diffInMinutes = $diff->i;
1219
1220 $diffInMinutes += $diff->h * 60;
1221
1222 return $diffInMinutes + $diff->days * 24 * 60;
1223 }
1224
1225 /**
1226 * Get the difference in seconds
1227 *
1228 * @param \FluentBoards\Framework\Support\DateTime $date
1229 * @return int
1230 */
1231 public function diffInSeconds($date)
1232 {
1233 $diff = $this->diff($date);
1234
1235 $diffInSeconds = $diff->days * 24 * 60 * 60;
1236
1237 $diffInSeconds += $diff->h * 60 * 60;
1238
1239 $diffInSeconds += $diff->i * 60;
1240
1241 return $diffInSeconds + $diff->s;
1242 }
1243
1244 /**
1245 * Get human friendly time difference (2 hours ago/ 2 hours from now)
1246 *
1247 * @param \DateTimeInterface|string|int $from The datetime to compare from
1248 * @param \DateTimeInterface|string|int $to The datetime to compare to
1249
1250 * @return string Human readable string, ie. 5 days ago/from now
1251 */
1252 public function diffForHumans($from = null, $to = null)
1253 {
1254 // Use the current object's timestamp if $from (and $to) is null
1255 // This is because ORM's datetime field can call it without params.
1256 if (is_null($from)) {
1257 $from = $this->getTimestamp();
1258 } elseif ($from instanceof \DateTimeInterface) {
1259 $from = $from->getTimestamp();
1260 } elseif (!is_numeric($from)) {
1261 $from = (new \DateTime($from))->getTimestamp();
1262 }
1263
1264 // Use the current time as $to if not provided
1265 if (is_null($to)) {
1266 $to = time();
1267 } elseif ($to instanceof \DateTimeInterface) {
1268 $to = $to->getTimestamp();
1269 } elseif (!is_numeric($to)) {
1270 $to = (new \DateTime($to))->getTimestamp();
1271 }
1272
1273 // Calculate the difference in seconds
1274 $diffInSeconds = abs($to - $from);
1275 $dateTimeDiff = human_time_diff($from, $to);
1276
1277 // Determine if the difference is in the past or future
1278 if ($from > $to) {
1279 // The "from" time is earlier than "to" (future)
1280 return sprintf(__('%s from now'), $dateTimeDiff);
1281 } else {
1282 // The "from" time is later than "to" (older)
1283 if ($diffInSeconds > 60) {
1284 return sprintf(__('%s ago'), $dateTimeDiff);
1285 }
1286
1287 // If difference is less than 1 minute, return just now
1288 return __('just now');
1289 }
1290 }
1291
1292 /**
1293 * Given a date in the timezone of the site, returns that date in UTC.
1294 *
1295 * Requires and returns a date in the Y-m-d H:i:s format.
1296 *
1297 * Return format can be overridden using the $format parameter.
1298 *
1299 * @param string $dateString The date to be converted, in the timezone of the site.
1300 * @param string $format The format string for the returned date. Default 'Y-m-d H:i:s'.
1301 * @see https://developer.wordpress.org/reference/functions/get_gmt_from_date/
1302 *
1303 * @return string Formatted version of the date, in UTC.
1304 */
1305 public function toUTC($dateString, $format = 'Y-m-d H:i:s')
1306 {
1307 return get_gmt_from_date($dateString, $format);
1308 }
1309
1310 /**
1311 * Return the ISO-8601 string
1312 *
1313 * @see https://stackoverflow.com/a/11173072/741747
1314 *
1315 * @return mixed
1316 */
1317 public function toJSON()
1318 {
1319 return date('c', $this->getTimestamp());
1320 }
1321
1322 /**
1323 * Returns the formatted string
1324 *
1325 * @return string
1326 */
1327 public function toString()
1328 {
1329 return (string) $this;
1330 }
1331
1332 /**
1333 * Return only the date part as string
1334 *
1335 * @return string
1336 */
1337 public function toDateString()
1338 {
1339 return (string) $this->format('Y-m-d');
1340 }
1341
1342 /**
1343 * Return only the time part as string
1344 *
1345 * @return string
1346 */
1347 public function toTimeString()
1348 {
1349 return (string) $this->format('H:i:s');
1350 }
1351
1352 /**
1353 * Returns the formatted string
1354 *
1355 * @return string
1356 */
1357 public function __toString()
1358 {
1359 return $this->format($this->getDateFormat());
1360 }
1361
1362 /**
1363 * Getter to get an unit of DateTime
1364 * @param string $key
1365 * @return string|null
1366 */
1367 public function __get($key)
1368 {
1369 if ($key == 'year') {
1370 return $this->format('Y');
1371 } elseif ($key == 'month') {
1372 return $this->format('m');
1373 } elseif ($key == 'day') {
1374 return $this->format('d');
1375 } elseif ($key == 'hour') {
1376 return $this->format('H');
1377 } elseif ($key == 'minute') {
1378 return $this->format('i');
1379 } elseif ($key == 'second') {
1380 return $this->format('s');
1381 }
1382 }
1383
1384 /**
1385 * Setter to set an unit of DateTime
1386 * @param string $key
1387 * @param string|int $value
1388 * @return $this
1389 */
1390 public function __set($key, $value)
1391 {
1392 if ($key == 'year') {
1393 return $this->setDate($value, $this->format('m'), $this->format('d'));
1394 } elseif ($key == 'month') {
1395 return $this->setDate($this->format('Y'), $value, $this->format('d'));
1396 } elseif ($key == 'day') {
1397 return $this->setDate($this->format('Y'), $this->format('m'), $value);
1398 } elseif ($key == 'hour') {
1399 return $this->setTime($value, $this->format('i'), $this->format('s'));
1400 } elseif ($key == 'minute') {
1401 return $this->setTime($this->format('H'), $value, $this->format('s'));
1402 } elseif ($key == 'second') {
1403 return $this->setTime($this->format('H'), $this->format('i'), $value);
1404 }
1405 }
1406
1407 /**
1408 * Handle Dynamic calls (add/sub)
1409 *
1410 * @param string $method
1411 * @param array $params
1412 * @return $this
1413 */
1414 public function __call($method, $params)
1415 {
1416 // Dynamic Setter/Getter
1417 if (strpos($method, 'set') === 0) {
1418 $unit = strtolower(substr($method, 3));
1419 if ($params && in_array($unit, static::$singularUnits)) {
1420 $this->{$unit} = reset($params);
1421 return $this;
1422 }
1423 } elseif (strpos($method, 'get') === 0) {
1424 $unit = strtolower(substr($method, 3));
1425 if (in_array($unit, static::$singularUnits)) {
1426 return $this->{$unit};
1427 }
1428 }
1429
1430 // Dynamic adder/subtractor
1431 if (strpos($method, 'add') === 0) {
1432 $action = '+';
1433 } elseif (strpos($method, 'sub') === 0) {
1434 $action = '-';
1435 }
1436
1437 if (isset($action) && in_array($action, ['+', '-'])) {
1438
1439 if (!$params) {
1440 $duration = 1;
1441 } else {
1442 $duration = reset($params);
1443 }
1444
1445
1446 $unit = strtolower(substr($method, 3));
1447
1448 $units = array_merge(static::$singularUnits, static::$pluralUnits);
1449
1450 if (in_array($unit, $units)) {
1451 return $this->modify("{$action}{$duration}{$unit}");
1452 }
1453 }
1454
1455 throw new InvalidArgumentException("Call to undefined method {$method}.");
1456 }
1457 }
1458