Passed
Push — develop ( 30cf64...589229 )
by Guillaume
06:18 queued 04:10
created

CarbonPeriod   F

Complexity

Total Complexity 316

Size/Duplication

Total Lines 2184
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 518
dl 0
loc 2184
rs 2
c 0
b 0
f 0
wmc 316

How to fix   Complexity   

Complex Class

Complex classes like CarbonPeriod often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CarbonPeriod, and based on these observations, apply Extract Interface, too.

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