-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
CarbonPeriod.php
2579 lines (2181 loc) · 84.1 KB
/
CarbonPeriod.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
declare(strict_types=1);
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Carbon;
use Carbon\Exceptions\EndLessPeriodException;
use Carbon\Exceptions\InvalidCastException;
use Carbon\Exceptions\InvalidIntervalException;
use Carbon\Exceptions\InvalidPeriodDateException;
use Carbon\Exceptions\InvalidPeriodParameterException;
use Carbon\Exceptions\NotACarbonClassException;
use Carbon\Exceptions\NotAPeriodException;
use Carbon\Exceptions\UnknownGetterException;
use Carbon\Exceptions\UnknownMethodException;
use Carbon\Exceptions\UnreachableException;
use Carbon\Traits\DeprecatedPeriodProperties;
use Carbon\Traits\IntervalRounding;
use Carbon\Traits\LocalFactory;
use Carbon\Traits\Mixin;
use Carbon\Traits\Options;
use Carbon\Traits\ToStringFormat;
use Closure;
use Countable;
use DateInterval;
use DatePeriod;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Generator;
use InvalidArgumentException;
use JsonSerializable;
use ReflectionException;
use ReturnTypeWillChange;
use RuntimeException;
use Throwable;
// @codeCoverageIgnoreStart
require PHP_VERSION < 8.2
? __DIR__.'/../../lazy/Carbon/ProtectedDatePeriod.php'
: __DIR__.'/../../lazy/Carbon/UnprotectedDatePeriod.php';
// @codeCoverageIgnoreEnd
/**
* Substitution of DatePeriod with some modifications and many more features.
*
* @method static static|CarbonInterface start($date = null, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance.
* @method static static since($date = null, $inclusive = null) Alias for start().
* @method static static sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance.
* @method static static|CarbonInterface end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance.
* @method static static until($date = null, $inclusive = null) Alias for end().
* @method static static untilNow($inclusive = null) Create instance with end date set to now or set the end date to now if called on an instance.
* @method static static dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance.
* @method static static between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance.
* @method static static recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance.
* @method static static times($recurrences = null) Alias for recurrences().
* @method static static|int|null options($options = null) Create instance with options or modify the options if called on an instance.
* @method static static toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance.
* @method static static filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance.
* @method static static push($callback, $name = null) Alias for filter().
* @method static static prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance.
* @method static static|array filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance.
* @method static static|CarbonInterval interval($interval = null) Create instance with given date interval or modify the interval if called on an instance.
* @method static static each($interval) Create instance with given date interval or modify the interval if called on an instance.
* @method static static every($interval) Create instance with given date interval or modify the interval if called on an instance.
* @method static static step($interval) Create instance with given date interval or modify the interval if called on an instance.
* @method static static stepBy($interval) Create instance with given date interval or modify the interval if called on an instance.
* @method static static invert() Create instance with inverted date interval or invert the interval if called on an instance.
* @method static static years($years = 1) Create instance specifying a number of years for date interval or replace the interval by the given a number of years if called on an instance.
* @method static static year($years = 1) Alias for years().
* @method static static months($months = 1) Create instance specifying a number of months for date interval or replace the interval by the given a number of months if called on an instance.
* @method static static month($months = 1) Alias for months().
* @method static static weeks($weeks = 1) Create instance specifying a number of weeks for date interval or replace the interval by the given a number of weeks if called on an instance.
* @method static static week($weeks = 1) Alias for weeks().
* @method static static days($days = 1) Create instance specifying a number of days for date interval or replace the interval by the given a number of days if called on an instance.
* @method static static dayz($days = 1) Alias for days().
* @method static static day($days = 1) Alias for days().
* @method static static hours($hours = 1) Create instance specifying a number of hours for date interval or replace the interval by the given a number of hours if called on an instance.
* @method static static hour($hours = 1) Alias for hours().
* @method static static minutes($minutes = 1) Create instance specifying a number of minutes for date interval or replace the interval by the given a number of minutes if called on an instance.
* @method static static minute($minutes = 1) Alias for minutes().
* @method static static seconds($seconds = 1) Create instance specifying a number of seconds for date interval or replace the interval by the given a number of seconds if called on an instance.
* @method static static second($seconds = 1) Alias for seconds().
* @method static static milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds for date interval or replace the interval by the given a number of milliseconds if called on an instance.
* @method static static millisecond($milliseconds = 1) Alias for milliseconds().
* @method static static microseconds($microseconds = 1) Create instance specifying a number of microseconds for date interval or replace the interval by the given a number of microseconds if called on an instance.
* @method static static microsecond($microseconds = 1) Alias for microseconds().
* @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
* @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
* @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision.
* @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision.
* @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision.
* @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision.
* @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
* @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
* @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision.
* @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision.
* @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision.
* @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision.
* @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
* @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
* @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision.
* @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision.
* @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision.
* @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision.
* @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
* @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
* @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision.
* @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision.
* @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision.
* @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision.
* @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
* @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
* @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision.
* @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision.
* @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision.
* @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision.
* @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
* @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
* @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision.
* @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision.
* @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision.
* @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision.
* @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
* @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
* @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision.
* @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision.
* @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision.
* @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision.
* @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
* @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
* @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision.
* @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision.
* @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision.
* @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision.
* @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
* @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
* @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision.
* @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision.
* @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision.
* @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision.
* @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
* @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
* @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision.
* @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision.
* @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision.
* @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision.
* @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
* @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
* @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision.
* @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision.
* @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision.
* @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision.
* @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
* @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
* @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision.
* @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision.
* @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision.
* @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision.
* @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
* @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
* @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision.
* @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision.
* @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision.
* @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision.
*
* @mixin DeprecatedPeriodProperties
*
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
{
use LocalFactory;
use IntervalRounding;
use Mixin {
Mixin::mixin as baseMixin;
}
use Options {
Options::__debugInfo as baseDebugInfo;
}
use ToStringFormat;
/**
* Built-in filter for limit by recurrences.
*
* @var callable
*/
public const RECURRENCES_FILTER = [self::class, 'filterRecurrences'];
/**
* Built-in filter for limit to an end.
*
* @var callable
*/
public const END_DATE_FILTER = [self::class, 'filterEndDate'];
/**
* Special value which can be returned by filters to end iteration. Also a filter.
*
* @var callable
*/
public const END_ITERATION = [self::class, 'endIteration'];
/**
* Exclude end date from iteration.
*
* @var int
*/
public const EXCLUDE_END_DATE = 8;
/**
* Yield CarbonImmutable instances.
*
* @var int
*/
public const IMMUTABLE = 4;
/**
* Number of maximum attempts before giving up on finding next valid date.
*
* @var int
*/
public const NEXT_MAX_ATTEMPTS = 1000;
/**
* Number of maximum attempts before giving up on finding end date.
*
* @var int
*/
public const END_MAX_ATTEMPTS = 10000;
/**
* Default date class of iteration items.
*
* @var string
*/
protected const DEFAULT_DATE_CLASS = Carbon::class;
/**
* The registered macros.
*/
protected static array $macros = [];
/**
* Date class of iteration items.
*/
protected string $dateClass = Carbon::class;
/**
* Underlying date interval instance. Always present, one day by default.
*/
protected ?CarbonInterval $dateInterval = null;
/**
* True once __construct is finished.
*/
protected bool $constructed = false;
/**
* Whether current date interval was set by default.
*/
protected bool $isDefaultInterval = false;
/**
* The filters stack.
*/
protected array $filters = [];
/**
* Period start date. Applied on rewind. Always present, now by default.
*/
protected ?CarbonInterface $startDate = null;
/**
* Period end date. For inverted interval should be before the start date. Applied via a filter.
*/
protected ?CarbonInterface $endDate = null;
/**
* Limit for number of recurrences. Applied via a filter.
*/
protected int|float|null $carbonRecurrences = null;
/**
* Iteration options.
*/
protected ?int $options = null;
/**
* Index of current date. Always sequential, even if some dates are skipped by filters.
* Equal to null only before the first iteration.
*/
protected int $key = 0;
/**
* Current date. May temporarily hold unaccepted value when looking for a next valid date.
* Equal to null only before the first iteration.
*/
protected ?CarbonInterface $carbonCurrent = null;
/**
* Timezone of current date. Taken from the start date.
*/
protected ?DateTimeZone $timezone = null;
/**
* The cached validation result for current date.
*/
protected array|string|bool|null $validationResult = null;
/**
* Timezone handler for settings() method.
*/
protected DateTimeZone|string|int|null $timezoneSetting = null;
public function getIterator(): Generator
{
$this->rewind();
while ($this->valid()) {
$key = $this->key();
$value = $this->current();
yield $key => $value;
$this->next();
}
}
/**
* Make a CarbonPeriod instance from given variable if possible.
*/
public static function make(mixed $var): ?static
{
try {
return static::instance($var);
} catch (NotAPeriodException) {
return static::create($var);
}
}
/**
* Create a new instance from a DatePeriod or CarbonPeriod object.
*/
public static function instance(mixed $period): static
{
if ($period instanceof static) {
return $period->copy();
}
if ($period instanceof self) {
return new static(
$period->getStartDate(),
$period->getEndDate() ?? $period->getRecurrences(),
$period->getDateInterval(),
$period->getOptions(),
);
}
if ($period instanceof DatePeriod) {
return new static(
$period->start,
$period->end ?: ($period->recurrences - 1),
$period->interval,
$period->include_start_date ? 0 : static::EXCLUDE_START_DATE,
);
}
$class = static::class;
$type = \gettype($period);
$chunks = explode('::', __METHOD__);
throw new NotAPeriodException(
'Argument 1 passed to '.$class.'::'.end($chunks).'() '.
'must be an instance of DatePeriod or '.$class.', '.
($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.',
);
}
/**
* Create a new instance.
*/
public static function create(...$params): static
{
return static::createFromArray($params);
}
/**
* Create a new instance from an array of parameters.
*/
public static function createFromArray(array $params): static
{
return new static(...$params);
}
/**
* Create CarbonPeriod from ISO 8601 string.
*/
public static function createFromIso(string $iso, ?int $options = null): static
{
$params = static::parseIso8601($iso);
$instance = static::createFromArray($params);
if ($options !== null) {
$instance->options = $options;
$instance->handleChangedParameters();
}
return $instance;
}
/**
* Return whether given interval contains non zero value of any time unit.
*/
protected static function intervalHasTime(DateInterval $interval): bool
{
return $interval->h || $interval->i || $interval->s || $interval->f;
}
/**
* Return whether given variable is an ISO 8601 specification.
*
* Note: Check is very basic, as actual validation will be done later when parsing.
* We just want to ensure that variable is not any other type of valid parameter.
*/
protected static function isIso8601(mixed $var): bool
{
if (!\is_string($var)) {
return false;
}
// Match slash but not within a timezone name.
$part = '[a-z]+(?:[_-][a-z]+)*';
preg_match("#\b$part/$part\b|(/)#i", $var, $match);
return isset($match[1]);
}
/**
* Parse given ISO 8601 string into an array of arguments.
*
* @SuppressWarnings(PHPMD.ElseExpression)
*/
protected static function parseIso8601(string $iso): array
{
$result = [];
$interval = null;
$start = null;
$end = null;
$dateClass = static::DEFAULT_DATE_CLASS;
foreach (explode('/', $iso) as $key => $part) {
if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) {
$parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : null;
} elseif ($interval === null && $parsed = self::makeInterval($part)) {
$interval = $part;
} elseif ($start === null && $parsed = $dateClass::make($part)) {
$start = $part;
} elseif ($end === null && $parsed = $dateClass::make(static::addMissingParts($start ?? '', $part))) {
$end = $part;
} else {
throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso.");
}
$result[] = $parsed;
}
return $result;
}
/**
* Add missing parts of the target date from the source date.
*/
protected static function addMissingParts(string $source, string $target): string
{
$pattern = '/'.preg_replace('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/';
$result = preg_replace($pattern, $target, $source, 1, $count);
return $count ? $result : $target;
}
private static function makeInterval(mixed $input): ?CarbonInterval
{
try {
return CarbonInterval::make($input);
} catch (Throwable) {
return null;
}
}
private static function makeTimezone(mixed $input): ?CarbonTimeZone
{
if (!\is_string($input)) {
return null;
}
try {
return CarbonTimeZone::create($input);
} catch (Throwable) {
return null;
}
}
/**
* Register a custom macro.
*
* Pass null macro to remove it.
*
* @example
* ```
* CarbonPeriod::macro('middle', function () {
* return $this->getStartDate()->average($this->getEndDate());
* });
* echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle();
* ```
*/
public static function macro(string $name, ?callable $macro): void
{
static::$macros[$name] = $macro;
}
/**
* Register macros from a mixin object.
*
* @example
* ```
* CarbonPeriod::mixin(new class {
* public function addDays() {
* return function ($count = 1) {
* return $this->setStartDate(
* $this->getStartDate()->addDays($count)
* )->setEndDate(
* $this->getEndDate()->addDays($count)
* );
* };
* }
* public function subDays() {
* return function ($count = 1) {
* return $this->setStartDate(
* $this->getStartDate()->subDays($count)
* )->setEndDate(
* $this->getEndDate()->subDays($count)
* );
* };
* }
* });
* echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3);
* ```
*
* @throws ReflectionException
*/
public static function mixin(object|string $mixin): void
{
static::baseMixin($mixin);
}
/**
* Check if macro is registered.
*/
public static function hasMacro(string $name): bool
{
return isset(static::$macros[$name]);
}
/**
* Provide static proxy for instance aliases.
*/
public static function __callStatic(string $method, array $parameters): mixed
{
$date = new static();
if (static::hasMacro($method)) {
return static::bindMacroContext(null, static fn () => $date->callMacro($method, $parameters));
}
return $date->$method(...$parameters);
}
/**
* CarbonPeriod constructor.
*
* @SuppressWarnings(PHPMD.ElseExpression)
*
* @throws InvalidArgumentException
*/
public function __construct(...$arguments)
{
$raw = null;
if (isset($arguments['raw'])) {
$raw = $arguments['raw'];
$this->isDefaultInterval = $arguments['isDefaultInterval'] ?? false;
if (isset($arguments['dateClass'])) {
$this->dateClass = $arguments['dateClass'];
}
$arguments = $raw;
}
// Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
// which will be first parsed into parts and then processed the same way.
$argumentsCount = \count($arguments);
if ($argumentsCount && static::isIso8601($iso = $arguments[0])) {
array_splice($arguments, 0, 1, static::parseIso8601($iso));
}
if ($argumentsCount === 1) {
if ($arguments[0] instanceof self) {
$arguments = [
$arguments[0]->getStartDate(),
$arguments[0]->getEndDate() ?? $arguments[0]->getRecurrences(),
$arguments[0]->getDateInterval(),
$arguments[0]->getOptions(),
];
} elseif ($arguments[0] instanceof DatePeriod) {
$arguments = [
$arguments[0]->start,
$arguments[0]->end ?: ($arguments[0]->recurrences - 1),
$arguments[0]->interval,
$arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE,
];
}
}
if (is_a($this->dateClass, DateTimeImmutable::class, true)) {
$this->options = static::IMMUTABLE;
}
$optionsSet = false;
$originalArguments = [];
$sortedArguments = [];
foreach ($arguments as $argument) {
$parsedDate = null;
if ($argument instanceof DateTimeZone) {
$sortedArguments = $this->configureTimezone($argument, $sortedArguments, $originalArguments);
} elseif (!isset($sortedArguments['interval']) &&
(
(\is_string($argument) && preg_match(
'/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i',
$argument,
)) ||
$argument instanceof DateInterval ||
$argument instanceof Closure ||
$argument instanceof Unit
) &&
$parsedInterval = self::makeInterval($argument)
) {
$sortedArguments['interval'] = $parsedInterval;
} elseif (!isset($sortedArguments['start']) && $parsedDate = $this->makeDateTime($argument)) {
$sortedArguments['start'] = $parsedDate;
$originalArguments['start'] = $argument;
} elseif (!isset($sortedArguments['end']) && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) {
$sortedArguments['end'] = $parsedDate;
$originalArguments['end'] = $argument;
} elseif (!isset($sortedArguments['recurrences']) &&
!isset($sortedArguments['end']) &&
(\is_int($argument) || \is_float($argument))
&& $argument >= 0
) {
$sortedArguments['recurrences'] = $argument;
} elseif (!$optionsSet && (\is_int($argument) || $argument === null)) {
$optionsSet = true;
$sortedArguments['options'] = (((int) $this->options) | ((int) $argument));
} elseif ($parsedTimezone = self::makeTimezone($argument)) {
$sortedArguments = $this->configureTimezone($parsedTimezone, $sortedArguments, $originalArguments);
} else {
throw new InvalidPeriodParameterException('Invalid constructor parameters.');
}
}
if ($raw === null && isset($sortedArguments['start'])) {
$end = $sortedArguments['end'] ?? max(1, $sortedArguments['recurrences'] ?? 1);
if (\is_float($end)) {
$end = $end === INF ? PHP_INT_MAX : (int) round($end);
}
$raw = [
$sortedArguments['start'],
$sortedArguments['interval'] ?? CarbonInterval::day(),
$end,
];
}
$this->setFromAssociativeArray($sortedArguments);
if ($this->startDate === null) {
$dateClass = $this->dateClass;
$this->setStartDate($dateClass::now());
}
if ($this->dateInterval === null) {
$this->setDateInterval(CarbonInterval::day());
$this->isDefaultInterval = true;
}
if ($this->options === null) {
$this->setOptions(0);
}
parent::__construct(
$this->startDate,
$this->dateInterval,
$this->endDate ?? $this->recurrences ?? 1,
$this->options,
);
$this->constructed = true;
}
/**
* Get a copy of the instance.
*/
public function copy(): static
{
return clone $this;
}
/**
* Prepare the instance to be set (self if mutable to be mutated,
* copy if immutable to generate a new instance).
*/
protected function copyIfImmutable(): static
{
return $this;
}
/**
* Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names.
*/
protected function getGetter(string $name): ?callable
{
return match (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) {
'start', 'start_date' => [$this, 'getStartDate'],
'end', 'end_date' => [$this, 'getEndDate'],
'interval', 'date_interval' => [$this, 'getDateInterval'],
'recurrences' => [$this, 'getRecurrences'],
'include_start_date' => [$this, 'isStartIncluded'],
'include_end_date' => [$this, 'isEndIncluded'],
'current' => [$this, 'current'],
'locale' => [$this, 'locale'],
'tzname', 'tz_name' => fn () => match (true) {
$this->timezoneSetting === null => null,
\is_string($this->timezoneSetting) => $this->timezoneSetting,
$this->timezoneSetting instanceof DateTimeZone => $this->timezoneSetting->getName(),
default => CarbonTimeZone::instance($this->timezoneSetting)->getName(),
},
default => null,
};
}
/**
* Get a property allowing both `DatePeriod` snakeCase and camelCase names.
*
* @param string $name
*
* @return bool|CarbonInterface|CarbonInterval|int|null
*/
public function get(string $name)
{
$getter = $this->getGetter($name);
if ($getter) {
return $getter();
}
throw new UnknownGetterException($name);
}
/**
* Get a property allowing both `DatePeriod` snakeCase and camelCase names.
*
* @param string $name
*
* @return bool|CarbonInterface|CarbonInterval|int|null
*/
public function __get(string $name)
{
return $this->get($name);
}
/**
* Check if an attribute exists on the object
*
* @param string $name
*
* @return bool
*/
public function __isset(string $name): bool
{
return $this->getGetter($name) !== null;
}
/**
* @alias copy
*
* Get a copy of the instance.
*
* @return static
*/
public function clone()
{
return clone $this;
}
/**
* Set the iteration item class.
*
* @param string $dateClass
*
* @return static
*/
public function setDateClass(string $dateClass)
{
if (!is_a($dateClass, CarbonInterface::class, true)) {
throw new NotACarbonClassException($dateClass);
}
$self = $this->copyIfImmutable();
$self->dateClass = $dateClass;
if (is_a($dateClass, Carbon::class, true)) {
$self->options = $self->options & ~static::IMMUTABLE;
} elseif (is_a($dateClass, CarbonImmutable::class, true)) {
$self->options = $self->options | static::IMMUTABLE;
}
return $self;
}
/**
* Returns iteration item date class.
*
* @return string
*/
public function getDateClass(): string
{
return $this->dateClass;
}
/**
* Change the period date interval.
*
* @param DateInterval|string|int $interval
* @param Unit|string $unit the unit of $interval if it's a number
*
* @throws InvalidIntervalException
*
* @return static
*/
public function setDateInterval(mixed $interval, Unit|string|null $unit = null): static
{
if ($interval instanceof Unit) {
$interval = $interval->interval();
}
if ($unit instanceof Unit) {
$unit = $unit->name;
}
if (!$interval = CarbonInterval::make($interval, $unit)) {
throw new InvalidIntervalException('Invalid interval.');
}
if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) {
throw new InvalidIntervalException('Empty interval is not accepted.');
}
$self = $this->copyIfImmutable();
$self->dateInterval = $interval;
$self->isDefaultInterval = false;
$self->handleChangedParameters();
return $self;
}
/**
* Reset the date interval to the default value.
*
* Difference with simply setting interval to 1-day is that P1D will not appear when calling toIso8601String()
* and also next adding to the interval won't include the default 1-day.
*/
public function resetDateInterval(): static
{
$self = $this->copyIfImmutable();
$self->setDateInterval(CarbonInterval::day());
$self->isDefaultInterval = true;
return $self;
}
/**
* Invert the period date interval.
*/
public function invertDateInterval(): static
{
return $this->setDateInterval($this->dateInterval->invert());
}
/**
* Set start and end date.
*
* @param DateTime|DateTimeInterface|string $start
* @param DateTime|DateTimeInterface|string|null $end
*
* @return static
*/
public function setDates(mixed $start, mixed $end): static
{
return $this->setStartDate($start)->setEndDate($end);
}
/**
* Change the period options.
*
* @param int|null $options
*
* @return static
*/
public function setOptions(?int $options): static
{
$self = $this->copyIfImmutable();
$self->options = $options ?? 0;
$self->handleChangedParameters();
return $self;
}
/**
* Get the period options.
*/
public function getOptions(): int
{
return $this->options ?? 0;
}
/**
* Toggle given options on or off.
*
* @param int $options
* @param bool|null $state
*
* @throws InvalidArgumentException
*
* @return static
*/
public function toggleOptions(int $options, ?bool $state = null): static
{
$self = $this->copyIfImmutable();
if ($state === null) {
$state = ($this->options & $options) !== $options;
}
return $self->setOptions(
$state ?
$this->options | $options :
$this->options & ~$options,
);
}
/**
* Toggle EXCLUDE_START_DATE option.
*/
public function excludeStartDate(bool $state = true): static
{
return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
}
/**
* Toggle EXCLUDE_END_DATE option.
*/
public function excludeEndDate(bool $state = true): static
{
return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);