blob: 70e6ba49a07426ef0d44f088965f1487b28c8071 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3/**
4 * This file is part of the Carbon package.
5 *
6 * (c) Brian Nesbitt <brian@nesbot.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11namespace Carbon;
12
13use Carbon\Exceptions\InvalidCastException;
14use Carbon\Exceptions\InvalidIntervalException;
15use Carbon\Exceptions\InvalidPeriodDateException;
16use Carbon\Exceptions\InvalidPeriodParameterException;
17use Carbon\Exceptions\NotACarbonClassException;
18use Carbon\Exceptions\NotAPeriodException;
19use Carbon\Exceptions\UnknownGetterException;
20use Carbon\Exceptions\UnknownMethodException;
21use Carbon\Exceptions\UnreachableException;
22use Carbon\Traits\IntervalRounding;
23use Carbon\Traits\Mixin;
24use Carbon\Traits\Options;
25use Closure;
26use Countable;
27use DateInterval;
28use DatePeriod;
29use DateTime;
30use DateTimeInterface;
31use InvalidArgumentException;
32use Iterator;
33use JsonSerializable;
34use ReflectionException;
35use ReturnTypeWillChange;
36use RuntimeException;
37
38/**
39 * Substitution of DatePeriod with some modifications and many more features.
40 *
41 * @property-read int|float $recurrences number of recurrences (if end not set).
42 * @property-read bool $include_start_date rather the start date is included in the iteration.
43 * @property-read bool $include_end_date rather the end date is included in the iteration (if recurrences not set).
44 * @property-read CarbonInterface $start Period start date.
45 * @property-read CarbonInterface $current Current date from the iteration.
46 * @property-read CarbonInterface $end Period end date.
47 * @property-read CarbonInterval $interval Underlying date interval instance. Always present, one day by default.
48 *
49 * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date or modify the start date if called on an instance.
50 * @method static CarbonPeriod since($date, $inclusive = null) Alias for start().
51 * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now or set the start date to now if called on an instance.
52 * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date or modify the end date if called on an instance.
53 * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end().
54 * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now or set the end date to now if called on an instance.
55 * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance.
56 * @method static CarbonPeriod between($start, $end = null) Create instance with start and end dates or modify the start and end dates if called on an instance.
57 * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences or modify the number of recurrences if called on an instance.
58 * @method static CarbonPeriod times($recurrences = null) Alias for recurrences().
59 * @method static CarbonPeriod options($options = null) Create instance with options or modify the options if called on an instance.
60 * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off, or toggle options if called on an instance.
61 * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack or append a filter if called on an instance.
62 * @method static CarbonPeriod push($callback, $name = null) Alias for filter().
63 * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepended to the stack or prepend a filter if called on an instance.
64 * @method static CarbonPeriod filters(array $filters = []) Create instance with filters stack or replace the whole filters stack if called on an instance.
65 * @method static CarbonPeriod interval($interval) Create instance with given date interval or modify the interval if called on an instance.
66 * @method static CarbonPeriod each($interval) Create instance with given date interval or modify the interval if called on an instance.
67 * @method static CarbonPeriod every($interval) Create instance with given date interval or modify the interval if called on an instance.
68 * @method static CarbonPeriod step($interval) Create instance with given date interval or modify the interval if called on an instance.
69 * @method static CarbonPeriod stepBy($interval) Create instance with given date interval or modify the interval if called on an instance.
70 * @method static CarbonPeriod invert() Create instance with inverted date interval or invert the interval if called on an instance.
71 * @method static CarbonPeriod 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.
72 * @method static CarbonPeriod year($years = 1) Alias for years().
73 * @method static CarbonPeriod 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.
74 * @method static CarbonPeriod month($months = 1) Alias for months().
75 * @method static CarbonPeriod 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.
76 * @method static CarbonPeriod week($weeks = 1) Alias for weeks().
77 * @method static CarbonPeriod 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.
78 * @method static CarbonPeriod dayz($days = 1) Alias for days().
79 * @method static CarbonPeriod day($days = 1) Alias for days().
80 * @method static CarbonPeriod 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.
81 * @method static CarbonPeriod hour($hours = 1) Alias for hours().
82 * @method static CarbonPeriod 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.
83 * @method static CarbonPeriod minute($minutes = 1) Alias for minutes().
84 * @method static CarbonPeriod 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.
85 * @method static CarbonPeriod second($seconds = 1) Alias for seconds().
86 * @method $this roundYear(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
87 * @method $this roundYears(float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
88 * @method $this floorYear(float $precision = 1) Truncate the current instance year with given precision.
89 * @method $this floorYears(float $precision = 1) Truncate the current instance year with given precision.
90 * @method $this ceilYear(float $precision = 1) Ceil the current instance year with given precision.
91 * @method $this ceilYears(float $precision = 1) Ceil the current instance year with given precision.
92 * @method $this roundMonth(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
93 * @method $this roundMonths(float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
94 * @method $this floorMonth(float $precision = 1) Truncate the current instance month with given precision.
95 * @method $this floorMonths(float $precision = 1) Truncate the current instance month with given precision.
96 * @method $this ceilMonth(float $precision = 1) Ceil the current instance month with given precision.
97 * @method $this ceilMonths(float $precision = 1) Ceil the current instance month with given precision.
98 * @method $this roundWeek(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
99 * @method $this roundWeeks(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
100 * @method $this floorWeek(float $precision = 1) Truncate the current instance day with given precision.
101 * @method $this floorWeeks(float $precision = 1) Truncate the current instance day with given precision.
102 * @method $this ceilWeek(float $precision = 1) Ceil the current instance day with given precision.
103 * @method $this ceilWeeks(float $precision = 1) Ceil the current instance day with given precision.
104 * @method $this roundDay(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
105 * @method $this roundDays(float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
106 * @method $this floorDay(float $precision = 1) Truncate the current instance day with given precision.
107 * @method $this floorDays(float $precision = 1) Truncate the current instance day with given precision.
108 * @method $this ceilDay(float $precision = 1) Ceil the current instance day with given precision.
109 * @method $this ceilDays(float $precision = 1) Ceil the current instance day with given precision.
110 * @method $this roundHour(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
111 * @method $this roundHours(float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
112 * @method $this floorHour(float $precision = 1) Truncate the current instance hour with given precision.
113 * @method $this floorHours(float $precision = 1) Truncate the current instance hour with given precision.
114 * @method $this ceilHour(float $precision = 1) Ceil the current instance hour with given precision.
115 * @method $this ceilHours(float $precision = 1) Ceil the current instance hour with given precision.
116 * @method $this roundMinute(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
117 * @method $this roundMinutes(float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
118 * @method $this floorMinute(float $precision = 1) Truncate the current instance minute with given precision.
119 * @method $this floorMinutes(float $precision = 1) Truncate the current instance minute with given precision.
120 * @method $this ceilMinute(float $precision = 1) Ceil the current instance minute with given precision.
121 * @method $this ceilMinutes(float $precision = 1) Ceil the current instance minute with given precision.
122 * @method $this roundSecond(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
123 * @method $this roundSeconds(float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
124 * @method $this floorSecond(float $precision = 1) Truncate the current instance second with given precision.
125 * @method $this floorSeconds(float $precision = 1) Truncate the current instance second with given precision.
126 * @method $this ceilSecond(float $precision = 1) Ceil the current instance second with given precision.
127 * @method $this ceilSeconds(float $precision = 1) Ceil the current instance second with given precision.
128 * @method $this roundMillennium(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
129 * @method $this roundMillennia(float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
130 * @method $this floorMillennium(float $precision = 1) Truncate the current instance millennium with given precision.
131 * @method $this floorMillennia(float $precision = 1) Truncate the current instance millennium with given precision.
132 * @method $this ceilMillennium(float $precision = 1) Ceil the current instance millennium with given precision.
133 * @method $this ceilMillennia(float $precision = 1) Ceil the current instance millennium with given precision.
134 * @method $this roundCentury(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
135 * @method $this roundCenturies(float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
136 * @method $this floorCentury(float $precision = 1) Truncate the current instance century with given precision.
137 * @method $this floorCenturies(float $precision = 1) Truncate the current instance century with given precision.
138 * @method $this ceilCentury(float $precision = 1) Ceil the current instance century with given precision.
139 * @method $this ceilCenturies(float $precision = 1) Ceil the current instance century with given precision.
140 * @method $this roundDecade(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
141 * @method $this roundDecades(float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
142 * @method $this floorDecade(float $precision = 1) Truncate the current instance decade with given precision.
143 * @method $this floorDecades(float $precision = 1) Truncate the current instance decade with given precision.
144 * @method $this ceilDecade(float $precision = 1) Ceil the current instance decade with given precision.
145 * @method $this ceilDecades(float $precision = 1) Ceil the current instance decade with given precision.
146 * @method $this roundQuarter(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
147 * @method $this roundQuarters(float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
148 * @method $this floorQuarter(float $precision = 1) Truncate the current instance quarter with given precision.
149 * @method $this floorQuarters(float $precision = 1) Truncate the current instance quarter with given precision.
150 * @method $this ceilQuarter(float $precision = 1) Ceil the current instance quarter with given precision.
151 * @method $this ceilQuarters(float $precision = 1) Ceil the current instance quarter with given precision.
152 * @method $this roundMillisecond(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
153 * @method $this roundMilliseconds(float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
154 * @method $this floorMillisecond(float $precision = 1) Truncate the current instance millisecond with given precision.
155 * @method $this floorMilliseconds(float $precision = 1) Truncate the current instance millisecond with given precision.
156 * @method $this ceilMillisecond(float $precision = 1) Ceil the current instance millisecond with given precision.
157 * @method $this ceilMilliseconds(float $precision = 1) Ceil the current instance millisecond with given precision.
158 * @method $this roundMicrosecond(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
159 * @method $this roundMicroseconds(float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
160 * @method $this floorMicrosecond(float $precision = 1) Truncate the current instance microsecond with given precision.
161 * @method $this floorMicroseconds(float $precision = 1) Truncate the current instance microsecond with given precision.
162 * @method $this ceilMicrosecond(float $precision = 1) Ceil the current instance microsecond with given precision.
163 * @method $this ceilMicroseconds(float $precision = 1) Ceil the current instance microsecond with given precision.
164 */
165class CarbonPeriod implements Iterator, Countable, JsonSerializable
166{
167 use IntervalRounding;
168 use Mixin {
169 Mixin::mixin as baseMixin;
170 }
171 use Options;
172
173 /**
174 * Built-in filters.
175 *
176 * @var string
177 */
178 public const RECURRENCES_FILTER = [self::class, 'filterRecurrences'];
179 public const END_DATE_FILTER = [self::class, 'filterEndDate'];
180
181 /**
182 * Special value which can be returned by filters to end iteration. Also a filter.
183 *
184 * @var string
185 */
186 public const END_ITERATION = [self::class, 'endIteration'];
187
188 /**
189 * Exclude start date from iteration.
190 *
191 * @var int
192 */
193 public const EXCLUDE_START_DATE = 1;
194
195 /**
196 * Exclude end date from iteration.
197 *
198 * @var int
199 */
200 public const EXCLUDE_END_DATE = 2;
201
202 /**
203 * Yield CarbonImmutable instances.
204 *
205 * @var int
206 */
207 public const IMMUTABLE = 4;
208
209 /**
210 * Number of maximum attempts before giving up on finding next valid date.
211 *
212 * @var int
213 */
214 public const NEXT_MAX_ATTEMPTS = 1000;
215
216 /**
217 * Number of maximum attempts before giving up on finding end date.
218 *
219 * @var int
220 */
221 public const END_MAX_ATTEMPTS = 10000;
222
223 /**
224 * The registered macros.
225 *
226 * @var array
227 */
228 protected static $macros = [];
229
230 /**
231 * Date class of iteration items.
232 *
233 * @var string
234 */
235 protected $dateClass = Carbon::class;
236
237 /**
238 * Underlying date interval instance. Always present, one day by default.
239 *
240 * @var CarbonInterval
241 */
242 protected $dateInterval;
243
244 /**
245 * Whether current date interval was set by default.
246 *
247 * @var bool
248 */
249 protected $isDefaultInterval;
250
251 /**
252 * The filters stack.
253 *
254 * @var array
255 */
256 protected $filters = [];
257
258 /**
259 * Period start date. Applied on rewind. Always present, now by default.
260 *
261 * @var CarbonInterface
262 */
263 protected $startDate;
264
265 /**
266 * Period end date. For inverted interval should be before the start date. Applied via a filter.
267 *
268 * @var CarbonInterface|null
269 */
270 protected $endDate;
271
272 /**
273 * Limit for number of recurrences. Applied via a filter.
274 *
275 * @var int|null
276 */
277 protected $recurrences;
278
279 /**
280 * Iteration options.
281 *
282 * @var int
283 */
284 protected $options;
285
286 /**
287 * Index of current date. Always sequential, even if some dates are skipped by filters.
288 * Equal to null only before the first iteration.
289 *
290 * @var int
291 */
292 protected $key;
293
294 /**
295 * Current date. May temporarily hold unaccepted value when looking for a next valid date.
296 * Equal to null only before the first iteration.
297 *
298 * @var CarbonInterface
299 */
300 protected $current;
301
302 /**
303 * Timezone of current date. Taken from the start date.
304 *
305 * @var \DateTimeZone|null
306 */
307 protected $timezone;
308
309 /**
310 * The cached validation result for current date.
311 *
312 * @var bool|string|null
313 */
314 protected $validationResult;
315
316 /**
317 * Timezone handler for settings() method.
318 *
319 * @var mixed
320 */
321 protected $tzName;
322
323 /**
324 * Make a CarbonPeriod instance from given variable if possible.
325 *
326 * @param mixed $var
327 *
328 * @return static|null
329 */
330 public static function make($var)
331 {
332 try {
333 return static::instance($var);
334 } catch (NotAPeriodException $e) {
335 return static::create($var);
336 }
337 }
338
339 /**
340 * Create a new instance from a DatePeriod or CarbonPeriod object.
341 *
342 * @param CarbonPeriod|DatePeriod $period
343 *
344 * @return static
345 */
346 public static function instance($period)
347 {
348 if ($period instanceof static) {
349 return $period->copy();
350 }
351
352 if ($period instanceof self) {
353 return new static(
354 $period->getStartDate(),
355 $period->getEndDate() ?: $period->getRecurrences(),
356 $period->getDateInterval(),
357 $period->getOptions()
358 );
359 }
360
361 if ($period instanceof DatePeriod) {
362 return new static(
363 $period->start,
364 $period->end ?: ($period->recurrences - 1),
365 $period->interval,
366 $period->include_start_date ? 0 : static::EXCLUDE_START_DATE
367 );
368 }
369
370 $class = \get_called_class();
371 $type = \gettype($period);
372
373 throw new NotAPeriodException(
374 'Argument 1 passed to '.$class.'::'.__METHOD__.'() '.
375 'must be an instance of DatePeriod or '.$class.', '.
376 ($type === 'object' ? 'instance of '.\get_class($period) : $type).' given.'
377 );
378 }
379
380 /**
381 * Create a new instance.
382 *
383 * @return static
384 */
385 public static function create(...$params)
386 {
387 return static::createFromArray($params);
388 }
389
390 /**
391 * Create a new instance from an array of parameters.
392 *
393 * @param array $params
394 *
395 * @return static
396 */
397 public static function createFromArray(array $params)
398 {
399 return new static(...$params);
400 }
401
402 /**
403 * Create CarbonPeriod from ISO 8601 string.
404 *
405 * @param string $iso
406 * @param int|null $options
407 *
408 * @return static
409 */
410 public static function createFromIso($iso, $options = null)
411 {
412 $params = static::parseIso8601($iso);
413
414 $instance = static::createFromArray($params);
415
416 if ($options !== null) {
417 $instance->setOptions($options);
418 }
419
420 return $instance;
421 }
422
423 /**
424 * Return whether given interval contains non zero value of any time unit.
425 *
426 * @param \DateInterval $interval
427 *
428 * @return bool
429 */
430 protected static function intervalHasTime(DateInterval $interval)
431 {
432 return $interval->h || $interval->i || $interval->s || $interval->f;
433 }
434
435 /**
436 * Return whether given variable is an ISO 8601 specification.
437 *
438 * Note: Check is very basic, as actual validation will be done later when parsing.
439 * We just want to ensure that variable is not any other type of a valid parameter.
440 *
441 * @param mixed $var
442 *
443 * @return bool
444 */
445 protected static function isIso8601($var)
446 {
447 if (!\is_string($var)) {
448 return false;
449 }
450
451 // Match slash but not within a timezone name.
452 $part = '[a-z]+(?:[_-][a-z]+)*';
453
454 preg_match("#\b$part/$part\b|(/)#i", $var, $match);
455
456 return isset($match[1]);
457 }
458
459 /**
460 * Parse given ISO 8601 string into an array of arguments.
461 *
462 * @SuppressWarnings(PHPMD.ElseExpression)
463 *
464 * @param string $iso
465 *
466 * @return array
467 */
468 protected static function parseIso8601($iso)
469 {
470 $result = [];
471
472 $interval = null;
473 $start = null;
474 $end = null;
475
476 foreach (explode('/', $iso) as $key => $part) {
477 if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) {
478 $parsed = \strlen($match[1]) ? (int) $match[1] : null;
479 } elseif ($interval === null && $parsed = CarbonInterval::make($part)) {
480 $interval = $part;
481 } elseif ($start === null && $parsed = Carbon::make($part)) {
482 $start = $part;
483 } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start ?? '', $part))) {
484 $end = $part;
485 } else {
486 throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso.");
487 }
488
489 $result[] = $parsed;
490 }
491
492 return $result;
493 }
494
495 /**
496 * Add missing parts of the target date from the soure date.
497 *
498 * @param string $source
499 * @param string $target
500 *
501 * @return string
502 */
503 protected static function addMissingParts($source, $target)
504 {
505 $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/';
506
507 $result = preg_replace($pattern, $target, $source, 1, $count);
508
509 return $count ? $result : $target;
510 }
511
512 /**
513 * Register a custom macro.
514 *
515 * @example
516 * ```
517 * CarbonPeriod::macro('middle', function () {
518 * return $this->getStartDate()->average($this->getEndDate());
519 * });
520 * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle();
521 * ```
522 *
523 * @param string $name
524 * @param object|callable $macro
525 *
526 * @return void
527 */
528 public static function macro($name, $macro)
529 {
530 static::$macros[$name] = $macro;
531 }
532
533 /**
534 * Register macros from a mixin object.
535 *
536 * @example
537 * ```
538 * CarbonPeriod::mixin(new class {
539 * public function addDays() {
540 * return function ($count = 1) {
541 * return $this->setStartDate(
542 * $this->getStartDate()->addDays($count)
543 * )->setEndDate(
544 * $this->getEndDate()->addDays($count)
545 * );
546 * };
547 * }
548 * public function subDays() {
549 * return function ($count = 1) {
550 * return $this->setStartDate(
551 * $this->getStartDate()->subDays($count)
552 * )->setEndDate(
553 * $this->getEndDate()->subDays($count)
554 * );
555 * };
556 * }
557 * });
558 * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3);
559 * ```
560 *
561 * @param object|string $mixin
562 *
563 * @throws ReflectionException
564 *
565 * @return void
566 */
567 public static function mixin($mixin)
568 {
569 static::baseMixin($mixin);
570 }
571
572 /**
573 * Check if macro is registered.
574 *
575 * @param string $name
576 *
577 * @return bool
578 */
579 public static function hasMacro($name)
580 {
581 return isset(static::$macros[$name]);
582 }
583
584 /**
585 * Provide static proxy for instance aliases.
586 *
587 * @param string $method
588 * @param array $parameters
589 *
590 * @return mixed
591 */
592 public static function __callStatic($method, $parameters)
593 {
594 $date = new static();
595
596 if (static::hasMacro($method)) {
597 return static::bindMacroContext(null, function () use (&$method, &$parameters, &$date) {
598 return $date->callMacro($method, $parameters);
599 });
600 }
601
602 return $date->$method(...$parameters);
603 }
604
605 /**
606 * CarbonPeriod constructor.
607 *
608 * @SuppressWarnings(PHPMD.ElseExpression)
609 *
610 * @throws InvalidArgumentException
611 */
612 public function __construct(...$arguments)
613 {
614 // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
615 // which will be first parsed into parts and then processed the same way.
616
617 $argumentsCount = \count($arguments);
618
619 if ($argumentsCount && static::isIso8601($iso = $arguments[0])) {
620 array_splice($arguments, 0, 1, static::parseIso8601($iso));
621 }
622
623 if ($argumentsCount === 1) {
624 if ($arguments[0] instanceof DatePeriod) {
625 $arguments = [
626 $arguments[0]->start,
627 $arguments[0]->end ?: ($arguments[0]->recurrences - 1),
628 $arguments[0]->interval,
629 $arguments[0]->include_start_date ? 0 : static::EXCLUDE_START_DATE,
630 ];
631 } elseif ($arguments[0] instanceof self) {
632 $arguments = [
633 $arguments[0]->getStartDate(),
634 $arguments[0]->getEndDate() ?: $arguments[0]->getRecurrences(),
635 $arguments[0]->getDateInterval(),
636 $arguments[0]->getOptions(),
637 ];
638 }
639 }
640
641 foreach ($arguments as $argument) {
642 if ($this->dateInterval === null &&
643 (
644 \is_string($argument) && preg_match(
645 '/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T0-9].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i',
646 $argument
647 ) ||
648 $argument instanceof DateInterval ||
649 $argument instanceof Closure
650 ) &&
651 $parsed = @CarbonInterval::make($argument)
652 ) {
653 $this->setDateInterval($parsed);
654 } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) {
655 $this->setStartDate($parsed);
656 } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) {
657 $this->setEndDate($parsed);
658 } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) {
659 $this->setRecurrences($argument);
660 } elseif ($this->options === null && (\is_int($argument) || $argument === null)) {
661 $this->setOptions($argument);
662 } else {
663 throw new InvalidPeriodParameterException('Invalid constructor parameters.');
664 }
665 }
666
667 if ($this->startDate === null) {
668 $this->setStartDate(Carbon::now());
669 }
670
671 if ($this->dateInterval === null) {
672 $this->setDateInterval(CarbonInterval::day());
673
674 $this->isDefaultInterval = true;
675 }
676
677 if ($this->options === null) {
678 $this->setOptions(0);
679 }
680 }
681
682 /**
683 * Get a copy of the instance.
684 *
685 * @return static
686 */
687 public function copy()
688 {
689 return clone $this;
690 }
691
692 /**
693 * Get the getter for a property allowing both `DatePeriod` snakeCase and camelCase names.
694 *
695 * @param string $name
696 *
697 * @return callable|null
698 */
699 protected function getGetter(string $name)
700 {
701 switch (strtolower(preg_replace('/[A-Z]/', '_$0', $name))) {
702 case 'start':
703 case 'start_date':
704 return [$this, 'getStartDate'];
705 case 'end':
706 case 'end_date':
707 return [$this, 'getEndDate'];
708 case 'interval':
709 case 'date_interval':
710 return [$this, 'getDateInterval'];
711 case 'recurrences':
712 return [$this, 'getRecurrences'];
713 case 'include_start_date':
714 return [$this, 'isStartIncluded'];
715 case 'include_end_date':
716 return [$this, 'isEndIncluded'];
717 case 'current':
718 return [$this, 'current'];
719 default:
720 return null;
721 }
722 }
723
724 /**
725 * Get a property allowing both `DatePeriod` snakeCase and camelCase names.
726 *
727 * @param string $name
728 *
729 * @return bool|CarbonInterface|CarbonInterval|int|null
730 */
731 public function get(string $name)
732 {
733 $getter = $this->getGetter($name);
734
735 if ($getter) {
736 return $getter();
737 }
738
739 throw new UnknownGetterException($name);
740 }
741
742 /**
743 * Get a property allowing both `DatePeriod` snakeCase and camelCase names.
744 *
745 * @param string $name
746 *
747 * @return bool|CarbonInterface|CarbonInterval|int|null
748 */
749 public function __get(string $name)
750 {
751 return $this->get($name);
752 }
753
754 /**
755 * Check if an attribute exists on the object
756 *
757 * @param string $name
758 *
759 * @return bool
760 */
761 public function __isset(string $name): bool
762 {
763 return $this->getGetter($name) !== null;
764 }
765
766 /**
767 * @alias copy
768 *
769 * Get a copy of the instance.
770 *
771 * @return static
772 */
773 public function clone()
774 {
775 return clone $this;
776 }
777
778 /**
779 * Set the iteration item class.
780 *
781 * @param string $dateClass
782 *
783 * @return $this
784 */
785 public function setDateClass(string $dateClass)
786 {
787 if (!is_a($dateClass, CarbonInterface::class, true)) {
788 throw new NotACarbonClassException($dateClass);
789 }
790
791 $this->dateClass = $dateClass;
792
793 if (is_a($dateClass, Carbon::class, true)) {
794 $this->toggleOptions(static::IMMUTABLE, false);
795 } elseif (is_a($dateClass, CarbonImmutable::class, true)) {
796 $this->toggleOptions(static::IMMUTABLE, true);
797 }
798
799 return $this;
800 }
801
802 /**
803 * Returns iteration item date class.
804 *
805 * @return string
806 */
807 public function getDateClass(): string
808 {
809 return $this->dateClass;
810 }
811
812 /**
813 * Change the period date interval.
814 *
815 * @param DateInterval|string $interval
816 *
817 * @throws InvalidIntervalException
818 *
819 * @return $this
820 */
821 public function setDateInterval($interval)
822 {
823 if (!$interval = CarbonInterval::make($interval)) {
824 throw new InvalidIntervalException('Invalid interval.');
825 }
826
827 if ($interval->spec() === 'PT0S' && !$interval->f && !$interval->getStep()) {
828 throw new InvalidIntervalException('Empty interval is not accepted.');
829 }
830
831 $this->dateInterval = $interval;
832
833 $this->isDefaultInterval = false;
834
835 $this->handleChangedParameters();
836
837 return $this;
838 }
839
840 /**
841 * Invert the period date interval.
842 *
843 * @return $this
844 */
845 public function invertDateInterval()
846 {
847 $interval = $this->dateInterval->invert();
848
849 return $this->setDateInterval($interval);
850 }
851
852 /**
853 * Set start and end date.
854 *
855 * @param DateTime|DateTimeInterface|string $start
856 * @param DateTime|DateTimeInterface|string|null $end
857 *
858 * @return $this
859 */
860 public function setDates($start, $end)
861 {
862 $this->setStartDate($start);
863 $this->setEndDate($end);
864
865 return $this;
866 }
867
868 /**
869 * Change the period options.
870 *
871 * @param int|null $options
872 *
873 * @throws InvalidArgumentException
874 *
875 * @return $this
876 */
877 public function setOptions($options)
878 {
879 if (!\is_int($options) && $options !== null) {
880 throw new InvalidPeriodParameterException('Invalid options.');
881 }
882
883 $this->options = $options ?: 0;
884
885 $this->handleChangedParameters();
886
887 return $this;
888 }
889
890 /**
891 * Get the period options.
892 *
893 * @return int
894 */
895 public function getOptions()
896 {
897 return $this->options;
898 }
899
900 /**
901 * Toggle given options on or off.
902 *
903 * @param int $options
904 * @param bool|null $state
905 *
906 * @throws \InvalidArgumentException
907 *
908 * @return $this
909 */
910 public function toggleOptions($options, $state = null)
911 {
912 if ($state === null) {
913 $state = ($this->options & $options) !== $options;
914 }
915
916 return $this->setOptions(
917 $state ?
918 $this->options | $options :
919 $this->options & ~$options
920 );
921 }
922
923 /**
924 * Toggle EXCLUDE_START_DATE option.
925 *
926 * @param bool $state
927 *
928 * @return $this
929 */
930 public function excludeStartDate($state = true)
931 {
932 return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
933 }
934
935 /**
936 * Toggle EXCLUDE_END_DATE option.
937 *
938 * @param bool $state
939 *
940 * @return $this
941 */
942 public function excludeEndDate($state = true)
943 {
944 return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);
945 }
946
947 /**
948 * Get the underlying date interval.
949 *
950 * @return CarbonInterval
951 */
952 public function getDateInterval()
953 {
954 return $this->dateInterval->copy();
955 }
956
957 /**
958 * Get start date of the period.
959 *
960 * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval.
961 *
962 * @return CarbonInterface
963 */
964 public function getStartDate(string $rounding = null)
965 {
966 $date = $this->startDate->avoidMutation();
967
968 return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date;
969 }
970
971 /**
972 * Get end date of the period.
973 *
974 * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval.
975 *
976 * @return CarbonInterface|null
977 */
978 public function getEndDate(string $rounding = null)
979 {
980 if (!$this->endDate) {
981 return null;
982 }
983
984 $date = $this->endDate->avoidMutation();
985
986 return $rounding ? $date->round($this->getDateInterval(), $rounding) : $date;
987 }
988
989 /**
990 * Get number of recurrences.
991 *
992 * @return int|float|null
993 */
994 public function getRecurrences()
995 {
996 return $this->recurrences;
997 }
998
999 /**
1000 * Returns true if the start date should be excluded.
1001 *
1002 * @return bool
1003 */
1004 public function isStartExcluded()
1005 {
1006 return ($this->options & static::EXCLUDE_START_DATE) !== 0;
1007 }
1008
1009 /**
1010 * Returns true if the end date should be excluded.
1011 *
1012 * @return bool
1013 */
1014 public function isEndExcluded()
1015 {
1016 return ($this->options & static::EXCLUDE_END_DATE) !== 0;
1017 }
1018
1019 /**
1020 * Returns true if the start date should be included.
1021 *
1022 * @return bool
1023 */
1024 public function isStartIncluded()
1025 {
1026 return !$this->isStartExcluded();
1027 }
1028
1029 /**
1030 * Returns true if the end date should be included.
1031 *
1032 * @return bool
1033 */
1034 public function isEndIncluded()
1035 {
1036 return !$this->isEndExcluded();
1037 }
1038
1039 /**
1040 * Return the start if it's included by option, else return the start + 1 period interval.
1041 *
1042 * @return CarbonInterface
1043 */
1044 public function getIncludedStartDate()
1045 {
1046 $start = $this->getStartDate();
1047
1048 if ($this->isStartExcluded()) {
1049 return $start->add($this->getDateInterval());
1050 }
1051
1052 return $start;
1053 }
1054
1055 /**
1056 * Return the end if it's included by option, else return the end - 1 period interval.
1057 * Warning: if the period has no fixed end, this method will iterate the period to calculate it.
1058 *
1059 * @return CarbonInterface
1060 */
1061 public function getIncludedEndDate()
1062 {
1063 $end = $this->getEndDate();
1064
1065 if (!$end) {
1066 return $this->calculateEnd();
1067 }
1068
1069 if ($this->isEndExcluded()) {
1070 return $end->sub($this->getDateInterval());
1071 }
1072
1073 return $end;
1074 }
1075
1076 /**
1077 * Add a filter to the stack.
1078 *
1079 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
1080 *
1081 * @param callable $callback
1082 * @param string $name
1083 *
1084 * @return $this
1085 */
1086 public function addFilter($callback, $name = null)
1087 {
1088 $tuple = $this->createFilterTuple(\func_get_args());
1089
1090 $this->filters[] = $tuple;
1091
1092 $this->handleChangedParameters();
1093
1094 return $this;
1095 }
1096
1097 /**
1098 * Prepend a filter to the stack.
1099 *
1100 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
1101 *
1102 * @param callable $callback
1103 * @param string $name
1104 *
1105 * @return $this
1106 */
1107 public function prependFilter($callback, $name = null)
1108 {
1109 $tuple = $this->createFilterTuple(\func_get_args());
1110
1111 array_unshift($this->filters, $tuple);
1112
1113 $this->handleChangedParameters();
1114
1115 return $this;
1116 }
1117
1118 /**
1119 * Remove a filter by instance or name.
1120 *
1121 * @param callable|string $filter
1122 *
1123 * @return $this
1124 */
1125 public function removeFilter($filter)
1126 {
1127 $key = \is_callable($filter) ? 0 : 1;
1128
1129 $this->filters = array_values(array_filter(
1130 $this->filters,
1131 function ($tuple) use ($key, $filter) {
1132 return $tuple[$key] !== $filter;
1133 }
1134 ));
1135
1136 $this->updateInternalState();
1137
1138 $this->handleChangedParameters();
1139
1140 return $this;
1141 }
1142
1143 /**
1144 * Return whether given instance or name is in the filter stack.
1145 *
1146 * @param callable|string $filter
1147 *
1148 * @return bool
1149 */
1150 public function hasFilter($filter)
1151 {
1152 $key = \is_callable($filter) ? 0 : 1;
1153
1154 foreach ($this->filters as $tuple) {
1155 if ($tuple[$key] === $filter) {
1156 return true;
1157 }
1158 }
1159
1160 return false;
1161 }
1162
1163 /**
1164 * Get filters stack.
1165 *
1166 * @return array
1167 */
1168 public function getFilters()
1169 {
1170 return $this->filters;
1171 }
1172
1173 /**
1174 * Set filters stack.
1175 *
1176 * @param array $filters
1177 *
1178 * @return $this
1179 */
1180 public function setFilters(array $filters)
1181 {
1182 $this->filters = $filters;
1183
1184 $this->updateInternalState();
1185
1186 $this->handleChangedParameters();
1187
1188 return $this;
1189 }
1190
1191 /**
1192 * Reset filters stack.
1193 *
1194 * @return $this
1195 */
1196 public function resetFilters()
1197 {
1198 $this->filters = [];
1199
1200 if ($this->endDate !== null) {
1201 $this->filters[] = [static::END_DATE_FILTER, null];
1202 }
1203
1204 if ($this->recurrences !== null) {
1205 $this->filters[] = [static::RECURRENCES_FILTER, null];
1206 }
1207
1208 $this->handleChangedParameters();
1209
1210 return $this;
1211 }
1212
1213 /**
1214 * Add a recurrences filter (set maximum number of recurrences).
1215 *
1216 * @param int|float|null $recurrences
1217 *
1218 * @throws InvalidArgumentException
1219 *
1220 * @return $this
1221 */
1222 public function setRecurrences($recurrences)
1223 {
1224 if (!is_numeric($recurrences) && $recurrences !== null || $recurrences < 0) {
1225 throw new InvalidPeriodParameterException('Invalid number of recurrences.');
1226 }
1227
1228 if ($recurrences === null) {
1229 return $this->removeFilter(static::RECURRENCES_FILTER);
1230 }
1231
1232 $this->recurrences = $recurrences === INF ? INF : (int) $recurrences;
1233
1234 if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
1235 return $this->addFilter(static::RECURRENCES_FILTER);
1236 }
1237
1238 $this->handleChangedParameters();
1239
1240 return $this;
1241 }
1242
1243 /**
1244 * Change the period start date.
1245 *
1246 * @param DateTime|DateTimeInterface|string $date
1247 * @param bool|null $inclusive
1248 *
1249 * @throws InvalidPeriodDateException
1250 *
1251 * @return $this
1252 */
1253 public function setStartDate($date, $inclusive = null)
1254 {
1255 if (!$date = ([$this->dateClass, 'make'])($date)) {
1256 throw new InvalidPeriodDateException('Invalid start date.');
1257 }
1258
1259 $this->startDate = $date;
1260
1261 if ($inclusive !== null) {
1262 $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
1263 }
1264
1265 return $this;
1266 }
1267
1268 /**
1269 * Change the period end date.
1270 *
1271 * @param DateTime|DateTimeInterface|string|null $date
1272 * @param bool|null $inclusive
1273 *
1274 * @throws \InvalidArgumentException
1275 *
1276 * @return $this
1277 */
1278 public function setEndDate($date, $inclusive = null)
1279 {
1280 if ($date !== null && !$date = ([$this->dateClass, 'make'])($date)) {
1281 throw new InvalidPeriodDateException('Invalid end date.');
1282 }
1283
1284 if (!$date) {
1285 return $this->removeFilter(static::END_DATE_FILTER);
1286 }
1287
1288 $this->endDate = $date;
1289
1290 if ($inclusive !== null) {
1291 $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
1292 }
1293
1294 if (!$this->hasFilter(static::END_DATE_FILTER)) {
1295 return $this->addFilter(static::END_DATE_FILTER);
1296 }
1297
1298 $this->handleChangedParameters();
1299
1300 return $this;
1301 }
1302
1303 /**
1304 * Check if the current position is valid.
1305 *
1306 * @return bool
1307 */
1308 public function valid()
1309 {
1310 return $this->validateCurrentDate() === true;
1311 }
1312
1313 /**
1314 * Return the current key.
1315 *
1316 * @return int|null
1317 */
1318 public function key()
1319 {
1320 return $this->valid()
1321 ? $this->key
1322 : null;
1323 }
1324
1325 /**
1326 * Return the current date.
1327 *
1328 * @return CarbonInterface|null
1329 */
1330 public function current()
1331 {
1332 return $this->valid()
1333 ? $this->prepareForReturn($this->current)
1334 : null;
1335 }
1336
1337 /**
1338 * Move forward to the next date.
1339 *
1340 * @throws RuntimeException
1341 *
1342 * @return void
1343 */
1344 public function next()
1345 {
1346 if ($this->current === null) {
1347 $this->rewind();
1348 }
1349
1350 if ($this->validationResult !== static::END_ITERATION) {
1351 $this->key++;
1352
1353 $this->incrementCurrentDateUntilValid();
1354 }
1355 }
1356
1357 /**
1358 * Rewind to the start date.
1359 *
1360 * Iterating over a date in the UTC timezone avoids bug during backward DST change.
1361 *
1362 * @see https://bugs.php.net/bug.php?id=72255
1363 * @see https://bugs.php.net/bug.php?id=74274
1364 * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time
1365 *
1366 * @throws RuntimeException
1367 *
1368 * @return void
1369 */
1370 public function rewind()
1371 {
1372 $this->key = 0;
1373 $this->current = ([$this->dateClass, 'make'])($this->startDate);
1374 $settings = $this->getSettings();
1375
1376 if ($this->hasLocalTranslator()) {
1377 $settings['locale'] = $this->getTranslatorLocale();
1378 }
1379
1380 $this->current->settings($settings);
1381 $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null;
1382
1383 if ($this->timezone) {
1384 $this->current = $this->current->utc();
1385 }
1386
1387 $this->validationResult = null;
1388
1389 if ($this->isStartExcluded() || $this->validateCurrentDate() === false) {
1390 $this->incrementCurrentDateUntilValid();
1391 }
1392 }
1393
1394 /**
1395 * Skip iterations and returns iteration state (false if ended, true if still valid).
1396 *
1397 * @param int $count steps number to skip (1 by default)
1398 *
1399 * @return bool
1400 */
1401 public function skip($count = 1)
1402 {
1403 for ($i = $count; $this->valid() && $i > 0; $i--) {
1404 $this->next();
1405 }
1406
1407 return $this->valid();
1408 }
1409
1410 /**
1411 * Format the date period as ISO 8601.
1412 *
1413 * @return string
1414 */
1415 public function toIso8601String()
1416 {
1417 $parts = [];
1418
1419 if ($this->recurrences !== null) {
1420 $parts[] = 'R'.$this->recurrences;
1421 }
1422
1423 $parts[] = $this->startDate->toIso8601String();
1424
1425 $parts[] = $this->dateInterval->spec();
1426
1427 if ($this->endDate !== null) {
1428 $parts[] = $this->endDate->toIso8601String();
1429 }
1430
1431 return implode('/', $parts);
1432 }
1433
1434 /**
1435 * Convert the date period into a string.
1436 *
1437 * @return string
1438 */
1439 public function toString()
1440 {
1441 $translator = ([$this->dateClass, 'getTranslator'])();
1442
1443 $parts = [];
1444
1445 $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay()
1446 ? 'Y-m-d H:i:s'
1447 : 'Y-m-d';
1448
1449 if ($this->recurrences !== null) {
1450 $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator);
1451 }
1452
1453 $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([
1454 'join' => true,
1455 ])], null, $translator);
1456
1457 $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator);
1458
1459 if ($this->endDate !== null) {
1460 $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator);
1461 }
1462
1463 $result = implode(' ', $parts);
1464
1465 return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1);
1466 }
1467
1468 /**
1469 * Format the date period as ISO 8601.
1470 *
1471 * @return string
1472 */
1473 public function spec()
1474 {
1475 return $this->toIso8601String();
1476 }
1477
1478 /**
1479 * Cast the current instance into the given class.
1480 *
1481 * @param string $className The $className::instance() method will be called to cast the current object.
1482 *
1483 * @return DatePeriod
1484 */
1485 public function cast(string $className)
1486 {
1487 if (!method_exists($className, 'instance')) {
1488 if (is_a($className, DatePeriod::class, true)) {
1489 return new $className(
1490 $this->getStartDate(),
1491 $this->getDateInterval(),
1492 $this->getEndDate() ? $this->getIncludedEndDate() : $this->getRecurrences(),
1493 $this->isStartExcluded() ? DatePeriod::EXCLUDE_START_DATE : 0
1494 );
1495 }
1496
1497 throw new InvalidCastException("$className has not the instance() method needed to cast the date.");
1498 }
1499
1500 return $className::instance($this);
1501 }
1502
1503 /**
1504 * Return native DatePeriod PHP object matching the current instance.
1505 *
1506 * @example
1507 * ```
1508 * var_dump(CarbonPeriod::create('2021-01-05', '2021-02-15')->toDatePeriod());
1509 * ```
1510 *
1511 * @return DatePeriod
1512 */
1513 public function toDatePeriod()
1514 {
1515 return $this->cast(DatePeriod::class);
1516 }
1517
1518 /**
1519 * Convert the date period into an array without changing current iteration state.
1520 *
1521 * @return CarbonInterface[]
1522 */
1523 public function toArray()
1524 {
1525 $state = [
1526 $this->key,
1527 $this->current ? $this->current->avoidMutation() : null,
1528 $this->validationResult,
1529 ];
1530
1531 $result = iterator_to_array($this);
1532
1533 [$this->key, $this->current, $this->validationResult] = $state;
1534
1535 return $result;
1536 }
1537
1538 /**
1539 * Count dates in the date period.
1540 *
1541 * @return int
1542 */
1543 public function count()
1544 {
1545 return \count($this->toArray());
1546 }
1547
1548 /**
1549 * Return the first date in the date period.
1550 *
1551 * @return CarbonInterface|null
1552 */
1553 public function first()
1554 {
1555 return ($this->toArray() ?: [])[0] ?? null;
1556 }
1557
1558 /**
1559 * Return the last date in the date period.
1560 *
1561 * @return CarbonInterface|null
1562 */
1563 public function last()
1564 {
1565 $array = $this->toArray();
1566
1567 return $array ? $array[\count($array) - 1] : null;
1568 }
1569
1570 /**
1571 * Convert the date period into a string.
1572 *
1573 * @return string
1574 */
1575 public function __toString()
1576 {
1577 return $this->toString();
1578 }
1579
1580 /**
1581 * Add aliases for setters.
1582 *
1583 * CarbonPeriod::days(3)->hours(5)->invert()
1584 * ->sinceNow()->until('2010-01-10')
1585 * ->filter(...)
1586 * ->count()
1587 *
1588 * Note: We use magic method to let static and instance aliases with the same names.
1589 *
1590 * @param string $method
1591 * @param array $parameters
1592 *
1593 * @return mixed
1594 */
1595 public function __call($method, $parameters)
1596 {
1597 if (static::hasMacro($method)) {
1598 return static::bindMacroContext($this, function () use (&$method, &$parameters) {
1599 return $this->callMacro($method, $parameters);
1600 });
1601 }
1602
1603 $roundedValue = $this->callRoundMethod($method, $parameters);
1604
1605 if ($roundedValue !== null) {
1606 return $roundedValue;
1607 }
1608
1609 $first = \count($parameters) >= 1 ? $parameters[0] : null;
1610 $second = \count($parameters) >= 2 ? $parameters[1] : null;
1611
1612 switch ($method) {
1613 case 'start':
1614 case 'since':
1615 return $this->setStartDate($first, $second);
1616
1617 case 'sinceNow':
1618 return $this->setStartDate(new Carbon, $first);
1619
1620 case 'end':
1621 case 'until':
1622 return $this->setEndDate($first, $second);
1623
1624 case 'untilNow':
1625 return $this->setEndDate(new Carbon, $first);
1626
1627 case 'dates':
1628 case 'between':
1629 return $this->setDates($first, $second);
1630
1631 case 'recurrences':
1632 case 'times':
1633 return $this->setRecurrences($first);
1634
1635 case 'options':
1636 return $this->setOptions($first);
1637
1638 case 'toggle':
1639 return $this->toggleOptions($first, $second);
1640
1641 case 'filter':
1642 case 'push':
1643 return $this->addFilter($first, $second);
1644
1645 case 'prepend':
1646 return $this->prependFilter($first, $second);
1647
1648 case 'filters':
1649 return $this->setFilters($first ?: []);
1650
1651 case 'interval':
1652 case 'each':
1653 case 'every':
1654 case 'step':
1655 case 'stepBy':
1656 return $this->setDateInterval($first);
1657
1658 case 'invert':
1659 return $this->invertDateInterval();
1660
1661 case 'years':
1662 case 'year':
1663 case 'months':
1664 case 'month':
1665 case 'weeks':
1666 case 'week':
1667 case 'days':
1668 case 'dayz':
1669 case 'day':
1670 case 'hours':
1671 case 'hour':
1672 case 'minutes':
1673 case 'minute':
1674 case 'seconds':
1675 case 'second':
1676 return $this->setDateInterval((
1677 // Override default P1D when instantiating via fluent setters.
1678 [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method]
1679 )(
1680 \count($parameters) === 0 ? 1 : $first
1681 ));
1682 }
1683
1684 if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) {
1685 throw new UnknownMethodException($method);
1686 }
1687
1688 return $this;
1689 }
1690
1691 /**
1692 * Set the instance's timezone from a string or object and add/subtract the offset difference.
1693 *
1694 * @param \DateTimeZone|string $timezone
1695 *
1696 * @return static
1697 */
1698 public function shiftTimezone($timezone)
1699 {
1700 $this->tzName = $timezone;
1701 $this->timezone = $timezone;
1702
1703 return $this;
1704 }
1705
1706 /**
1707 * Returns the end is set, else calculated from start an recurrences.
1708 *
1709 * @param string|null $rounding Optional rounding 'floor', 'ceil', 'round' using the period interval.
1710 *
1711 * @return CarbonInterface
1712 */
1713 public function calculateEnd(string $rounding = null)
1714 {
1715 if ($end = $this->getEndDate($rounding)) {
1716 return $end;
1717 }
1718
1719 if ($this->dateInterval->isEmpty()) {
1720 return $this->getStartDate($rounding);
1721 }
1722
1723 $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd();
1724
1725 if ($date && $rounding) {
1726 $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding);
1727 }
1728
1729 return $date;
1730 }
1731
1732 /**
1733 * @return CarbonInterface|null
1734 */
1735 private function getEndFromRecurrences()
1736 {
1737 if ($this->recurrences === null) {
1738 throw new UnreachableException(
1739 "Could not calculate period end without either explicit end or recurrences.\n".
1740 "If you're looking for a forever-period, use ->setRecurrences(INF)."
1741 );
1742 }
1743
1744 if ($this->recurrences === INF) {
1745 $start = $this->getStartDate();
1746
1747 return $start < $start->avoidMutation()->add($this->getDateInterval())
1748 ? CarbonImmutable::endOfTime()
1749 : CarbonImmutable::startOfTime();
1750 }
1751
1752 if ($this->filters === [[static::RECURRENCES_FILTER, null]]) {
1753 return $this->getStartDate()->avoidMutation()->add(
1754 $this->getDateInterval()->times(
1755 $this->recurrences - ($this->isStartExcluded() ? 0 : 1)
1756 )
1757 );
1758 }
1759
1760 return null;
1761 }
1762
1763 /**
1764 * @return CarbonInterface|null
1765 */
1766 private function iterateUntilEnd()
1767 {
1768 $attempts = 0;
1769 $date = null;
1770
1771 foreach ($this as $date) {
1772 if (++$attempts > static::END_MAX_ATTEMPTS) {
1773 throw new UnreachableException(
1774 'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.'
1775 );
1776 }
1777 }
1778
1779 return $date;
1780 }
1781
1782 /**
1783 * Returns true if the current period overlaps the given one (if 1 parameter passed)
1784 * or the period between 2 dates (if 2 parameters passed).
1785 *
1786 * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart
1787 * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null $rangeEnd
1788 *
1789 * @return bool
1790 */
1791 public function overlaps($rangeOrRangeStart, $rangeEnd = null)
1792 {
1793 $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart;
1794
1795 if (!($range instanceof self)) {
1796 $range = static::create($range);
1797 }
1798
1799 [$start, $end] = $this->orderCouple($this->getStartDate(), $this->calculateEnd());
1800 [$rangeStart, $rangeEnd] = $this->orderCouple($range->getStartDate(), $range->calculateEnd());
1801
1802 return $end > $rangeStart && $rangeEnd > $start;
1803 }
1804
1805 /**
1806 * Execute a given function on each date of the period.
1807 *
1808 * @example
1809 * ```
1810 * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) {
1811 * echo $date->diffInDays('2020-12-25')." days before Christmas!\n";
1812 * });
1813 * ```
1814 *
1815 * @param callable $callback
1816 */
1817 public function forEach(callable $callback)
1818 {
1819 foreach ($this as $date) {
1820 $callback($date);
1821 }
1822 }
1823
1824 /**
1825 * Execute a given function on each date of the period and yield the result of this function.
1826 *
1827 * @example
1828 * ```
1829 * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24');
1830 * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) {
1831 * return $date->diffInDays('2020-12-25').' days before Christmas!';
1832 * })));
1833 * ```
1834 *
1835 * @param callable $callback
1836 *
1837 * @return \Generator
1838 */
1839 public function map(callable $callback)
1840 {
1841 foreach ($this as $date) {
1842 yield $callback($date);
1843 }
1844 }
1845
1846 /**
1847 * Determines if the instance is equal to another.
1848 * Warning: if options differ, instances wil never be equal.
1849 *
1850 * @param mixed $period
1851 *
1852 * @see equalTo()
1853 *
1854 * @return bool
1855 */
1856 public function eq($period): bool
1857 {
1858 return $this->equalTo($period);
1859 }
1860
1861 /**
1862 * Determines if the instance is equal to another.
1863 * Warning: if options differ, instances wil never be equal.
1864 *
1865 * @param mixed $period
1866 *
1867 * @return bool
1868 */
1869 public function equalTo($period): bool
1870 {
1871 if (!($period instanceof self)) {
1872 $period = self::make($period);
1873 }
1874
1875 $end = $this->getEndDate();
1876
1877 return $period !== null
1878 && $this->getDateInterval()->eq($period->getDateInterval())
1879 && $this->getStartDate()->eq($period->getStartDate())
1880 && ($end ? $end->eq($period->getEndDate()) : $this->getRecurrences() === $period->getRecurrences())
1881 && ($this->getOptions() & (~static::IMMUTABLE)) === ($period->getOptions() & (~static::IMMUTABLE));
1882 }
1883
1884 /**
1885 * Determines if the instance is not equal to another.
1886 * Warning: if options differ, instances wil never be equal.
1887 *
1888 * @param mixed $period
1889 *
1890 * @see notEqualTo()
1891 *
1892 * @return bool
1893 */
1894 public function ne($period): bool
1895 {
1896 return $this->notEqualTo($period);
1897 }
1898
1899 /**
1900 * Determines if the instance is not equal to another.
1901 * Warning: if options differ, instances wil never be equal.
1902 *
1903 * @param mixed $period
1904 *
1905 * @return bool
1906 */
1907 public function notEqualTo($period): bool
1908 {
1909 return !$this->eq($period);
1910 }
1911
1912 /**
1913 * Determines if the start date is before an other given date.
1914 * (Rather start/end are included by options is ignored.)
1915 *
1916 * @param mixed $date
1917 *
1918 * @return bool
1919 */
1920 public function startsBefore($date = null): bool
1921 {
1922 return $this->getStartDate()->lessThan($this->resolveCarbon($date));
1923 }
1924
1925 /**
1926 * Determines if the start date is before or the same as a given date.
1927 * (Rather start/end are included by options is ignored.)
1928 *
1929 * @param mixed $date
1930 *
1931 * @return bool
1932 */
1933 public function startsBeforeOrAt($date = null): bool
1934 {
1935 return $this->getStartDate()->lessThanOrEqualTo($this->resolveCarbon($date));
1936 }
1937
1938 /**
1939 * Determines if the start date is after an other given date.
1940 * (Rather start/end are included by options is ignored.)
1941 *
1942 * @param mixed $date
1943 *
1944 * @return bool
1945 */
1946 public function startsAfter($date = null): bool
1947 {
1948 return $this->getStartDate()->greaterThan($this->resolveCarbon($date));
1949 }
1950
1951 /**
1952 * Determines if the start date is after or the same as a given date.
1953 * (Rather start/end are included by options is ignored.)
1954 *
1955 * @param mixed $date
1956 *
1957 * @return bool
1958 */
1959 public function startsAfterOrAt($date = null): bool
1960 {
1961 return $this->getStartDate()->greaterThanOrEqualTo($this->resolveCarbon($date));
1962 }
1963
1964 /**
1965 * Determines if the start date is the same as a given date.
1966 * (Rather start/end are included by options is ignored.)
1967 *
1968 * @param mixed $date
1969 *
1970 * @return bool
1971 */
1972 public function startsAt($date = null): bool
1973 {
1974 return $this->getStartDate()->equalTo($this->resolveCarbon($date));
1975 }
1976
1977 /**
1978 * Determines if the end date is before an other given date.
1979 * (Rather start/end are included by options is ignored.)
1980 *
1981 * @param mixed $date
1982 *
1983 * @return bool
1984 */
1985 public function endsBefore($date = null): bool
1986 {
1987 return $this->calculateEnd()->lessThan($this->resolveCarbon($date));
1988 }
1989
1990 /**
1991 * Determines if the end date is before or the same as a given date.
1992 * (Rather start/end are included by options is ignored.)
1993 *
1994 * @param mixed $date
1995 *
1996 * @return bool
1997 */
1998 public function endsBeforeOrAt($date = null): bool
1999 {
2000 return $this->calculateEnd()->lessThanOrEqualTo($this->resolveCarbon($date));
2001 }
2002
2003 /**
2004 * Determines if the end date is after an other given date.
2005 * (Rather start/end are included by options is ignored.)
2006 *
2007 * @param mixed $date
2008 *
2009 * @return bool
2010 */
2011 public function endsAfter($date = null): bool
2012 {
2013 return $this->calculateEnd()->greaterThan($this->resolveCarbon($date));
2014 }
2015
2016 /**
2017 * Determines if the end date is after or the same as a given date.
2018 * (Rather start/end are included by options is ignored.)
2019 *
2020 * @param mixed $date
2021 *
2022 * @return bool
2023 */
2024 public function endsAfterOrAt($date = null): bool
2025 {
2026 return $this->calculateEnd()->greaterThanOrEqualTo($this->resolveCarbon($date));
2027 }
2028
2029 /**
2030 * Determines if the end date is the same as a given date.
2031 * (Rather start/end are included by options is ignored.)
2032 *
2033 * @param mixed $date
2034 *
2035 * @return bool
2036 */
2037 public function endsAt($date = null): bool
2038 {
2039 return $this->calculateEnd()->equalTo($this->resolveCarbon($date));
2040 }
2041
2042 /**
2043 * Return true if start date is now or later.
2044 * (Rather start/end are included by options is ignored.)
2045 *
2046 * @return bool
2047 */
2048 public function isStarted(): bool
2049 {
2050 return $this->startsBeforeOrAt();
2051 }
2052
2053 /**
2054 * Return true if end date is now or later.
2055 * (Rather start/end are included by options is ignored.)
2056 *
2057 * @return bool
2058 */
2059 public function isEnded(): bool
2060 {
2061 return $this->endsBeforeOrAt();
2062 }
2063
2064 /**
2065 * Return true if now is between start date (included) and end date (excluded).
2066 * (Rather start/end are included by options is ignored.)
2067 *
2068 * @return bool
2069 */
2070 public function isInProgress(): bool
2071 {
2072 return $this->isStarted() && !$this->isEnded();
2073 }
2074
2075 /**
2076 * Round the current instance at the given unit with given precision if specified and the given function.
2077 *
2078 * @param string $unit
2079 * @param float|int|string|\DateInterval|null $precision
2080 * @param string $function
2081 *
2082 * @return $this
2083 */
2084 public function roundUnit($unit, $precision = 1, $function = 'round')
2085 {
2086 $this->setStartDate($this->getStartDate()->roundUnit($unit, $precision, $function));
2087
2088 if ($this->endDate) {
2089 $this->setEndDate($this->getEndDate()->roundUnit($unit, $precision, $function));
2090 }
2091
2092 $this->setDateInterval($this->getDateInterval()->roundUnit($unit, $precision, $function));
2093
2094 return $this;
2095 }
2096
2097 /**
2098 * Truncate the current instance at the given unit with given precision if specified.
2099 *
2100 * @param string $unit
2101 * @param float|int|string|\DateInterval|null $precision
2102 *
2103 * @return $this
2104 */
2105 public function floorUnit($unit, $precision = 1)
2106 {
2107 return $this->roundUnit($unit, $precision, 'floor');
2108 }
2109
2110 /**
2111 * Ceil the current instance at the given unit with given precision if specified.
2112 *
2113 * @param string $unit
2114 * @param float|int|string|\DateInterval|null $precision
2115 *
2116 * @return $this
2117 */
2118 public function ceilUnit($unit, $precision = 1)
2119 {
2120 return $this->roundUnit($unit, $precision, 'ceil');
2121 }
2122
2123 /**
2124 * Round the current instance second with given precision if specified (else period interval is used).
2125 *
2126 * @param float|int|string|\DateInterval|null $precision
2127 * @param string $function
2128 *
2129 * @return $this
2130 */
2131 public function round($precision = null, $function = 'round')
2132 {
2133 return $this->roundWith($precision ?? (string) $this->getDateInterval(), $function);
2134 }
2135
2136 /**
2137 * Round the current instance second with given precision if specified (else period interval is used).
2138 *
2139 * @param float|int|string|\DateInterval|null $precision
2140 *
2141 * @return $this
2142 */
2143 public function floor($precision = null)
2144 {
2145 return $this->round($precision, 'floor');
2146 }
2147
2148 /**
2149 * Ceil the current instance second with given precision if specified (else period interval is used).
2150 *
2151 * @param float|int|string|\DateInterval|null $precision
2152 *
2153 * @return $this
2154 */
2155 public function ceil($precision = null)
2156 {
2157 return $this->round($precision, 'ceil');
2158 }
2159
2160 /**
2161 * Specify data which should be serialized to JSON.
2162 *
2163 * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
2164 *
2165 * @return CarbonInterface[]
2166 */
2167 #[ReturnTypeWillChange]
2168 public function jsonSerialize()
2169 {
2170 return $this->toArray();
2171 }
2172
2173 /**
2174 * Return true if the given date is between start and end.
2175 *
2176 * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date
2177 *
2178 * @return bool
2179 */
2180 public function contains($date = null): bool
2181 {
2182 $startMethod = 'startsBefore'.($this->isStartIncluded() ? 'OrAt' : '');
2183 $endMethod = 'endsAfter'.($this->isEndIncluded() ? 'OrAt' : '');
2184
2185 return $this->$startMethod($date) && $this->$endMethod($date);
2186 }
2187
2188 /**
2189 * Return true if the current period follows a given other period (with no overlap).
2190 * For instance, [2019-08-01 -> 2019-08-12] follows [2019-07-29 -> 2019-07-31]
2191 * Note than in this example, follows() would be false if 2019-08-01 or 2019-07-31 was excluded by options.
2192 *
2193 * @param \Carbon\CarbonPeriod|\DatePeriod|string $period
2194 *
2195 * @return bool
2196 */
2197 public function follows($period, ...$arguments): bool
2198 {
2199 $period = $this->resolveCarbonPeriod($period, ...$arguments);
2200
2201 return $this->getIncludedStartDate()->equalTo($period->getIncludedEndDate()->add($period->getDateInterval()));
2202 }
2203
2204 /**
2205 * Return true if the given other period follows the current one (with no overlap).
2206 * For instance, [2019-07-29 -> 2019-07-31] is followed by [2019-08-01 -> 2019-08-12]
2207 * Note than in this example, isFollowedBy() would be false if 2019-08-01 or 2019-07-31 was excluded by options.
2208 *
2209 * @param \Carbon\CarbonPeriod|\DatePeriod|string $period
2210 *
2211 * @return bool
2212 */
2213 public function isFollowedBy($period, ...$arguments): bool
2214 {
2215 $period = $this->resolveCarbonPeriod($period, ...$arguments);
2216
2217 return $period->follows($this);
2218 }
2219
2220 /**
2221 * Return true if the given period either follows or is followed by the current one.
2222 *
2223 * @see follows()
2224 * @see isFollowedBy()
2225 *
2226 * @param \Carbon\CarbonPeriod|\DatePeriod|string $period
2227 *
2228 * @return bool
2229 */
2230 public function isConsecutiveWith($period, ...$arguments): bool
2231 {
2232 return $this->follows($period, ...$arguments) || $this->isFollowedBy($period, ...$arguments);
2233 }
2234
2235 /**
2236 * Update properties after removing built-in filters.
2237 *
2238 * @return void
2239 */
2240 protected function updateInternalState()
2241 {
2242 if (!$this->hasFilter(static::END_DATE_FILTER)) {
2243 $this->endDate = null;
2244 }
2245
2246 if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
2247 $this->recurrences = null;
2248 }
2249 }
2250
2251 /**
2252 * Create a filter tuple from raw parameters.
2253 *
2254 * Will create an automatic filter callback for one of Carbon's is* methods.
2255 *
2256 * @param array $parameters
2257 *
2258 * @return array
2259 */
2260 protected function createFilterTuple(array $parameters)
2261 {
2262 $method = array_shift($parameters);
2263
2264 if (!$this->isCarbonPredicateMethod($method)) {
2265 return [$method, array_shift($parameters)];
2266 }
2267
2268 return [function ($date) use ($method, $parameters) {
2269 return ([$date, $method])(...$parameters);
2270 }, $method];
2271 }
2272
2273 /**
2274 * Return whether given callable is a string pointing to one of Carbon's is* methods
2275 * and should be automatically converted to a filter callback.
2276 *
2277 * @param callable $callable
2278 *
2279 * @return bool
2280 */
2281 protected function isCarbonPredicateMethod($callable)
2282 {
2283 return \is_string($callable) && str_starts_with($callable, 'is') &&
2284 (method_exists($this->dateClass, $callable) || ([$this->dateClass, 'hasMacro'])($callable));
2285 }
2286
2287 /**
2288 * Recurrences filter callback (limits number of recurrences).
2289 *
2290 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
2291 *
2292 * @param \Carbon\Carbon $current
2293 * @param int $key
2294 *
2295 * @return bool|string
2296 */
2297 protected function filterRecurrences($current, $key)
2298 {
2299 if ($key < $this->recurrences) {
2300 return true;
2301 }
2302
2303 return static::END_ITERATION;
2304 }
2305
2306 /**
2307 * End date filter callback.
2308 *
2309 * @param \Carbon\Carbon $current
2310 *
2311 * @return bool|string
2312 */
2313 protected function filterEndDate($current)
2314 {
2315 if (!$this->isEndExcluded() && $current == $this->endDate) {
2316 return true;
2317 }
2318
2319 if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
2320 return true;
2321 }
2322
2323 return static::END_ITERATION;
2324 }
2325
2326 /**
2327 * End iteration filter callback.
2328 *
2329 * @return string
2330 */
2331 protected function endIteration()
2332 {
2333 return static::END_ITERATION;
2334 }
2335
2336 /**
2337 * Handle change of the parameters.
2338 */
2339 protected function handleChangedParameters()
2340 {
2341 if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) {
2342 $this->setDateClass(CarbonImmutable::class);
2343 } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) {
2344 $this->setDateClass(Carbon::class);
2345 }
2346
2347 $this->validationResult = null;
2348 }
2349
2350 /**
2351 * Validate current date and stop iteration when necessary.
2352 *
2353 * Returns true when current date is valid, false if it is not, or static::END_ITERATION
2354 * when iteration should be stopped.
2355 *
2356 * @return bool|string
2357 */
2358 protected function validateCurrentDate()
2359 {
2360 if ($this->current === null) {
2361 $this->rewind();
2362 }
2363
2364 // Check after the first rewind to avoid repeating the initial validation.
2365 if ($this->validationResult !== null) {
2366 return $this->validationResult;
2367 }
2368
2369 return $this->validationResult = $this->checkFilters();
2370 }
2371
2372 /**
2373 * Check whether current value and key pass all the filters.
2374 *
2375 * @return bool|string
2376 */
2377 protected function checkFilters()
2378 {
2379 $current = $this->prepareForReturn($this->current);
2380
2381 foreach ($this->filters as $tuple) {
2382 $result = \call_user_func(
2383 $tuple[0],
2384 $current->avoidMutation(),
2385 $this->key,
2386 $this
2387 );
2388
2389 if ($result === static::END_ITERATION) {
2390 return static::END_ITERATION;
2391 }
2392
2393 if (!$result) {
2394 return false;
2395 }
2396 }
2397
2398 return true;
2399 }
2400
2401 /**
2402 * Prepare given date to be returned to the external logic.
2403 *
2404 * @param CarbonInterface $date
2405 *
2406 * @return CarbonInterface
2407 */
2408 protected function prepareForReturn(CarbonInterface $date)
2409 {
2410 $date = ([$this->dateClass, 'make'])($date);
2411
2412 if ($this->timezone) {
2413 $date = $date->setTimezone($this->timezone);
2414 }
2415
2416 return $date;
2417 }
2418
2419 /**
2420 * Keep incrementing the current date until a valid date is found or the iteration is ended.
2421 *
2422 * @throws RuntimeException
2423 *
2424 * @return void
2425 */
2426 protected function incrementCurrentDateUntilValid()
2427 {
2428 $attempts = 0;
2429
2430 do {
2431 $this->current = $this->current->add($this->dateInterval);
2432
2433 $this->validationResult = null;
2434
2435 if (++$attempts > static::NEXT_MAX_ATTEMPTS) {
2436 throw new UnreachableException('Could not find next valid date.');
2437 }
2438 } while ($this->validateCurrentDate() === false);
2439 }
2440
2441 /**
2442 * Call given macro.
2443 *
2444 * @param string $name
2445 * @param array $parameters
2446 *
2447 * @return mixed
2448 */
2449 protected function callMacro($name, $parameters)
2450 {
2451 $macro = static::$macros[$name];
2452
2453 if ($macro instanceof Closure) {
2454 $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class);
2455
2456 return ($boundMacro ?: $macro)(...$parameters);
2457 }
2458
2459 return $macro(...$parameters);
2460 }
2461
2462 /**
2463 * Return the Carbon instance passed through, a now instance in the same timezone
2464 * if null given or parse the input if string given.
2465 *
2466 * @param \Carbon\Carbon|\Carbon\CarbonPeriod|\Carbon\CarbonInterval|\DateInterval|\DatePeriod|\DateTimeInterface|string|null $date
2467 *
2468 * @return \Carbon\CarbonInterface
2469 */
2470 protected function resolveCarbon($date = null)
2471 {
2472 return $this->getStartDate()->nowWithSameTz()->carbonize($date);
2473 }
2474
2475 /**
2476 * Resolve passed arguments or DatePeriod to a CarbonPeriod object.
2477 *
2478 * @param mixed $period
2479 * @param mixed ...$arguments
2480 *
2481 * @return static
2482 */
2483 protected function resolveCarbonPeriod($period, ...$arguments)
2484 {
2485 if ($period instanceof self) {
2486 return $period;
2487 }
2488
2489 return $period instanceof DatePeriod
2490 ? static::instance($period)
2491 : static::create($period, ...$arguments);
2492 }
2493
2494 private function orderCouple($first, $second): array
2495 {
2496 return $first > $second ? [$second, $first] : [$first, $second];
2497 }
2498}