Vous êtes connecté en tant que anonymous Se Deconnecter
Browse code

Application modulaire fonctionnelle !

Emmanuel ROY authored on 12/08/2019 15:10:25
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,1765 +0,0 @@
1
-<?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
- */
11
-namespace Carbon;
12
-
13
-use BadMethodCallException;
14
-use Carbon\Exceptions\NotAPeriodException;
15
-use Carbon\Traits\Options;
16
-use Closure;
17
-use Countable;
18
-use DateInterval;
19
-use DatePeriod;
20
-use DateTime;
21
-use DateTimeInterface;
22
-use InvalidArgumentException;
23
-use Iterator;
24
-use ReflectionClass;
25
-use ReflectionMethod;
26
-use RuntimeException;
27
-
28
-/**
29
- * Substitution of DatePeriod with some modifications and many more features.
30
- *
31
- * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date.
32
- * @method static CarbonPeriod since($date, $inclusive = null) Alias for start().
33
- * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now.
34
- * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date.
35
- * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end().
36
- * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now.
37
- * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end date.
38
- * @method static CarbonPeriod between($start, $end = null) Create instance with start and end date.
39
- * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences.
40
- * @method static CarbonPeriod times($recurrences = null) Alias for recurrences().
41
- * @method static CarbonPeriod options($options = null) Create instance with options.
42
- * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off.
43
- * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack.
44
- * @method static CarbonPeriod push($callback, $name = null) Alias for filter().
45
- * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepened to the stack.
46
- * @method static CarbonPeriod filters(array $filters) Create instance with filters stack.
47
- * @method static CarbonPeriod interval($interval) Create instance with given date interval.
48
- * @method static CarbonPeriod each($interval) Create instance with given date interval.
49
- * @method static CarbonPeriod every($interval) Create instance with given date interval.
50
- * @method static CarbonPeriod step($interval) Create instance with given date interval.
51
- * @method static CarbonPeriod stepBy($interval) Create instance with given date interval.
52
- * @method static CarbonPeriod invert() Create instance with inverted date interval.
53
- * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval.
54
- * @method static CarbonPeriod year($years = 1) Alias for years().
55
- * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval.
56
- * @method static CarbonPeriod month($months = 1) Alias for months().
57
- * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval.
58
- * @method static CarbonPeriod week($weeks = 1) Alias for weeks().
59
- * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval.
60
- * @method static CarbonPeriod dayz($days = 1) Alias for days().
61
- * @method static CarbonPeriod day($days = 1) Alias for days().
62
- * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval.
63
- * @method static CarbonPeriod hour($hours = 1) Alias for hours().
64
- * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval.
65
- * @method static CarbonPeriod minute($minutes = 1) Alias for minutes().
66
- * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval.
67
- * @method static CarbonPeriod second($seconds = 1) Alias for seconds().
68
- * @method CarbonPeriod start($date, $inclusive = null) Change the period start date.
69
- * @method CarbonPeriod since($date, $inclusive = null) Alias for start().
70
- * @method CarbonPeriod sinceNow($inclusive = null) Change the period start date to now.
71
- * @method CarbonPeriod end($date = null, $inclusive = null) Change the period end date.
72
- * @method CarbonPeriod until($date = null, $inclusive = null) Alias for end().
73
- * @method CarbonPeriod untilNow($inclusive = null) Change the period end date to now.
74
- * @method CarbonPeriod dates($start, $end = null) Change the period start and end date.
75
- * @method CarbonPeriod recurrences($recurrences = null) Change the maximum number of recurrences.
76
- * @method CarbonPeriod times($recurrences = null) Alias for recurrences().
77
- * @method CarbonPeriod options($options = null) Change the period options.
78
- * @method CarbonPeriod toggle($options, $state = null) Toggle given options on or off.
79
- * @method CarbonPeriod filter($callback, $name = null) Add a filter to the stack.
80
- * @method CarbonPeriod push($callback, $name = null) Alias for filter().
81
- * @method CarbonPeriod prepend($callback, $name = null) Prepend a filter to the stack.
82
- * @method CarbonPeriod filters(array $filters = []) Set filters stack.
83
- * @method CarbonPeriod interval($interval) Change the period date interval.
84
- * @method CarbonPeriod invert() Invert the period date interval.
85
- * @method CarbonPeriod years($years = 1) Set the years portion of the date interval.
86
- * @method CarbonPeriod year($years = 1) Alias for years().
87
- * @method CarbonPeriod months($months = 1) Set the months portion of the date interval.
88
- * @method CarbonPeriod month($months = 1) Alias for months().
89
- * @method CarbonPeriod weeks($weeks = 1) Set the weeks portion of the date interval.
90
- * @method CarbonPeriod week($weeks = 1) Alias for weeks().
91
- * @method CarbonPeriod days($days = 1) Set the days portion of the date interval.
92
- * @method CarbonPeriod dayz($days = 1) Alias for days().
93
- * @method CarbonPeriod day($days = 1) Alias for days().
94
- * @method CarbonPeriod hours($hours = 1) Set the hours portion of the date interval.
95
- * @method CarbonPeriod hour($hours = 1) Alias for hours().
96
- * @method CarbonPeriod minutes($minutes = 1) Set the minutes portion of the date interval.
97
- * @method CarbonPeriod minute($minutes = 1) Alias for minutes().
98
- * @method CarbonPeriod seconds($seconds = 1) Set the seconds portion of the date interval.
99
- * @method CarbonPeriod second($seconds = 1) Alias for seconds().
100
- */
101
-class CarbonPeriod implements Iterator, Countable
102
-{
103
-    use Options;
104
-
105
-    /**
106
-     * Built-in filters.
107
-     *
108
-     * @var string
109
-     */
110
-    const RECURRENCES_FILTER = 'Carbon\CarbonPeriod::filterRecurrences';
111
-    const END_DATE_FILTER = 'Carbon\CarbonPeriod::filterEndDate';
112
-
113
-    /**
114
-     * Special value which can be returned by filters to end iteration. Also a filter.
115
-     *
116
-     * @var string
117
-     */
118
-    const END_ITERATION = 'Carbon\CarbonPeriod::endIteration';
119
-
120
-    /**
121
-     * Available options.
122
-     *
123
-     * @var int
124
-     */
125
-    const EXCLUDE_START_DATE = 1;
126
-    const EXCLUDE_END_DATE = 2;
127
-    const IMMUTABLE = 4;
128
-
129
-    /**
130
-     * Number of maximum attempts before giving up on finding next valid date.
131
-     *
132
-     * @var int
133
-     */
134
-    const NEXT_MAX_ATTEMPTS = 1000;
135
-
136
-    /**
137
-     * The registered macros.
138
-     *
139
-     * @var array
140
-     */
141
-    protected static $macros = [];
142
-
143
-    /**
144
-     * Date class of iteration items.
145
-     *
146
-     * @var string
147
-     */
148
-    protected $dateClass = Carbon::class;
149
-
150
-    /**
151
-     * Underlying date interval instance. Always present, one day by default.
152
-     *
153
-     * @var CarbonInterval
154
-     */
155
-    protected $dateInterval;
156
-
157
-    /**
158
-     * Whether current date interval was set by default.
159
-     *
160
-     * @var bool
161
-     */
162
-    protected $isDefaultInterval;
163
-
164
-    /**
165
-     * The filters stack.
166
-     *
167
-     * @var array
168
-     */
169
-    protected $filters = [];
170
-
171
-    /**
172
-     * Period start date. Applied on rewind. Always present, now by default.
173
-     *
174
-     * @var CarbonInterface
175
-     */
176
-    protected $startDate;
177
-
178
-    /**
179
-     * Period end date. For inverted interval should be before the start date. Applied via a filter.
180
-     *
181
-     * @var CarbonInterface|null
182
-     */
183
-    protected $endDate;
184
-
185
-    /**
186
-     * Limit for number of recurrences. Applied via a filter.
187
-     *
188
-     * @var int|null
189
-     */
190
-    protected $recurrences;
191
-
192
-    /**
193
-     * Iteration options.
194
-     *
195
-     * @var int
196
-     */
197
-    protected $options;
198
-
199
-    /**
200
-     * Index of current date. Always sequential, even if some dates are skipped by filters.
201
-     * Equal to null only before the first iteration.
202
-     *
203
-     * @var int
204
-     */
205
-    protected $key;
206
-
207
-    /**
208
-     * Current date. May temporarily hold unaccepted value when looking for a next valid date.
209
-     * Equal to null only before the first iteration.
210
-     *
211
-     * @var CarbonInterface
212
-     */
213
-    protected $current;
214
-
215
-    /**
216
-     * Timezone of current date. Taken from the start date.
217
-     *
218
-     * @var \DateTimeZone|null
219
-     */
220
-    protected $timezone;
221
-
222
-    /**
223
-     * The cached validation result for current date.
224
-     *
225
-     * @var bool|string|null
226
-     */
227
-    protected $validationResult;
228
-
229
-    /**
230
-     * Timezone handler for settings() method.
231
-     *
232
-     * @var mixed
233
-     */
234
-    protected $tzName;
235
-
236
-    /**
237
-     * Make a CarbonPeriod instance from given variable if possible.
238
-     *
239
-     * @param mixed $var
240
-     *
241
-     * @return static|null
242
-     */
243
-    public static function make($var)
244
-    {
245
-        try {
246
-            return static::instance($var);
247
-        } catch (NotAPeriodException $e) {
248
-            return static::create($var);
249
-        }
250
-    }
251
-
252
-    /**
253
-     * Create a new instance from a DatePeriod or CarbonPeriod object.
254
-     *
255
-     * @param CarbonPeriod|DatePeriod $period
256
-     *
257
-     * @return static
258
-     */
259
-    public static function instance($period)
260
-    {
261
-        if ($period instanceof self) {
262
-            return $period->copy();
263
-        }
264
-
265
-        if ($period instanceof DatePeriod) {
266
-            return new static(
267
-                $period->start,
268
-                $period->end ?: $period->recurrences,
269
-                $period->interval,
270
-                $period->include_start_date ? 0 : static::EXCLUDE_START_DATE
271
-            );
272
-        }
273
-
274
-        $class = get_called_class();
275
-        $type = gettype($period);
276
-
277
-        throw new NotAPeriodException(
278
-            'Argument 1 passed to '.$class.'::'.__METHOD__.'() '.
279
-            'must be an instance of DatePeriod or '.$class.', '.
280
-            ($type === 'object' ? 'instance of '.get_class($period) : $type).' given.'
281
-        );
282
-    }
283
-
284
-    /**
285
-     * Get a copy of the instance.
286
-     *
287
-     * @return static
288
-     */
289
-    public function copy()
290
-    {
291
-        return clone $this;
292
-    }
293
-
294
-    /**
295
-     * @alias copy
296
-     *
297
-     * Get a copy of the instance.
298
-     *
299
-     * @return static
300
-     */
301
-    public function clone()
302
-    {
303
-        return clone $this;
304
-    }
305
-
306
-    /**
307
-     * Create a new instance.
308
-     *
309
-     * @return static
310
-     */
311
-    public static function create(...$params)
312
-    {
313
-        return static::createFromArray($params);
314
-    }
315
-
316
-    /**
317
-     * Create a new instance from an array of parameters.
318
-     *
319
-     * @param array $params
320
-     *
321
-     * @return static
322
-     */
323
-    public static function createFromArray(array $params)
324
-    {
325
-        return new static(...$params);
326
-    }
327
-
328
-    /**
329
-     * Create CarbonPeriod from ISO 8601 string.
330
-     *
331
-     * @param string   $iso
332
-     * @param int|null $options
333
-     *
334
-     * @return static
335
-     */
336
-    public static function createFromIso($iso, $options = null)
337
-    {
338
-        $params = static::parseIso8601($iso);
339
-
340
-        $instance = static::createFromArray($params);
341
-
342
-        if ($options !== null) {
343
-            $instance->setOptions($options);
344
-        }
345
-
346
-        return $instance;
347
-    }
348
-
349
-    /**
350
-     * Return whether given interval contains non zero value of any time unit.
351
-     *
352
-     * @param \DateInterval $interval
353
-     *
354
-     * @return bool
355
-     */
356
-    protected static function intervalHasTime(DateInterval $interval)
357
-    {
358
-        return $interval->h || $interval->i || $interval->s || $interval->f;
359
-    }
360
-
361
-    /**
362
-     * Return whether given variable is an ISO 8601 specification.
363
-     *
364
-     * Note: Check is very basic, as actual validation will be done later when parsing.
365
-     * We just want to ensure that variable is not any other type of a valid parameter.
366
-     *
367
-     * @param mixed $var
368
-     *
369
-     * @return bool
370
-     */
371
-    protected static function isIso8601($var)
372
-    {
373
-        if (!is_string($var)) {
374
-            return false;
375
-        }
376
-
377
-        // Match slash but not within a timezone name.
378
-        $part = '[a-z]+(?:[_-][a-z]+)*';
379
-
380
-        preg_match("#\b$part/$part\b|(/)#i", $var, $match);
381
-
382
-        return isset($match[1]);
383
-    }
384
-
385
-    /**
386
-     * Parse given ISO 8601 string into an array of arguments.
387
-     *
388
-     * @SuppressWarnings(PHPMD.ElseExpression)
389
-     *
390
-     * @param string $iso
391
-     *
392
-     * @return array
393
-     */
394
-    protected static function parseIso8601($iso)
395
-    {
396
-        $result = [];
397
-
398
-        $interval = null;
399
-        $start = null;
400
-        $end = null;
401
-
402
-        foreach (explode('/', $iso) as $key => $part) {
403
-            if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) {
404
-                $parsed = strlen($match[1]) ? (int) $match[1] : null;
405
-            } elseif ($interval === null && $parsed = CarbonInterval::make($part)) {
406
-                $interval = $part;
407
-            } elseif ($start === null && $parsed = Carbon::make($part)) {
408
-                $start = $part;
409
-            } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start, $part))) {
410
-                $end = $part;
411
-            } else {
412
-                throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso.");
413
-            }
414
-
415
-            $result[] = $parsed;
416
-        }
417
-
418
-        return $result;
419
-    }
420
-
421
-    /**
422
-     * Add missing parts of the target date from the soure date.
423
-     *
424
-     * @param string $source
425
-     * @param string $target
426
-     *
427
-     * @return string
428
-     */
429
-    protected static function addMissingParts($source, $target)
430
-    {
431
-        $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/';
432
-
433
-        $result = preg_replace($pattern, $target, $source, 1, $count);
434
-
435
-        return $count ? $result : $target;
436
-    }
437
-
438
-    /**
439
-     * Register a custom macro.
440
-     *
441
-     * @example
442
-     * ```
443
-     * CarbonPeriod::macro('middle', function () {
444
-     *   return $this->getStartDate()->average($this->getEndDate());
445
-     * });
446
-     * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle();
447
-     * ```
448
-     *
449
-     * @param string          $name
450
-     * @param object|callable $macro
451
-     *
452
-     * @return void
453
-     */
454
-    public static function macro($name, $macro)
455
-    {
456
-        static::$macros[$name] = $macro;
457
-    }
458
-
459
-    /**
460
-     * Register macros from a mixin object.
461
-     *
462
-     * @example
463
-     * ```
464
-     * CarbonPeriod::mixin(new class {
465
-     *   public function addDays() {
466
-     *     return function ($count = 1) {
467
-     *       return $this->setStartDate(
468
-     *         $this->getStartDate()->addDays($count)
469
-     *       )->setEndDate(
470
-     *         $this->getEndDate()->addDays($count)
471
-     *       );
472
-     *     };
473
-     *   }
474
-     *   public function subDays() {
475
-     *     return function ($count = 1) {
476
-     *       return $this->setStartDate(
477
-     *         $this->getStartDate()->subDays($count)
478
-     *       )->setEndDate(
479
-     *         $this->getEndDate()->subDays($count)
480
-     *       );
481
-     *     };
482
-     *   }
483
-     * });
484
-     * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3);
485
-     * ```
486
-     *
487
-     * @param object $mixin
488
-     *
489
-     * @throws \ReflectionException
490
-     *
491
-     * @return void
492
-     */
493
-    public static function mixin($mixin)
494
-    {
495
-        $reflection = new ReflectionClass($mixin);
496
-
497
-        $methods = $reflection->getMethods(
498
-            ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
499
-        );
500
-
501
-        foreach ($methods as $method) {
502
-            $method->setAccessible(true);
503
-
504
-            static::macro($method->name, $method->invoke($mixin));
505
-        }
506
-    }
507
-
508
-    /**
509
-     * Check if macro is registered.
510
-     *
511
-     * @param string $name
512
-     *
513
-     * @return bool
514
-     */
515
-    public static function hasMacro($name)
516
-    {
517
-        return isset(static::$macros[$name]);
518
-    }
519
-
520
-    /**
521
-     * Provide static proxy for instance aliases.
522
-     *
523
-     * @param string $method
524
-     * @param array  $parameters
525
-     *
526
-     * @return mixed
527
-     */
528
-    public static function __callStatic($method, $parameters)
529
-    {
530
-        return (new static)->$method(...$parameters);
531
-    }
532
-
533
-    /**
534
-     * CarbonPeriod constructor.
535
-     *
536
-     * @SuppressWarnings(PHPMD.ElseExpression)
537
-     *
538
-     * @throws InvalidArgumentException
539
-     */
540
-    public function __construct(...$arguments)
541
-    {
542
-        // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
543
-        // which will be first parsed into parts and then processed the same way.
544
-
545
-        if (count($arguments) && static::isIso8601($iso = $arguments[0])) {
546
-            array_splice($arguments, 0, 1, static::parseIso8601($iso));
547
-        }
548
-
549
-        foreach ($arguments as $argument) {
550
-            if ($this->dateInterval === null &&
551
-                (
552
-                    is_string($argument) && preg_match('/^(\d.*|P[T0-9].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', $argument) ||
553
-                    $argument instanceof DateInterval
554
-                ) &&
555
-                $parsed = @CarbonInterval::make($argument)
556
-            ) {
557
-                $this->setDateInterval($parsed);
558
-            } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) {
559
-                $this->setStartDate($parsed);
560
-            } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) {
561
-                $this->setEndDate($parsed);
562
-            } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) {
563
-                $this->setRecurrences($argument);
564
-            } elseif ($this->options === null && (is_int($argument) || $argument === null)) {
565
-                $this->setOptions($argument);
566
-            } else {
567
-                throw new InvalidArgumentException('Invalid constructor parameters.');
568
-            }
569
-        }
570
-
571
-        if ($this->startDate === null) {
572
-            $this->setStartDate(Carbon::now());
573
-        }
574
-
575
-        if ($this->dateInterval === null) {
576
-            $this->setDateInterval(CarbonInterval::day());
577
-
578
-            $this->isDefaultInterval = true;
579
-        }
580
-
581
-        if ($this->options === null) {
582
-            $this->setOptions(0);
583
-        }
584
-    }
585
-
586
-    /**
587
-     * Return whether given callable is a string pointing to one of Carbon's is* methods
588
-     * and should be automatically converted to a filter callback.
589
-     *
590
-     * @param callable $callable
591
-     *
592
-     * @return bool
593
-     */
594
-    protected function isCarbonPredicateMethod($callable)
595
-    {
596
-        return is_string($callable) && substr($callable, 0, 2) === 'is' && (method_exists($this->dateClass, $callable) || call_user_func([$this->dateClass, 'hasMacro'], $callable));
597
-    }
598
-
599
-    /**
600
-     * Set the iteration item class.
601
-     *
602
-     * @param string $dateClass
603
-     *
604
-     * @return $this
605
-     */
606
-    public function setDateClass(string $dateClass)
607
-    {
608
-        if (!is_a($dateClass, CarbonInterface::class, true)) {
609
-            throw new InvalidArgumentException(sprintf(
610
-                'Given class does not implement %s: %s',
611
-                CarbonInterface::class,
612
-                $dateClass
613
-            ));
614
-        }
615
-
616
-        $this->dateClass = $dateClass;
617
-
618
-        if (is_a($dateClass, Carbon::class, true)) {
619
-            $this->toggleOptions(static::IMMUTABLE, false);
620
-        } elseif (is_a($dateClass, CarbonImmutable::class, true)) {
621
-            $this->toggleOptions(static::IMMUTABLE, true);
622
-        }
623
-
624
-        return $this;
625
-    }
626
-
627
-    /**
628
-     * Returns iteration item date class.
629
-     *
630
-     * @return string
631
-     */
632
-    public function getDateClass(): string
633
-    {
634
-        return $this->dateClass;
635
-    }
636
-
637
-    /**
638
-     * Change the period date interval.
639
-     *
640
-     * @param DateInterval|string $interval
641
-     *
642
-     * @throws \InvalidArgumentException
643
-     *
644
-     * @return $this
645
-     */
646
-    public function setDateInterval($interval)
647
-    {
648
-        if (!$interval = CarbonInterval::make($interval)) {
649
-            throw new InvalidArgumentException('Invalid interval.');
650
-        }
651
-
652
-        if ($interval->spec() === 'PT0S' && !$interval->f) {
653
-            throw new InvalidArgumentException('Empty interval is not accepted.');
654
-        }
655
-
656
-        $this->dateInterval = $interval;
657
-
658
-        $this->isDefaultInterval = false;
659
-
660
-        $this->handleChangedParameters();
661
-
662
-        return $this;
663
-    }
664
-
665
-    /**
666
-     * Invert the period date interval.
667
-     *
668
-     * @return $this
669
-     */
670
-    public function invertDateInterval()
671
-    {
672
-        $interval = $this->dateInterval->invert();
673
-
674
-        return $this->setDateInterval($interval);
675
-    }
676
-
677
-    /**
678
-     * Set start and end date.
679
-     *
680
-     * @param DateTime|DateTimeInterface|string      $start
681
-     * @param DateTime|DateTimeInterface|string|null $end
682
-     *
683
-     * @return $this
684
-     */
685
-    public function setDates($start, $end)
686
-    {
687
-        $this->setStartDate($start);
688
-        $this->setEndDate($end);
689
-
690
-        return $this;
691
-    }
692
-
693
-    /**
694
-     * Change the period options.
695
-     *
696
-     * @param int|null $options
697
-     *
698
-     * @throws \InvalidArgumentException
699
-     *
700
-     * @return $this
701
-     */
702
-    public function setOptions($options)
703
-    {
704
-        if (!is_int($options) && !is_null($options)) {
705
-            throw new InvalidArgumentException('Invalid options.');
706
-        }
707
-
708
-        $this->options = $options ?: 0;
709
-
710
-        $this->handleChangedParameters();
711
-
712
-        return $this;
713
-    }
714
-
715
-    /**
716
-     * Get the period options.
717
-     *
718
-     * @return int
719
-     */
720
-    public function getOptions()
721
-    {
722
-        return $this->options;
723
-    }
724
-
725
-    /**
726
-     * Toggle given options on or off.
727
-     *
728
-     * @param int       $options
729
-     * @param bool|null $state
730
-     *
731
-     * @throws \InvalidArgumentException
732
-     *
733
-     * @return $this
734
-     */
735
-    public function toggleOptions($options, $state = null)
736
-    {
737
-        if ($state === null) {
738
-            $state = ($this->options & $options) !== $options;
739
-        }
740
-
741
-        return $this->setOptions(
742
-            $state ?
743
-            $this->options | $options :
744
-            $this->options & ~$options
745
-        );
746
-    }
747
-
748
-    /**
749
-     * Toggle EXCLUDE_START_DATE option.
750
-     *
751
-     * @param bool $state
752
-     *
753
-     * @return $this
754
-     */
755
-    public function excludeStartDate($state = true)
756
-    {
757
-        return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
758
-    }
759
-
760
-    /**
761
-     * Toggle EXCLUDE_END_DATE option.
762
-     *
763
-     * @param bool $state
764
-     *
765
-     * @return $this
766
-     */
767
-    public function excludeEndDate($state = true)
768
-    {
769
-        return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);
770
-    }
771
-
772
-    /**
773
-     * Get the underlying date interval.
774
-     *
775
-     * @return CarbonInterval
776
-     */
777
-    public function getDateInterval()
778
-    {
779
-        return $this->dateInterval->copy();
780
-    }
781
-
782
-    /**
783
-     * Get start date of the period.
784
-     *
785
-     * @return CarbonInterface
786
-     */
787
-    public function getStartDate()
788
-    {
789
-        return $this->startDate->copy();
790
-    }
791
-
792
-    /**
793
-     * Get end date of the period.
794
-     *
795
-     * @return CarbonInterface|null
796
-     */
797
-    public function getEndDate()
798
-    {
799
-        return $this->endDate ? $this->endDate->copy() : null;
800
-    }
801
-
802
-    /**
803
-     * Get number of recurrences.
804
-     *
805
-     * @return int|null
806
-     */
807
-    public function getRecurrences()
808
-    {
809
-        return $this->recurrences;
810
-    }
811
-
812
-    /**
813
-     * Returns true if the start date should be excluded.
814
-     *
815
-     * @return bool
816
-     */
817
-    public function isStartExcluded()
818
-    {
819
-        return ($this->options & static::EXCLUDE_START_DATE) !== 0;
820
-    }
821
-
822
-    /**
823
-     * Returns true if the end date should be excluded.
824
-     *
825
-     * @return bool
826
-     */
827
-    public function isEndExcluded()
828
-    {
829
-        return ($this->options & static::EXCLUDE_END_DATE) !== 0;
830
-    }
831
-
832
-    /**
833
-     * Add a filter to the stack.
834
-     *
835
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
836
-     *
837
-     * @param callable $callback
838
-     * @param string   $name
839
-     *
840
-     * @return $this
841
-     */
842
-    public function addFilter($callback, $name = null)
843
-    {
844
-        $tuple = $this->createFilterTuple(func_get_args());
845
-
846
-        $this->filters[] = $tuple;
847
-
848
-        $this->handleChangedParameters();
849
-
850
-        return $this;
851
-    }
852
-
853
-    /**
854
-     * Prepend a filter to the stack.
855
-     *
856
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
857
-     *
858
-     * @param callable $callback
859
-     * @param string   $name
860
-     *
861
-     * @return $this
862
-     */
863
-    public function prependFilter($callback, $name = null)
864
-    {
865
-        $tuple = $this->createFilterTuple(func_get_args());
866
-
867
-        array_unshift($this->filters, $tuple);
868
-
869
-        $this->handleChangedParameters();
870
-
871
-        return $this;
872
-    }
873
-
874
-    /**
875
-     * Create a filter tuple from raw parameters.
876
-     *
877
-     * Will create an automatic filter callback for one of Carbon's is* methods.
878
-     *
879
-     * @param array $parameters
880
-     *
881
-     * @return array
882
-     */
883
-    protected function createFilterTuple(array $parameters)
884
-    {
885
-        $method = array_shift($parameters);
886
-
887
-        if (!$this->isCarbonPredicateMethod($method)) {
888
-            return [$method, array_shift($parameters)];
889
-        }
890
-
891
-        return [function ($date) use ($method, $parameters) {
892
-            return call_user_func_array([$date, $method], $parameters);
893
-        }, $method];
894
-    }
895
-
896
-    /**
897
-     * Remove a filter by instance or name.
898
-     *
899
-     * @param callable|string $filter
900
-     *
901
-     * @return $this
902
-     */
903
-    public function removeFilter($filter)
904
-    {
905
-        $key = is_callable($filter) ? 0 : 1;
906
-
907
-        $this->filters = array_values(array_filter(
908
-            $this->filters,
909
-            function ($tuple) use ($key, $filter) {
910
-                return $tuple[$key] !== $filter;
911
-            }
912
-        ));
913
-
914
-        $this->updateInternalState();
915
-
916
-        $this->handleChangedParameters();
917
-
918
-        return $this;
919
-    }
920
-
921
-    /**
922
-     * Return whether given instance or name is in the filter stack.
923
-     *
924
-     * @param callable|string $filter
925
-     *
926
-     * @return bool
927
-     */
928
-    public function hasFilter($filter)
929
-    {
930
-        $key = is_callable($filter) ? 0 : 1;
931
-
932
-        foreach ($this->filters as $tuple) {
933
-            if ($tuple[$key] === $filter) {
934
-                return true;
935
-            }
936
-        }
937
-
938
-        return false;
939
-    }
940
-
941
-    /**
942
-     * Get filters stack.
943
-     *
944
-     * @return array
945
-     */
946
-    public function getFilters()
947
-    {
948
-        return $this->filters;
949
-    }
950
-
951
-    /**
952
-     * Set filters stack.
953
-     *
954
-     * @param array $filters
955
-     *
956
-     * @return $this
957
-     */
958
-    public function setFilters(array $filters)
959
-    {
960
-        $this->filters = $filters;
961
-
962
-        $this->updateInternalState();
963
-
964
-        $this->handleChangedParameters();
965
-
966
-        return $this;
967
-    }
968
-
969
-    /**
970
-     * Reset filters stack.
971
-     *
972
-     * @return $this
973
-     */
974
-    public function resetFilters()
975
-    {
976
-        $this->filters = [];
977
-
978
-        if ($this->endDate !== null) {
979
-            $this->filters[] = [static::END_DATE_FILTER, null];
980
-        }
981
-
982
-        if ($this->recurrences !== null) {
983
-            $this->filters[] = [static::RECURRENCES_FILTER, null];
984
-        }
985
-
986
-        $this->handleChangedParameters();
987
-
988
-        return $this;
989
-    }
990
-
991
-    /**
992
-     * Update properties after removing built-in filters.
993
-     *
994
-     * @return void
995
-     */
996
-    protected function updateInternalState()
997
-    {
998
-        if (!$this->hasFilter(static::END_DATE_FILTER)) {
999
-            $this->endDate = null;
1000
-        }
1001
-
1002
-        if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
1003
-            $this->recurrences = null;
1004
-        }
1005
-    }
1006
-
1007
-    /**
1008
-     * Add a recurrences filter (set maximum number of recurrences).
1009
-     *
1010
-     * @param int|null $recurrences
1011
-     *
1012
-     * @throws \InvalidArgumentException
1013
-     *
1014
-     * @return $this
1015
-     */
1016
-    public function setRecurrences($recurrences)
1017
-    {
1018
-        if (!is_numeric($recurrences) && !is_null($recurrences) || $recurrences < 0) {
1019
-            throw new InvalidArgumentException('Invalid number of recurrences.');
1020
-        }
1021
-
1022
-        if ($recurrences === null) {
1023
-            return $this->removeFilter(static::RECURRENCES_FILTER);
1024
-        }
1025
-
1026
-        $this->recurrences = (int) $recurrences;
1027
-
1028
-        if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
1029
-            return $this->addFilter(static::RECURRENCES_FILTER);
1030
-        }
1031
-
1032
-        $this->handleChangedParameters();
1033
-
1034
-        return $this;
1035
-    }
1036
-
1037
-    /**
1038
-     * Recurrences filter callback (limits number of recurrences).
1039
-     *
1040
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
1041
-     *
1042
-     * @param \Carbon\Carbon $current
1043
-     * @param int            $key
1044
-     *
1045
-     * @return bool|string
1046
-     */
1047
-    protected function filterRecurrences($current, $key)
1048
-    {
1049
-        if ($key < $this->recurrences) {
1050
-            return true;
1051
-        }
1052
-
1053
-        return static::END_ITERATION;
1054
-    }
1055
-
1056
-    /**
1057
-     * Change the period start date.
1058
-     *
1059
-     * @param DateTime|DateTimeInterface|string $date
1060
-     * @param bool|null                         $inclusive
1061
-     *
1062
-     * @throws \InvalidArgumentException
1063
-     *
1064
-     * @return $this
1065
-     */
1066
-    public function setStartDate($date, $inclusive = null)
1067
-    {
1068
-        if (!$date = call_user_func([$this->dateClass, 'make'], $date)) {
1069
-            throw new InvalidArgumentException('Invalid start date.');
1070
-        }
1071
-
1072
-        $this->startDate = $date;
1073
-
1074
-        if ($inclusive !== null) {
1075
-            $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
1076
-        }
1077
-
1078
-        return $this;
1079
-    }
1080
-
1081
-    /**
1082
-     * Change the period end date.
1083
-     *
1084
-     * @param DateTime|DateTimeInterface|string|null $date
1085
-     * @param bool|null                              $inclusive
1086
-     *
1087
-     * @throws \InvalidArgumentException
1088
-     *
1089
-     * @return $this
1090
-     */
1091
-    public function setEndDate($date, $inclusive = null)
1092
-    {
1093
-        if (!is_null($date) && !$date = call_user_func([$this->dateClass, 'make'], $date)) {
1094
-            throw new InvalidArgumentException('Invalid end date.');
1095
-        }
1096
-
1097
-        if (!$date) {
1098
-            return $this->removeFilter(static::END_DATE_FILTER);
1099
-        }
1100
-
1101
-        $this->endDate = $date;
1102
-
1103
-        if ($inclusive !== null) {
1104
-            $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
1105
-        }
1106
-
1107
-        if (!$this->hasFilter(static::END_DATE_FILTER)) {
1108
-            return $this->addFilter(static::END_DATE_FILTER);
1109
-        }
1110
-
1111
-        $this->handleChangedParameters();
1112
-
1113
-        return $this;
1114
-    }
1115
-
1116
-    /**
1117
-     * End date filter callback.
1118
-     *
1119
-     * @param \Carbon\Carbon $current
1120
-     *
1121
-     * @return bool|string
1122
-     */
1123
-    protected function filterEndDate($current)
1124
-    {
1125
-        if (!$this->isEndExcluded() && $current == $this->endDate) {
1126
-            return true;
1127
-        }
1128
-
1129
-        if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
1130
-            return true;
1131
-        }
1132
-
1133
-        return static::END_ITERATION;
1134
-    }
1135
-
1136
-    /**
1137
-     * End iteration filter callback.
1138
-     *
1139
-     * @return string
1140
-     */
1141
-    protected function endIteration()
1142
-    {
1143
-        return static::END_ITERATION;
1144
-    }
1145
-
1146
-    /**
1147
-     * Handle change of the parameters.
1148
-     */
1149
-    protected function handleChangedParameters()
1150
-    {
1151
-        if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) {
1152
-            $this->setDateClass(CarbonImmutable::class);
1153
-        } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) {
1154
-            $this->setDateClass(Carbon::class);
1155
-        }
1156
-
1157
-        $this->validationResult = null;
1158
-    }
1159
-
1160
-    /**
1161
-     * Validate current date and stop iteration when necessary.
1162
-     *
1163
-     * Returns true when current date is valid, false if it is not, or static::END_ITERATION
1164
-     * when iteration should be stopped.
1165
-     *
1166
-     * @return bool|string
1167
-     */
1168
-    protected function validateCurrentDate()
1169
-    {
1170
-        if ($this->current === null) {
1171
-            $this->rewind();
1172
-        }
1173
-
1174
-        // Check after the first rewind to avoid repeating the initial validation.
1175
-        if ($this->validationResult !== null) {
1176
-            return $this->validationResult;
1177
-        }
1178
-
1179
-        return $this->validationResult = $this->checkFilters();
1180
-    }
1181
-
1182
-    /**
1183
-     * Check whether current value and key pass all the filters.
1184
-     *
1185
-     * @return bool|string
1186
-     */
1187
-    protected function checkFilters()
1188
-    {
1189
-        $current = $this->prepareForReturn($this->current);
1190
-
1191
-        foreach ($this->filters as $tuple) {
1192
-            $result = call_user_func(
1193
-                $tuple[0],
1194
-                $current->copy(),
1195
-                $this->key,
1196
-                $this
1197
-            );
1198
-
1199
-            if ($result === static::END_ITERATION) {
1200
-                return static::END_ITERATION;
1201
-            }
1202
-
1203
-            if (!$result) {
1204
-                return false;
1205
-            }
1206
-        }
1207
-
1208
-        return true;
1209
-    }
1210
-
1211
-    /**
1212
-     * Prepare given date to be returned to the external logic.
1213
-     *
1214
-     * @param CarbonInterface $date
1215
-     *
1216
-     * @return CarbonInterface
1217
-     */
1218
-    protected function prepareForReturn(CarbonInterface $date)
1219
-    {
1220
-        $date = call_user_func([$this->dateClass, 'make'], $date);
1221
-
1222
-        if ($this->timezone) {
1223
-            $date = $date->setTimezone($this->timezone);
1224
-        }
1225
-
1226
-        return $date;
1227
-    }
1228
-
1229
-    /**
1230
-     * Check if the current position is valid.
1231
-     *
1232
-     * @return bool
1233
-     */
1234
-    public function valid()
1235
-    {
1236
-        return $this->validateCurrentDate() === true;
1237
-    }
1238
-
1239
-    /**
1240
-     * Return the current key.
1241
-     *
1242
-     * @return int|null
1243
-     */
1244
-    public function key()
1245
-    {
1246
-        if ($this->valid()) {
1247
-            return $this->key;
1248
-        }
1249
-    }
1250
-
1251
-    /**
1252
-     * Return the current date.
1253
-     *
1254
-     * @return CarbonInterface|null
1255
-     */
1256
-    public function current()
1257
-    {
1258
-        if ($this->valid()) {
1259
-            return $this->prepareForReturn($this->current);
1260
-        }
1261
-    }
1262
-
1263
-    /**
1264
-     * Move forward to the next date.
1265
-     *
1266
-     * @throws \RuntimeException
1267
-     *
1268
-     * @return void
1269
-     */
1270
-    public function next()
1271
-    {
1272
-        if ($this->current === null) {
1273
-            $this->rewind();
1274
-        }
1275
-
1276
-        if ($this->validationResult !== static::END_ITERATION) {
1277
-            $this->key++;
1278
-
1279
-            $this->incrementCurrentDateUntilValid();
1280
-        }
1281
-    }
1282
-
1283
-    /**
1284
-     * Rewind to the start date.
1285
-     *
1286
-     * Iterating over a date in the UTC timezone avoids bug during backward DST change.
1287
-     *
1288
-     * @see https://bugs.php.net/bug.php?id=72255
1289
-     * @see https://bugs.php.net/bug.php?id=74274
1290
-     * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time
1291
-     *
1292
-     * @throws \RuntimeException
1293
-     *
1294
-     * @return void
1295
-     */
1296
-    public function rewind()
1297
-    {
1298
-        $this->key = 0;
1299
-        $this->current = call_user_func([$this->dateClass, 'make'], $this->startDate);
1300
-        $settings = $this->getSettings();
1301
-        $locale = $this->getLocalTranslator()->getLocale();
1302
-        if ($locale) {
1303
-            $settings['locale'] = $locale;
1304
-        }
1305
-        $this->current->settings($settings);
1306
-        $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null;
1307
-
1308
-        if ($this->timezone) {
1309
-            $this->current = $this->current->utc();
1310
-        }
1311
-
1312
-        $this->validationResult = null;
1313
-
1314
-        if ($this->isStartExcluded() || $this->validateCurrentDate() === false) {
1315
-            $this->incrementCurrentDateUntilValid();
1316
-        }
1317
-    }
1318
-
1319
-    /**
1320
-     * Skip iterations and returns iteration state (false if ended, true if still valid).
1321
-     *
1322
-     * @param int $count steps number to skip (1 by default)
1323
-     *
1324
-     * @return bool
1325
-     */
1326
-    public function skip($count = 1)
1327
-    {
1328
-        for ($i = $count; $this->valid() && $i > 0; $i--) {
1329
-            $this->next();
1330
-        }
1331
-
1332
-        return $this->valid();
1333
-    }
1334
-
1335
-    /**
1336
-     * Keep incrementing the current date until a valid date is found or the iteration is ended.
1337
-     *
1338
-     * @throws \RuntimeException
1339
-     *
1340
-     * @return void
1341
-     */
1342
-    protected function incrementCurrentDateUntilValid()
1343
-    {
1344
-        $attempts = 0;
1345
-
1346
-        do {
1347
-            $this->current = $this->current->add($this->dateInterval);
1348
-
1349
-            $this->validationResult = null;
1350
-
1351
-            if (++$attempts > static::NEXT_MAX_ATTEMPTS) {
1352
-                throw new RuntimeException('Could not find next valid date.');
1353
-            }
1354
-        } while ($this->validateCurrentDate() === false);
1355
-    }
1356
-
1357
-    /**
1358
-     * Format the date period as ISO 8601.
1359
-     *
1360
-     * @return string
1361
-     */
1362
-    public function toIso8601String()
1363
-    {
1364
-        $parts = [];
1365
-
1366
-        if ($this->recurrences !== null) {
1367
-            $parts[] = 'R'.$this->recurrences;
1368
-        }
1369
-
1370
-        $parts[] = $this->startDate->toIso8601String();
1371
-
1372
-        $parts[] = $this->dateInterval->spec();
1373
-
1374
-        if ($this->endDate !== null) {
1375
-            $parts[] = $this->endDate->toIso8601String();
1376
-        }
1377
-
1378
-        return implode('/', $parts);
1379
-    }
1380
-
1381
-    /**
1382
-     * Convert the date period into a string.
1383
-     *
1384
-     * @return string
1385
-     */
1386
-    public function toString()
1387
-    {
1388
-        $translator = call_user_func([$this->dateClass, 'getTranslator']);
1389
-
1390
-        $parts = [];
1391
-
1392
-        $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay()
1393
-            ? 'Y-m-d H:i:s'
1394
-            : 'Y-m-d';
1395
-
1396
-        if ($this->recurrences !== null) {
1397
-            $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator);
1398
-        }
1399
-
1400
-        $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([
1401
-            'join' => true,
1402
-        ])], null, $translator);
1403
-
1404
-        $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator);
1405
-
1406
-        if ($this->endDate !== null) {
1407
-            $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator);
1408
-        }
1409
-
1410
-        $result = implode(' ', $parts);
1411
-
1412
-        return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1);
1413
-    }
1414
-
1415
-    /**
1416
-     * Format the date period as ISO 8601.
1417
-     *
1418
-     * @return string
1419
-     */
1420
-    public function spec()
1421
-    {
1422
-        return $this->toIso8601String();
1423
-    }
1424
-
1425
-    /**
1426
-     * Convert the date period into an array without changing current iteration state.
1427
-     *
1428
-     * @return array
1429
-     */
1430
-    public function toArray()
1431
-    {
1432
-        $state = [
1433
-            $this->key,
1434
-            $this->current ? $this->current->copy() : null,
1435
-            $this->validationResult,
1436
-        ];
1437
-
1438
-        $result = iterator_to_array($this);
1439
-
1440
-        [
1441
-            $this->key,
1442
-            $this->current,
1443
-            $this->validationResult
1444
-        ] = $state;
1445
-
1446
-        return $result;
1447
-    }
1448
-
1449
-    /**
1450
-     * Count dates in the date period.
1451
-     *
1452
-     * @return int
1453
-     */
1454
-    public function count()
1455
-    {
1456
-        return count($this->toArray());
1457
-    }
1458
-
1459
-    /**
1460
-     * Return the first date in the date period.
1461
-     *
1462
-     * @return CarbonInterface|null
1463
-     */
1464
-    public function first()
1465
-    {
1466
-        if ($array = $this->toArray()) {
1467
-            return $array[0];
1468
-        }
1469
-    }
1470
-
1471
-    /**
1472
-     * Return the last date in the date period.
1473
-     *
1474
-     * @return CarbonInterface|null
1475
-     */
1476
-    public function last()
1477
-    {
1478
-        if ($array = $this->toArray()) {
1479
-            return $array[count($array) - 1];
1480
-        }
1481
-    }
1482
-
1483
-    /**
1484
-     * Call given macro.
1485
-     *
1486
-     * @param string $name
1487
-     * @param array  $parameters
1488
-     *
1489
-     * @return mixed
1490
-     */
1491
-    protected function callMacro($name, $parameters)
1492
-    {
1493
-        $macro = static::$macros[$name];
1494
-
1495
-        if ($macro instanceof Closure) {
1496
-            return call_user_func_array($macro->bindTo($this, static::class), $parameters);
1497
-        }
1498
-
1499
-        return call_user_func_array($macro, $parameters);
1500
-    }
1501
-
1502
-    /**
1503
-     * Convert the date period into a string.
1504
-     *
1505
-     * @return string
1506
-     */
1507
-    public function __toString()
1508
-    {
1509
-        return $this->toString();
1510
-    }
1511
-
1512
-    /**
1513
-     * Add aliases for setters.
1514
-     *
1515
-     * CarbonPeriod::days(3)->hours(5)->invert()
1516
-     *     ->sinceNow()->until('2010-01-10')
1517
-     *     ->filter(...)
1518
-     *     ->count()
1519
-     *
1520
-     * Note: We use magic method to let static and instance aliases with the same names.
1521
-     *
1522
-     * @param string $method
1523
-     * @param array  $parameters
1524
-     *
1525
-     * @return mixed
1526
-     */
1527
-    public function __call($method, $parameters)
1528
-    {
1529
-        if (static::hasMacro($method)) {
1530
-            return $this->callMacro($method, $parameters);
1531
-        }
1532
-
1533
-        $first = count($parameters) >= 1 ? $parameters[0] : null;
1534
-        $second = count($parameters) >= 2 ? $parameters[1] : null;
1535
-
1536
-        switch ($method) {
1537
-            case 'start':
1538
-            case 'since':
1539
-                return $this->setStartDate($first, $second);
1540
-
1541
-            case 'sinceNow':
1542
-                return $this->setStartDate(new Carbon, $first);
1543
-
1544
-            case 'end':
1545
-            case 'until':
1546
-                return $this->setEndDate($first, $second);
1547
-
1548
-            case 'untilNow':
1549
-                return $this->setEndDate(new Carbon, $first);
1550
-
1551
-            case 'dates':
1552
-            case 'between':
1553
-                return $this->setDates($first, $second);
1554
-
1555
-            case 'recurrences':
1556
-            case 'times':
1557
-                return $this->setRecurrences($first);
1558
-
1559
-            case 'options':
1560
-                return $this->setOptions($first);
1561
-
1562
-            case 'toggle':
1563
-                return $this->toggleOptions($first, $second);
1564
-
1565
-            case 'filter':
1566
-            case 'push':
1567
-                return $this->addFilter($first, $second);
1568
-
1569
-            case 'prepend':
1570
-                return $this->prependFilter($first, $second);
1571
-
1572
-            case 'filters':
1573
-                return $this->setFilters($first ?: []);
1574
-
1575
-            case 'interval':
1576
-            case 'each':
1577
-            case 'every':
1578
-            case 'step':
1579
-            case 'stepBy':
1580
-                return $this->setDateInterval($first);
1581
-
1582
-            case 'invert':
1583
-                return $this->invertDateInterval();
1584
-
1585
-            case 'years':
1586
-            case 'year':
1587
-            case 'months':
1588
-            case 'month':
1589
-            case 'weeks':
1590
-            case 'week':
1591
-            case 'days':
1592
-            case 'dayz':
1593
-            case 'day':
1594
-            case 'hours':
1595
-            case 'hour':
1596
-            case 'minutes':
1597
-            case 'minute':
1598
-            case 'seconds':
1599
-            case 'second':
1600
-                return $this->setDateInterval(call_user_func(
1601
-                    // Override default P1D when instantiating via fluent setters.
1602
-                    [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method],
1603
-                    count($parameters) === 0 ? 1 : $first
1604
-                ));
1605
-        }
1606
-
1607
-        if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) {
1608
-            throw new BadMethodCallException("Method $method does not exist.");
1609
-        }
1610
-
1611
-        return $this;
1612
-    }
1613
-
1614
-    /**
1615
-     * Set the instance's timezone from a string or object and add/subtract the offset difference.
1616
-     *
1617
-     * @param \DateTimeZone|string $timezone
1618
-     *
1619
-     * @return static
1620
-     */
1621
-    public function shiftTimezone($timezone)
1622
-    {
1623
-        $this->tzName = $timezone;
1624
-        $this->timezone = $timezone;
1625
-
1626
-        return $this;
1627
-    }
1628
-
1629
-    /**
1630
-     * Returns the end is set, else calculated from start an recurrences.
1631
-     *
1632
-     * @return CarbonInterface
1633
-     */
1634
-    public function calculateEnd()
1635
-    {
1636
-        if ($end = $this->getEndDate()) {
1637
-            return $end;
1638
-        }
1639
-
1640
-        $dates = iterator_to_array($this);
1641
-
1642
-        return end($dates);
1643
-    }
1644
-
1645
-    /**
1646
-     * Returns true if the current period overlaps the given one (if 1 parameter passed)
1647
-     * or the period between 2 dates (if 2 parameters passed).
1648
-     *
1649
-     * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart
1650
-     * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null         $rangeEnd
1651
-     *
1652
-     * @return bool
1653
-     */
1654
-    public function overlaps($rangeOrRangeStart, $rangeEnd = null)
1655
-    {
1656
-        $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart;
1657
-
1658
-        if (!($range instanceof self)) {
1659
-            $range = static::create($range);
1660
-        }
1661
-
1662
-        return $this->calculateEnd() > $range->getStartDate() && $range->calculateEnd() > $this->getStartDate();
1663
-    }
1664
-
1665
-    /**
1666
-     * Execute a given function on each date of the period.
1667
-     *
1668
-     * @example
1669
-     * ```
1670
-     * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) {
1671
-     *   echo $date->diffInDays('2020-12-25')." days before Christmas!\n";
1672
-     * });
1673
-     * ```
1674
-     *
1675
-     * @param callable $callback
1676
-     */
1677
-    public function forEach(callable $callback)
1678
-    {
1679
-        foreach ($this as $date) {
1680
-            $callback($date);
1681
-        }
1682
-    }
1683
-
1684
-    /**
1685
-     * Execute a given function on each date of the period and yield the result of this function.
1686
-     *
1687
-     * @example
1688
-     * ```
1689
-     * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24');
1690
-     * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) {
1691
-     *   return $date->diffInDays('2020-12-25').' days before Christmas!';
1692
-     * })));
1693
-     * ```
1694
-     *
1695
-     * @param callable $callback
1696
-     *
1697
-     * @return \Generator
1698
-     */
1699
-    public function map(callable $callback)
1700
-    {
1701
-        foreach ($this as $date) {
1702
-            yield $callback($date);
1703
-        }
1704
-    }
1705
-
1706
-    /**
1707
-     * Determines if the instance is equal to another
1708
-     *
1709
-     * @param mixed $period
1710
-     *
1711
-     * @see equalTo()
1712
-     *
1713
-     * @return bool
1714
-     */
1715
-    public function eq($period): bool
1716
-    {
1717
-        return $this->equalTo($period);
1718
-    }
1719
-
1720
-    /**
1721
-     * Determines if the instance is equal to another
1722
-     *
1723
-     * @param mixed $period
1724
-     *
1725
-     * @return bool
1726
-     */
1727
-    public function equalTo($period): bool
1728
-    {
1729
-        if (!($period instanceof self)) {
1730
-            $period = self::make($period);
1731
-        }
1732
-
1733
-        return $period !== null
1734
-            && $this->getDateInterval()->eq($period->getDateInterval())
1735
-            && $this->getStartDate()->eq($period->getStartDate())
1736
-            && $this->getEndDate()->eq($period->getEndDate())
1737
-            && $this->getOptions() === $period->getOptions();
1738
-    }
1739
-
1740
-    /**
1741
-     * Determines if the instance is not equal to another
1742
-     *
1743
-     * @param mixed $period
1744
-     *
1745
-     * @see notEqualTo()
1746
-     *
1747
-     * @return bool
1748
-     */
1749
-    public function ne($period): bool
1750
-    {
1751
-        return $this->notEqualTo($period);
1752
-    }
1753
-
1754
-    /**
1755
-     * Determines if the instance is not equal to another
1756
-     *
1757
-     * @param mixed $period
1758
-     *
1759
-     * @return bool
1760
-     */
1761
-    public function notEqualTo($period): bool
1762
-    {
1763
-        return !$this->eq($period);
1764
-    }
1765
-}
Browse code

initial commit

Emmanuel ROY authored on 09/08/2019 08:39:02
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,1765 @@
1
+<?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
+ */
11
+namespace Carbon;
12
+
13
+use BadMethodCallException;
14
+use Carbon\Exceptions\NotAPeriodException;
15
+use Carbon\Traits\Options;
16
+use Closure;
17
+use Countable;
18
+use DateInterval;
19
+use DatePeriod;
20
+use DateTime;
21
+use DateTimeInterface;
22
+use InvalidArgumentException;
23
+use Iterator;
24
+use ReflectionClass;
25
+use ReflectionMethod;
26
+use RuntimeException;
27
+
28
+/**
29
+ * Substitution of DatePeriod with some modifications and many more features.
30
+ *
31
+ * @method static CarbonPeriod start($date, $inclusive = null) Create instance specifying start date.
32
+ * @method static CarbonPeriod since($date, $inclusive = null) Alias for start().
33
+ * @method static CarbonPeriod sinceNow($inclusive = null) Create instance with start date set to now.
34
+ * @method static CarbonPeriod end($date = null, $inclusive = null) Create instance specifying end date.
35
+ * @method static CarbonPeriod until($date = null, $inclusive = null) Alias for end().
36
+ * @method static CarbonPeriod untilNow($inclusive = null) Create instance with end date set to now.
37
+ * @method static CarbonPeriod dates($start, $end = null) Create instance with start and end date.
38
+ * @method static CarbonPeriod between($start, $end = null) Create instance with start and end date.
39
+ * @method static CarbonPeriod recurrences($recurrences = null) Create instance with maximum number of recurrences.
40
+ * @method static CarbonPeriod times($recurrences = null) Alias for recurrences().
41
+ * @method static CarbonPeriod options($options = null) Create instance with options.
42
+ * @method static CarbonPeriod toggle($options, $state = null) Create instance with options toggled on or off.
43
+ * @method static CarbonPeriod filter($callback, $name = null) Create instance with filter added to the stack.
44
+ * @method static CarbonPeriod push($callback, $name = null) Alias for filter().
45
+ * @method static CarbonPeriod prepend($callback, $name = null) Create instance with filter prepened to the stack.
46
+ * @method static CarbonPeriod filters(array $filters) Create instance with filters stack.
47
+ * @method static CarbonPeriod interval($interval) Create instance with given date interval.
48
+ * @method static CarbonPeriod each($interval) Create instance with given date interval.
49
+ * @method static CarbonPeriod every($interval) Create instance with given date interval.
50
+ * @method static CarbonPeriod step($interval) Create instance with given date interval.
51
+ * @method static CarbonPeriod stepBy($interval) Create instance with given date interval.
52
+ * @method static CarbonPeriod invert() Create instance with inverted date interval.
53
+ * @method static CarbonPeriod years($years = 1) Create instance specifying a number of years for date interval.
54
+ * @method static CarbonPeriod year($years = 1) Alias for years().
55
+ * @method static CarbonPeriod months($months = 1) Create instance specifying a number of months for date interval.
56
+ * @method static CarbonPeriod month($months = 1) Alias for months().
57
+ * @method static CarbonPeriod weeks($weeks = 1) Create instance specifying a number of weeks for date interval.
58
+ * @method static CarbonPeriod week($weeks = 1) Alias for weeks().
59
+ * @method static CarbonPeriod days($days = 1) Create instance specifying a number of days for date interval.
60
+ * @method static CarbonPeriod dayz($days = 1) Alias for days().
61
+ * @method static CarbonPeriod day($days = 1) Alias for days().
62
+ * @method static CarbonPeriod hours($hours = 1) Create instance specifying a number of hours for date interval.
63
+ * @method static CarbonPeriod hour($hours = 1) Alias for hours().
64
+ * @method static CarbonPeriod minutes($minutes = 1) Create instance specifying a number of minutes for date interval.
65
+ * @method static CarbonPeriod minute($minutes = 1) Alias for minutes().
66
+ * @method static CarbonPeriod seconds($seconds = 1) Create instance specifying a number of seconds for date interval.
67
+ * @method static CarbonPeriod second($seconds = 1) Alias for seconds().
68
+ * @method CarbonPeriod start($date, $inclusive = null) Change the period start date.
69
+ * @method CarbonPeriod since($date, $inclusive = null) Alias for start().
70
+ * @method CarbonPeriod sinceNow($inclusive = null) Change the period start date to now.
71
+ * @method CarbonPeriod end($date = null, $inclusive = null) Change the period end date.
72
+ * @method CarbonPeriod until($date = null, $inclusive = null) Alias for end().
73
+ * @method CarbonPeriod untilNow($inclusive = null) Change the period end date to now.
74
+ * @method CarbonPeriod dates($start, $end = null) Change the period start and end date.
75
+ * @method CarbonPeriod recurrences($recurrences = null) Change the maximum number of recurrences.
76
+ * @method CarbonPeriod times($recurrences = null) Alias for recurrences().
77
+ * @method CarbonPeriod options($options = null) Change the period options.
78
+ * @method CarbonPeriod toggle($options, $state = null) Toggle given options on or off.
79
+ * @method CarbonPeriod filter($callback, $name = null) Add a filter to the stack.
80
+ * @method CarbonPeriod push($callback, $name = null) Alias for filter().
81
+ * @method CarbonPeriod prepend($callback, $name = null) Prepend a filter to the stack.
82
+ * @method CarbonPeriod filters(array $filters = []) Set filters stack.
83
+ * @method CarbonPeriod interval($interval) Change the period date interval.
84
+ * @method CarbonPeriod invert() Invert the period date interval.
85
+ * @method CarbonPeriod years($years = 1) Set the years portion of the date interval.
86
+ * @method CarbonPeriod year($years = 1) Alias for years().
87
+ * @method CarbonPeriod months($months = 1) Set the months portion of the date interval.
88
+ * @method CarbonPeriod month($months = 1) Alias for months().
89
+ * @method CarbonPeriod weeks($weeks = 1) Set the weeks portion of the date interval.
90
+ * @method CarbonPeriod week($weeks = 1) Alias for weeks().
91
+ * @method CarbonPeriod days($days = 1) Set the days portion of the date interval.
92
+ * @method CarbonPeriod dayz($days = 1) Alias for days().
93
+ * @method CarbonPeriod day($days = 1) Alias for days().
94
+ * @method CarbonPeriod hours($hours = 1) Set the hours portion of the date interval.
95
+ * @method CarbonPeriod hour($hours = 1) Alias for hours().
96
+ * @method CarbonPeriod minutes($minutes = 1) Set the minutes portion of the date interval.
97
+ * @method CarbonPeriod minute($minutes = 1) Alias for minutes().
98
+ * @method CarbonPeriod seconds($seconds = 1) Set the seconds portion of the date interval.
99
+ * @method CarbonPeriod second($seconds = 1) Alias for seconds().
100
+ */
101
+class CarbonPeriod implements Iterator, Countable
102
+{
103
+    use Options;
104
+
105
+    /**
106
+     * Built-in filters.
107
+     *
108
+     * @var string
109
+     */
110
+    const RECURRENCES_FILTER = 'Carbon\CarbonPeriod::filterRecurrences';
111
+    const END_DATE_FILTER = 'Carbon\CarbonPeriod::filterEndDate';
112
+
113
+    /**
114
+     * Special value which can be returned by filters to end iteration. Also a filter.
115
+     *
116
+     * @var string
117
+     */
118
+    const END_ITERATION = 'Carbon\CarbonPeriod::endIteration';
119
+
120
+    /**
121
+     * Available options.
122
+     *
123
+     * @var int
124
+     */
125
+    const EXCLUDE_START_DATE = 1;
126
+    const EXCLUDE_END_DATE = 2;
127
+    const IMMUTABLE = 4;
128
+
129
+    /**
130
+     * Number of maximum attempts before giving up on finding next valid date.
131
+     *
132
+     * @var int
133
+     */
134
+    const NEXT_MAX_ATTEMPTS = 1000;
135
+
136
+    /**
137
+     * The registered macros.
138
+     *
139
+     * @var array
140
+     */
141
+    protected static $macros = [];
142
+
143
+    /**
144
+     * Date class of iteration items.
145
+     *
146
+     * @var string
147
+     */
148
+    protected $dateClass = Carbon::class;
149
+
150
+    /**
151
+     * Underlying date interval instance. Always present, one day by default.
152
+     *
153
+     * @var CarbonInterval
154
+     */
155
+    protected $dateInterval;
156
+
157
+    /**
158
+     * Whether current date interval was set by default.
159
+     *
160
+     * @var bool
161
+     */
162
+    protected $isDefaultInterval;
163
+
164
+    /**
165
+     * The filters stack.
166
+     *
167
+     * @var array
168
+     */
169
+    protected $filters = [];
170
+
171
+    /**
172
+     * Period start date. Applied on rewind. Always present, now by default.
173
+     *
174
+     * @var CarbonInterface
175
+     */
176
+    protected $startDate;
177
+
178
+    /**
179
+     * Period end date. For inverted interval should be before the start date. Applied via a filter.
180
+     *
181
+     * @var CarbonInterface|null
182
+     */
183
+    protected $endDate;
184
+
185
+    /**
186
+     * Limit for number of recurrences. Applied via a filter.
187
+     *
188
+     * @var int|null
189
+     */
190
+    protected $recurrences;
191
+
192
+    /**
193
+     * Iteration options.
194
+     *
195
+     * @var int
196
+     */
197
+    protected $options;
198
+
199
+    /**
200
+     * Index of current date. Always sequential, even if some dates are skipped by filters.
201
+     * Equal to null only before the first iteration.
202
+     *
203
+     * @var int
204
+     */
205
+    protected $key;
206
+
207
+    /**
208
+     * Current date. May temporarily hold unaccepted value when looking for a next valid date.
209
+     * Equal to null only before the first iteration.
210
+     *
211
+     * @var CarbonInterface
212
+     */
213
+    protected $current;
214
+
215
+    /**
216
+     * Timezone of current date. Taken from the start date.
217
+     *
218
+     * @var \DateTimeZone|null
219
+     */
220
+    protected $timezone;
221
+
222
+    /**
223
+     * The cached validation result for current date.
224
+     *
225
+     * @var bool|string|null
226
+     */
227
+    protected $validationResult;
228
+
229
+    /**
230
+     * Timezone handler for settings() method.
231
+     *
232
+     * @var mixed
233
+     */
234
+    protected $tzName;
235
+
236
+    /**
237
+     * Make a CarbonPeriod instance from given variable if possible.
238
+     *
239
+     * @param mixed $var
240
+     *
241
+     * @return static|null
242
+     */
243
+    public static function make($var)
244
+    {
245
+        try {
246
+            return static::instance($var);
247
+        } catch (NotAPeriodException $e) {
248
+            return static::create($var);
249
+        }
250
+    }
251
+
252
+    /**
253
+     * Create a new instance from a DatePeriod or CarbonPeriod object.
254
+     *
255
+     * @param CarbonPeriod|DatePeriod $period
256
+     *
257
+     * @return static
258
+     */
259
+    public static function instance($period)
260
+    {
261
+        if ($period instanceof self) {
262
+            return $period->copy();
263
+        }
264
+
265
+        if ($period instanceof DatePeriod) {
266
+            return new static(
267
+                $period->start,
268
+                $period->end ?: $period->recurrences,
269
+                $period->interval,
270
+                $period->include_start_date ? 0 : static::EXCLUDE_START_DATE
271
+            );
272
+        }
273
+
274
+        $class = get_called_class();
275
+        $type = gettype($period);
276
+
277
+        throw new NotAPeriodException(
278
+            'Argument 1 passed to '.$class.'::'.__METHOD__.'() '.
279
+            'must be an instance of DatePeriod or '.$class.', '.
280
+            ($type === 'object' ? 'instance of '.get_class($period) : $type).' given.'
281
+        );
282
+    }
283
+
284
+    /**
285
+     * Get a copy of the instance.
286
+     *
287
+     * @return static
288
+     */
289
+    public function copy()
290
+    {
291
+        return clone $this;
292
+    }
293
+
294
+    /**
295
+     * @alias copy
296
+     *
297
+     * Get a copy of the instance.
298
+     *
299
+     * @return static
300
+     */
301
+    public function clone()
302
+    {
303
+        return clone $this;
304
+    }
305
+
306
+    /**
307
+     * Create a new instance.
308
+     *
309
+     * @return static
310
+     */
311
+    public static function create(...$params)
312
+    {
313
+        return static::createFromArray($params);
314
+    }
315
+
316
+    /**
317
+     * Create a new instance from an array of parameters.
318
+     *
319
+     * @param array $params
320
+     *
321
+     * @return static
322
+     */
323
+    public static function createFromArray(array $params)
324
+    {
325
+        return new static(...$params);
326
+    }
327
+
328
+    /**
329
+     * Create CarbonPeriod from ISO 8601 string.
330
+     *
331
+     * @param string   $iso
332
+     * @param int|null $options
333
+     *
334
+     * @return static
335
+     */
336
+    public static function createFromIso($iso, $options = null)
337
+    {
338
+        $params = static::parseIso8601($iso);
339
+
340
+        $instance = static::createFromArray($params);
341
+
342
+        if ($options !== null) {
343
+            $instance->setOptions($options);
344
+        }
345
+
346
+        return $instance;
347
+    }
348
+
349
+    /**
350
+     * Return whether given interval contains non zero value of any time unit.
351
+     *
352
+     * @param \DateInterval $interval
353
+     *
354
+     * @return bool
355
+     */
356
+    protected static function intervalHasTime(DateInterval $interval)
357
+    {
358
+        return $interval->h || $interval->i || $interval->s || $interval->f;
359
+    }
360
+
361
+    /**
362
+     * Return whether given variable is an ISO 8601 specification.
363
+     *
364
+     * Note: Check is very basic, as actual validation will be done later when parsing.
365
+     * We just want to ensure that variable is not any other type of a valid parameter.
366
+     *
367
+     * @param mixed $var
368
+     *
369
+     * @return bool
370
+     */
371
+    protected static function isIso8601($var)
372
+    {
373
+        if (!is_string($var)) {
374
+            return false;
375
+        }
376
+
377
+        // Match slash but not within a timezone name.
378
+        $part = '[a-z]+(?:[_-][a-z]+)*';
379
+
380
+        preg_match("#\b$part/$part\b|(/)#i", $var, $match);
381
+
382
+        return isset($match[1]);
383
+    }
384
+
385
+    /**
386
+     * Parse given ISO 8601 string into an array of arguments.
387
+     *
388
+     * @SuppressWarnings(PHPMD.ElseExpression)
389
+     *
390
+     * @param string $iso
391
+     *
392
+     * @return array
393
+     */
394
+    protected static function parseIso8601($iso)
395
+    {
396
+        $result = [];
397
+
398
+        $interval = null;
399
+        $start = null;
400
+        $end = null;
401
+
402
+        foreach (explode('/', $iso) as $key => $part) {
403
+            if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) {
404
+                $parsed = strlen($match[1]) ? (int) $match[1] : null;
405
+            } elseif ($interval === null && $parsed = CarbonInterval::make($part)) {
406
+                $interval = $part;
407
+            } elseif ($start === null && $parsed = Carbon::make($part)) {
408
+                $start = $part;
409
+            } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start, $part))) {
410
+                $end = $part;
411
+            } else {
412
+                throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso.");
413
+            }
414
+
415
+            $result[] = $parsed;
416
+        }
417
+
418
+        return $result;
419
+    }
420
+
421
+    /**
422
+     * Add missing parts of the target date from the soure date.
423
+     *
424
+     * @param string $source
425
+     * @param string $target
426
+     *
427
+     * @return string
428
+     */
429
+    protected static function addMissingParts($source, $target)
430
+    {
431
+        $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/';
432
+
433
+        $result = preg_replace($pattern, $target, $source, 1, $count);
434
+
435
+        return $count ? $result : $target;
436
+    }
437
+
438
+    /**
439
+     * Register a custom macro.
440
+     *
441
+     * @example
442
+     * ```
443
+     * CarbonPeriod::macro('middle', function () {
444
+     *   return $this->getStartDate()->average($this->getEndDate());
445
+     * });
446
+     * echo CarbonPeriod::since('2011-05-12')->until('2011-06-03')->middle();
447
+     * ```
448
+     *
449
+     * @param string          $name
450
+     * @param object|callable $macro
451
+     *
452
+     * @return void
453
+     */
454
+    public static function macro($name, $macro)
455
+    {
456
+        static::$macros[$name] = $macro;
457
+    }
458
+
459
+    /**
460
+     * Register macros from a mixin object.
461
+     *
462
+     * @example
463
+     * ```
464
+     * CarbonPeriod::mixin(new class {
465
+     *   public function addDays() {
466
+     *     return function ($count = 1) {
467
+     *       return $this->setStartDate(
468
+     *         $this->getStartDate()->addDays($count)
469
+     *       )->setEndDate(
470
+     *         $this->getEndDate()->addDays($count)
471
+     *       );
472
+     *     };
473
+     *   }
474
+     *   public function subDays() {
475
+     *     return function ($count = 1) {
476
+     *       return $this->setStartDate(
477
+     *         $this->getStartDate()->subDays($count)
478
+     *       )->setEndDate(
479
+     *         $this->getEndDate()->subDays($count)
480
+     *       );
481
+     *     };
482
+     *   }
483
+     * });
484
+     * echo CarbonPeriod::create('2000-01-01', '2000-02-01')->addDays(5)->subDays(3);
485
+     * ```
486
+     *
487
+     * @param object $mixin
488
+     *
489
+     * @throws \ReflectionException
490
+     *
491
+     * @return void
492
+     */
493
+    public static function mixin($mixin)
494
+    {
495
+        $reflection = new ReflectionClass($mixin);
496
+
497
+        $methods = $reflection->getMethods(
498
+            ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
499
+        );
500
+
501
+        foreach ($methods as $method) {
502
+            $method->setAccessible(true);
503
+
504
+            static::macro($method->name, $method->invoke($mixin));
505
+        }
506
+    }
507
+
508
+    /**
509
+     * Check if macro is registered.
510
+     *
511
+     * @param string $name
512
+     *
513
+     * @return bool
514
+     */
515
+    public static function hasMacro($name)
516
+    {
517
+        return isset(static::$macros[$name]);
518
+    }
519
+
520
+    /**
521
+     * Provide static proxy for instance aliases.
522
+     *
523
+     * @param string $method
524
+     * @param array  $parameters
525
+     *
526
+     * @return mixed
527
+     */
528
+    public static function __callStatic($method, $parameters)
529
+    {
530
+        return (new static)->$method(...$parameters);
531
+    }
532
+
533
+    /**
534
+     * CarbonPeriod constructor.
535
+     *
536
+     * @SuppressWarnings(PHPMD.ElseExpression)
537
+     *
538
+     * @throws InvalidArgumentException
539
+     */
540
+    public function __construct(...$arguments)
541
+    {
542
+        // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
543
+        // which will be first parsed into parts and then processed the same way.
544
+
545
+        if (count($arguments) && static::isIso8601($iso = $arguments[0])) {
546
+            array_splice($arguments, 0, 1, static::parseIso8601($iso));
547
+        }
548
+
549
+        foreach ($arguments as $argument) {
550
+            if ($this->dateInterval === null &&
551
+                (
552
+                    is_string($argument) && preg_match('/^(\d.*|P[T0-9].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i', $argument) ||
553
+                    $argument instanceof DateInterval
554
+                ) &&
555
+                $parsed = @CarbonInterval::make($argument)
556
+            ) {
557
+                $this->setDateInterval($parsed);
558
+            } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) {
559
+                $this->setStartDate($parsed);
560
+            } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) {
561
+                $this->setEndDate($parsed);
562
+            } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) {
563
+                $this->setRecurrences($argument);
564
+            } elseif ($this->options === null && (is_int($argument) || $argument === null)) {
565
+                $this->setOptions($argument);
566
+            } else {
567
+                throw new InvalidArgumentException('Invalid constructor parameters.');
568
+            }
569
+        }
570
+
571
+        if ($this->startDate === null) {
572
+            $this->setStartDate(Carbon::now());
573
+        }
574
+
575
+        if ($this->dateInterval === null) {
576
+            $this->setDateInterval(CarbonInterval::day());
577
+
578
+            $this->isDefaultInterval = true;
579
+        }
580
+
581
+        if ($this->options === null) {
582
+            $this->setOptions(0);
583
+        }
584
+    }
585
+
586
+    /**
587
+     * Return whether given callable is a string pointing to one of Carbon's is* methods
588
+     * and should be automatically converted to a filter callback.
589
+     *
590
+     * @param callable $callable
591
+     *
592
+     * @return bool
593
+     */
594
+    protected function isCarbonPredicateMethod($callable)
595
+    {
596
+        return is_string($callable) && substr($callable, 0, 2) === 'is' && (method_exists($this->dateClass, $callable) || call_user_func([$this->dateClass, 'hasMacro'], $callable));
597
+    }
598
+
599
+    /**
600
+     * Set the iteration item class.
601
+     *
602
+     * @param string $dateClass
603
+     *
604
+     * @return $this
605
+     */
606
+    public function setDateClass(string $dateClass)
607
+    {
608
+        if (!is_a($dateClass, CarbonInterface::class, true)) {
609
+            throw new InvalidArgumentException(sprintf(
610
+                'Given class does not implement %s: %s',
611
+                CarbonInterface::class,
612
+                $dateClass
613
+            ));
614
+        }
615
+
616
+        $this->dateClass = $dateClass;
617
+
618
+        if (is_a($dateClass, Carbon::class, true)) {
619
+            $this->toggleOptions(static::IMMUTABLE, false);
620
+        } elseif (is_a($dateClass, CarbonImmutable::class, true)) {
621
+            $this->toggleOptions(static::IMMUTABLE, true);
622
+        }
623
+
624
+        return $this;
625
+    }
626
+
627
+    /**
628
+     * Returns iteration item date class.
629
+     *
630
+     * @return string
631
+     */
632
+    public function getDateClass(): string
633
+    {
634
+        return $this->dateClass;
635
+    }
636
+
637
+    /**
638
+     * Change the period date interval.
639
+     *
640
+     * @param DateInterval|string $interval
641
+     *
642
+     * @throws \InvalidArgumentException
643
+     *
644
+     * @return $this
645
+     */
646
+    public function setDateInterval($interval)
647
+    {
648
+        if (!$interval = CarbonInterval::make($interval)) {
649
+            throw new InvalidArgumentException('Invalid interval.');
650
+        }
651
+
652
+        if ($interval->spec() === 'PT0S' && !$interval->f) {
653
+            throw new InvalidArgumentException('Empty interval is not accepted.');
654
+        }
655
+
656
+        $this->dateInterval = $interval;
657
+
658
+        $this->isDefaultInterval = false;
659
+
660
+        $this->handleChangedParameters();
661
+
662
+        return $this;
663
+    }
664
+
665
+    /**
666
+     * Invert the period date interval.
667
+     *
668
+     * @return $this
669
+     */
670
+    public function invertDateInterval()
671
+    {
672
+        $interval = $this->dateInterval->invert();
673
+
674
+        return $this->setDateInterval($interval);
675
+    }
676
+
677
+    /**
678
+     * Set start and end date.
679
+     *
680
+     * @param DateTime|DateTimeInterface|string      $start
681
+     * @param DateTime|DateTimeInterface|string|null $end
682
+     *
683
+     * @return $this
684
+     */
685
+    public function setDates($start, $end)
686
+    {
687
+        $this->setStartDate($start);
688
+        $this->setEndDate($end);
689
+
690
+        return $this;
691
+    }
692
+
693
+    /**
694
+     * Change the period options.
695
+     *
696
+     * @param int|null $options
697
+     *
698
+     * @throws \InvalidArgumentException
699
+     *
700
+     * @return $this
701
+     */
702
+    public function setOptions($options)
703
+    {
704
+        if (!is_int($options) && !is_null($options)) {
705
+            throw new InvalidArgumentException('Invalid options.');
706
+        }
707
+
708
+        $this->options = $options ?: 0;
709
+
710
+        $this->handleChangedParameters();
711
+
712
+        return $this;
713
+    }
714
+
715
+    /**
716
+     * Get the period options.
717
+     *
718
+     * @return int
719
+     */
720
+    public function getOptions()
721
+    {
722
+        return $this->options;
723
+    }
724
+
725
+    /**
726
+     * Toggle given options on or off.
727
+     *
728
+     * @param int       $options
729
+     * @param bool|null $state
730
+     *
731
+     * @throws \InvalidArgumentException
732
+     *
733
+     * @return $this
734
+     */
735
+    public function toggleOptions($options, $state = null)
736
+    {
737
+        if ($state === null) {
738
+            $state = ($this->options & $options) !== $options;
739
+        }
740
+
741
+        return $this->setOptions(
742
+            $state ?
743
+            $this->options | $options :
744
+            $this->options & ~$options
745
+        );
746
+    }
747
+
748
+    /**
749
+     * Toggle EXCLUDE_START_DATE option.
750
+     *
751
+     * @param bool $state
752
+     *
753
+     * @return $this
754
+     */
755
+    public function excludeStartDate($state = true)
756
+    {
757
+        return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
758
+    }
759
+
760
+    /**
761
+     * Toggle EXCLUDE_END_DATE option.
762
+     *
763
+     * @param bool $state
764
+     *
765
+     * @return $this
766
+     */
767
+    public function excludeEndDate($state = true)
768
+    {
769
+        return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);
770
+    }
771
+
772
+    /**
773
+     * Get the underlying date interval.
774
+     *
775
+     * @return CarbonInterval
776
+     */
777
+    public function getDateInterval()
778
+    {
779
+        return $this->dateInterval->copy();
780
+    }
781
+
782
+    /**
783
+     * Get start date of the period.
784
+     *
785
+     * @return CarbonInterface
786
+     */
787
+    public function getStartDate()
788
+    {
789
+        return $this->startDate->copy();
790
+    }
791
+
792
+    /**
793
+     * Get end date of the period.
794
+     *
795
+     * @return CarbonInterface|null
796
+     */
797
+    public function getEndDate()
798
+    {
799
+        return $this->endDate ? $this->endDate->copy() : null;
800
+    }
801
+
802
+    /**
803
+     * Get number of recurrences.
804
+     *
805
+     * @return int|null
806
+     */
807
+    public function getRecurrences()
808
+    {
809
+        return $this->recurrences;
810
+    }
811
+
812
+    /**
813
+     * Returns true if the start date should be excluded.
814
+     *
815
+     * @return bool
816
+     */
817
+    public function isStartExcluded()
818
+    {
819
+        return ($this->options & static::EXCLUDE_START_DATE) !== 0;
820
+    }
821
+
822
+    /**
823
+     * Returns true if the end date should be excluded.
824
+     *
825
+     * @return bool
826
+     */
827
+    public function isEndExcluded()
828
+    {
829
+        return ($this->options & static::EXCLUDE_END_DATE) !== 0;
830
+    }
831
+
832
+    /**
833
+     * Add a filter to the stack.
834
+     *
835
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
836
+     *
837
+     * @param callable $callback
838
+     * @param string   $name
839
+     *
840
+     * @return $this
841
+     */
842
+    public function addFilter($callback, $name = null)
843
+    {
844
+        $tuple = $this->createFilterTuple(func_get_args());
845
+
846
+        $this->filters[] = $tuple;
847
+
848
+        $this->handleChangedParameters();
849
+
850
+        return $this;
851
+    }
852
+
853
+    /**
854
+     * Prepend a filter to the stack.
855
+     *
856
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
857
+     *
858
+     * @param callable $callback
859
+     * @param string   $name
860
+     *
861
+     * @return $this
862
+     */
863
+    public function prependFilter($callback, $name = null)
864
+    {
865
+        $tuple = $this->createFilterTuple(func_get_args());
866
+
867
+        array_unshift($this->filters, $tuple);
868
+
869
+        $this->handleChangedParameters();
870
+
871
+        return $this;
872
+    }
873
+
874
+    /**
875
+     * Create a filter tuple from raw parameters.
876
+     *
877
+     * Will create an automatic filter callback for one of Carbon's is* methods.
878
+     *
879
+     * @param array $parameters
880
+     *
881
+     * @return array
882
+     */
883
+    protected function createFilterTuple(array $parameters)
884
+    {
885
+        $method = array_shift($parameters);
886
+
887
+        if (!$this->isCarbonPredicateMethod($method)) {
888
+            return [$method, array_shift($parameters)];
889
+        }
890
+
891
+        return [function ($date) use ($method, $parameters) {
892
+            return call_user_func_array([$date, $method], $parameters);
893
+        }, $method];
894
+    }
895
+
896
+    /**
897
+     * Remove a filter by instance or name.
898
+     *
899
+     * @param callable|string $filter
900
+     *
901
+     * @return $this
902
+     */
903
+    public function removeFilter($filter)
904
+    {
905
+        $key = is_callable($filter) ? 0 : 1;
906
+
907
+        $this->filters = array_values(array_filter(
908
+            $this->filters,
909
+            function ($tuple) use ($key, $filter) {
910
+                return $tuple[$key] !== $filter;
911
+            }
912
+        ));
913
+
914
+        $this->updateInternalState();
915
+
916
+        $this->handleChangedParameters();
917
+
918
+        return $this;
919
+    }
920
+
921
+    /**
922
+     * Return whether given instance or name is in the filter stack.
923
+     *
924
+     * @param callable|string $filter
925
+     *
926
+     * @return bool
927
+     */
928
+    public function hasFilter($filter)
929
+    {
930
+        $key = is_callable($filter) ? 0 : 1;
931
+
932
+        foreach ($this->filters as $tuple) {
933
+            if ($tuple[$key] === $filter) {
934
+                return true;
935
+            }
936
+        }
937
+
938
+        return false;
939
+    }
940
+
941
+    /**
942
+     * Get filters stack.
943
+     *
944
+     * @return array
945
+     */
946
+    public function getFilters()
947
+    {
948
+        return $this->filters;
949
+    }
950
+
951
+    /**
952
+     * Set filters stack.
953
+     *
954
+     * @param array $filters
955
+     *
956
+     * @return $this
957
+     */
958
+    public function setFilters(array $filters)
959
+    {
960
+        $this->filters = $filters;
961
+
962
+        $this->updateInternalState();
963
+
964
+        $this->handleChangedParameters();
965
+
966
+        return $this;
967
+    }
968
+
969
+    /**
970
+     * Reset filters stack.
971
+     *
972
+     * @return $this
973
+     */
974
+    public function resetFilters()
975
+    {
976
+        $this->filters = [];
977
+
978
+        if ($this->endDate !== null) {
979
+            $this->filters[] = [static::END_DATE_FILTER, null];
980
+        }
981
+
982
+        if ($this->recurrences !== null) {
983
+            $this->filters[] = [static::RECURRENCES_FILTER, null];
984
+        }
985
+
986
+        $this->handleChangedParameters();
987
+
988
+        return $this;
989
+    }
990
+
991
+    /**
992
+     * Update properties after removing built-in filters.
993
+     *
994
+     * @return void
995
+     */
996
+    protected function updateInternalState()
997
+    {
998
+        if (!$this->hasFilter(static::END_DATE_FILTER)) {
999
+            $this->endDate = null;
1000
+        }
1001
+
1002
+        if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
1003
+            $this->recurrences = null;
1004
+        }
1005
+    }
1006
+
1007
+    /**
1008
+     * Add a recurrences filter (set maximum number of recurrences).
1009
+     *
1010
+     * @param int|null $recurrences
1011
+     *
1012
+     * @throws \InvalidArgumentException
1013
+     *
1014
+     * @return $this
1015
+     */
1016
+    public function setRecurrences($recurrences)
1017
+    {
1018
+        if (!is_numeric($recurrences) && !is_null($recurrences) || $recurrences < 0) {
1019
+            throw new InvalidArgumentException('Invalid number of recurrences.');
1020
+        }
1021
+
1022
+        if ($recurrences === null) {
1023
+            return $this->removeFilter(static::RECURRENCES_FILTER);
1024
+        }
1025
+
1026
+        $this->recurrences = (int) $recurrences;
1027
+
1028
+        if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
1029
+            return $this->addFilter(static::RECURRENCES_FILTER);
1030
+        }
1031
+
1032
+        $this->handleChangedParameters();
1033
+
1034
+        return $this;
1035
+    }
1036
+
1037
+    /**
1038
+     * Recurrences filter callback (limits number of recurrences).
1039
+     *
1040
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
1041
+     *
1042
+     * @param \Carbon\Carbon $current
1043
+     * @param int            $key
1044
+     *
1045
+     * @return bool|string
1046
+     */
1047
+    protected function filterRecurrences($current, $key)
1048
+    {
1049
+        if ($key < $this->recurrences) {
1050
+            return true;
1051
+        }
1052
+
1053
+        return static::END_ITERATION;
1054
+    }
1055
+
1056
+    /**
1057
+     * Change the period start date.
1058
+     *
1059
+     * @param DateTime|DateTimeInterface|string $date
1060
+     * @param bool|null                         $inclusive
1061
+     *
1062
+     * @throws \InvalidArgumentException
1063
+     *
1064
+     * @return $this
1065
+     */
1066
+    public function setStartDate($date, $inclusive = null)
1067
+    {
1068
+        if (!$date = call_user_func([$this->dateClass, 'make'], $date)) {
1069
+            throw new InvalidArgumentException('Invalid start date.');
1070
+        }
1071
+
1072
+        $this->startDate = $date;
1073
+
1074
+        if ($inclusive !== null) {
1075
+            $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
1076
+        }
1077
+
1078
+        return $this;
1079
+    }
1080
+
1081
+    /**
1082
+     * Change the period end date.
1083
+     *
1084
+     * @param DateTime|DateTimeInterface|string|null $date
1085
+     * @param bool|null                              $inclusive
1086
+     *
1087
+     * @throws \InvalidArgumentException
1088
+     *
1089
+     * @return $this
1090
+     */
1091
+    public function setEndDate($date, $inclusive = null)
1092
+    {
1093
+        if (!is_null($date) && !$date = call_user_func([$this->dateClass, 'make'], $date)) {
1094
+            throw new InvalidArgumentException('Invalid end date.');
1095
+        }
1096
+
1097
+        if (!$date) {
1098
+            return $this->removeFilter(static::END_DATE_FILTER);
1099
+        }
1100
+
1101
+        $this->endDate = $date;
1102
+
1103
+        if ($inclusive !== null) {
1104
+            $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
1105
+        }
1106
+
1107
+        if (!$this->hasFilter(static::END_DATE_FILTER)) {
1108
+            return $this->addFilter(static::END_DATE_FILTER);
1109
+        }
1110
+
1111
+        $this->handleChangedParameters();
1112
+
1113
+        return $this;
1114
+    }
1115
+
1116
+    /**
1117
+     * End date filter callback.
1118
+     *
1119
+     * @param \Carbon\Carbon $current
1120
+     *
1121
+     * @return bool|string
1122
+     */
1123
+    protected function filterEndDate($current)
1124
+    {
1125
+        if (!$this->isEndExcluded() && $current == $this->endDate) {
1126
+            return true;
1127
+        }
1128
+
1129
+        if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
1130
+            return true;
1131
+        }
1132
+
1133
+        return static::END_ITERATION;
1134
+    }
1135
+
1136
+    /**
1137
+     * End iteration filter callback.
1138
+     *
1139
+     * @return string
1140
+     */
1141
+    protected function endIteration()
1142
+    {
1143
+        return static::END_ITERATION;
1144
+    }
1145
+
1146
+    /**
1147
+     * Handle change of the parameters.
1148
+     */
1149
+    protected function handleChangedParameters()
1150
+    {
1151
+        if (($this->getOptions() & static::IMMUTABLE) && $this->dateClass === Carbon::class) {
1152
+            $this->setDateClass(CarbonImmutable::class);
1153
+        } elseif (!($this->getOptions() & static::IMMUTABLE) && $this->dateClass === CarbonImmutable::class) {
1154
+            $this->setDateClass(Carbon::class);
1155
+        }
1156
+
1157
+        $this->validationResult = null;
1158
+    }
1159
+
1160
+    /**
1161
+     * Validate current date and stop iteration when necessary.
1162
+     *
1163
+     * Returns true when current date is valid, false if it is not, or static::END_ITERATION
1164
+     * when iteration should be stopped.
1165
+     *
1166
+     * @return bool|string
1167
+     */
1168
+    protected function validateCurrentDate()
1169
+    {
1170
+        if ($this->current === null) {
1171
+            $this->rewind();
1172
+        }
1173
+
1174
+        // Check after the first rewind to avoid repeating the initial validation.
1175
+        if ($this->validationResult !== null) {
1176
+            return $this->validationResult;
1177
+        }
1178
+
1179
+        return $this->validationResult = $this->checkFilters();
1180
+    }
1181
+
1182
+    /**
1183
+     * Check whether current value and key pass all the filters.
1184
+     *
1185
+     * @return bool|string
1186
+     */
1187
+    protected function checkFilters()
1188
+    {
1189
+        $current = $this->prepareForReturn($this->current);
1190
+
1191
+        foreach ($this->filters as $tuple) {
1192
+            $result = call_user_func(
1193
+                $tuple[0],
1194
+                $current->copy(),
1195
+                $this->key,
1196
+                $this
1197
+            );
1198
+
1199
+            if ($result === static::END_ITERATION) {
1200
+                return static::END_ITERATION;
1201
+            }
1202
+
1203
+            if (!$result) {
1204
+                return false;
1205
+            }
1206
+        }
1207
+
1208
+        return true;
1209
+    }
1210
+
1211
+    /**
1212
+     * Prepare given date to be returned to the external logic.
1213
+     *
1214
+     * @param CarbonInterface $date
1215
+     *
1216
+     * @return CarbonInterface
1217
+     */
1218
+    protected function prepareForReturn(CarbonInterface $date)
1219
+    {
1220
+        $date = call_user_func([$this->dateClass, 'make'], $date);
1221
+
1222
+        if ($this->timezone) {
1223
+            $date = $date->setTimezone($this->timezone);
1224
+        }
1225
+
1226
+        return $date;
1227
+    }
1228
+
1229
+    /**
1230
+     * Check if the current position is valid.
1231
+     *
1232
+     * @return bool
1233
+     */
1234
+    public function valid()
1235
+    {
1236
+        return $this->validateCurrentDate() === true;
1237
+    }
1238
+
1239
+    /**
1240
+     * Return the current key.
1241
+     *
1242
+     * @return int|null
1243
+     */
1244
+    public function key()
1245
+    {
1246
+        if ($this->valid()) {
1247
+            return $this->key;
1248
+        }
1249
+    }
1250
+
1251
+    /**
1252
+     * Return the current date.
1253
+     *
1254
+     * @return CarbonInterface|null
1255
+     */
1256
+    public function current()
1257
+    {
1258
+        if ($this->valid()) {
1259
+            return $this->prepareForReturn($this->current);
1260
+        }
1261
+    }
1262
+
1263
+    /**
1264
+     * Move forward to the next date.
1265
+     *
1266
+     * @throws \RuntimeException
1267
+     *
1268
+     * @return void
1269
+     */
1270
+    public function next()
1271
+    {
1272
+        if ($this->current === null) {
1273
+            $this->rewind();
1274
+        }
1275
+
1276
+        if ($this->validationResult !== static::END_ITERATION) {
1277
+            $this->key++;
1278
+
1279
+            $this->incrementCurrentDateUntilValid();
1280
+        }
1281
+    }
1282
+
1283
+    /**
1284
+     * Rewind to the start date.
1285
+     *
1286
+     * Iterating over a date in the UTC timezone avoids bug during backward DST change.
1287
+     *
1288
+     * @see https://bugs.php.net/bug.php?id=72255
1289
+     * @see https://bugs.php.net/bug.php?id=74274
1290
+     * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time
1291
+     *
1292
+     * @throws \RuntimeException
1293
+     *
1294
+     * @return void
1295
+     */
1296
+    public function rewind()
1297
+    {
1298
+        $this->key = 0;
1299
+        $this->current = call_user_func([$this->dateClass, 'make'], $this->startDate);
1300
+        $settings = $this->getSettings();
1301
+        $locale = $this->getLocalTranslator()->getLocale();
1302
+        if ($locale) {
1303
+            $settings['locale'] = $locale;
1304
+        }
1305
+        $this->current->settings($settings);
1306
+        $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null;
1307
+
1308
+        if ($this->timezone) {
1309
+            $this->current = $this->current->utc();
1310
+        }
1311
+
1312
+        $this->validationResult = null;
1313
+
1314
+        if ($this->isStartExcluded() || $this->validateCurrentDate() === false) {
1315
+            $this->incrementCurrentDateUntilValid();
1316
+        }
1317
+    }
1318
+
1319
+    /**
1320
+     * Skip iterations and returns iteration state (false if ended, true if still valid).
1321
+     *
1322
+     * @param int $count steps number to skip (1 by default)
1323
+     *
1324
+     * @return bool
1325
+     */
1326
+    public function skip($count = 1)
1327
+    {
1328
+        for ($i = $count; $this->valid() && $i > 0; $i--) {
1329
+            $this->next();
1330
+        }
1331
+
1332
+        return $this->valid();
1333
+    }
1334
+
1335
+    /**
1336
+     * Keep incrementing the current date until a valid date is found or the iteration is ended.
1337
+     *
1338
+     * @throws \RuntimeException
1339
+     *
1340
+     * @return void
1341
+     */
1342
+    protected function incrementCurrentDateUntilValid()
1343
+    {
1344
+        $attempts = 0;
1345
+
1346
+        do {
1347
+            $this->current = $this->current->add($this->dateInterval);
1348
+
1349
+            $this->validationResult = null;
1350
+
1351
+            if (++$attempts > static::NEXT_MAX_ATTEMPTS) {
1352
+                throw new RuntimeException('Could not find next valid date.');
1353
+            }
1354
+        } while ($this->validateCurrentDate() === false);
1355
+    }
1356
+
1357
+    /**
1358
+     * Format the date period as ISO 8601.
1359
+     *
1360
+     * @return string
1361
+     */
1362
+    public function toIso8601String()
1363
+    {
1364
+        $parts = [];
1365
+
1366
+        if ($this->recurrences !== null) {
1367
+            $parts[] = 'R'.$this->recurrences;
1368
+        }
1369
+
1370
+        $parts[] = $this->startDate->toIso8601String();
1371
+
1372
+        $parts[] = $this->dateInterval->spec();
1373
+
1374
+        if ($this->endDate !== null) {
1375
+            $parts[] = $this->endDate->toIso8601String();
1376
+        }
1377
+
1378
+        return implode('/', $parts);
1379
+    }
1380
+
1381
+    /**
1382
+     * Convert the date period into a string.
1383
+     *
1384
+     * @return string
1385
+     */
1386
+    public function toString()
1387
+    {
1388
+        $translator = call_user_func([$this->dateClass, 'getTranslator']);
1389
+
1390
+        $parts = [];
1391
+
1392
+        $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay()
1393
+            ? 'Y-m-d H:i:s'
1394
+            : 'Y-m-d';
1395
+
1396
+        if ($this->recurrences !== null) {
1397
+            $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator);
1398
+        }
1399
+
1400
+        $parts[] = $this->translate('period_interval', [':interval' => $this->dateInterval->forHumans([
1401
+            'join' => true,
1402
+        ])], null, $translator);
1403
+
1404
+        $parts[] = $this->translate('period_start_date', [':date' => $this->startDate->rawFormat($format)], null, $translator);
1405
+
1406
+        if ($this->endDate !== null) {
1407
+            $parts[] = $this->translate('period_end_date', [':date' => $this->endDate->rawFormat($format)], null, $translator);
1408
+        }
1409
+
1410
+        $result = implode(' ', $parts);
1411
+
1412
+        return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1);
1413
+    }
1414
+
1415
+    /**
1416
+     * Format the date period as ISO 8601.
1417
+     *
1418
+     * @return string
1419
+     */
1420
+    public function spec()
1421
+    {
1422
+        return $this->toIso8601String();
1423
+    }
1424
+
1425
+    /**
1426
+     * Convert the date period into an array without changing current iteration state.
1427
+     *
1428
+     * @return array
1429
+     */
1430
+    public function toArray()
1431
+    {
1432
+        $state = [
1433
+            $this->key,
1434
+            $this->current ? $this->current->copy() : null,
1435
+            $this->validationResult,
1436
+        ];
1437
+
1438
+        $result = iterator_to_array($this);
1439
+
1440
+        [
1441
+            $this->key,
1442
+            $this->current,
1443
+            $this->validationResult
1444
+        ] = $state;
1445
+
1446
+        return $result;
1447
+    }
1448
+
1449
+    /**
1450
+     * Count dates in the date period.
1451
+     *
1452
+     * @return int
1453
+     */
1454
+    public function count()
1455
+    {
1456
+        return count($this->toArray());
1457
+    }
1458
+
1459
+    /**
1460
+     * Return the first date in the date period.
1461
+     *
1462
+     * @return CarbonInterface|null
1463
+     */
1464
+    public function first()
1465
+    {
1466
+        if ($array = $this->toArray()) {
1467
+            return $array[0];
1468
+        }
1469
+    }
1470
+
1471
+    /**
1472
+     * Return the last date in the date period.
1473
+     *
1474
+     * @return CarbonInterface|null
1475
+     */
1476
+    public function last()
1477
+    {
1478
+        if ($array = $this->toArray()) {
1479
+            return $array[count($array) - 1];
1480
+        }
1481
+    }
1482
+
1483
+    /**
1484
+     * Call given macro.
1485
+     *
1486
+     * @param string $name
1487
+     * @param array  $parameters
1488
+     *
1489
+     * @return mixed
1490
+     */
1491
+    protected function callMacro($name, $parameters)
1492
+    {
1493
+        $macro = static::$macros[$name];
1494
+
1495
+        if ($macro instanceof Closure) {
1496
+            return call_user_func_array($macro->bindTo($this, static::class), $parameters);
1497
+        }
1498
+
1499
+        return call_user_func_array($macro, $parameters);
1500
+    }
1501
+
1502
+    /**
1503
+     * Convert the date period into a string.
1504
+     *
1505
+     * @return string
1506
+     */
1507
+    public function __toString()
1508
+    {
1509
+        return $this->toString();
1510
+    }
1511
+
1512
+    /**
1513
+     * Add aliases for setters.
1514
+     *
1515
+     * CarbonPeriod::days(3)->hours(5)->invert()
1516
+     *     ->sinceNow()->until('2010-01-10')
1517
+     *     ->filter(...)
1518
+     *     ->count()
1519
+     *
1520
+     * Note: We use magic method to let static and instance aliases with the same names.
1521
+     *
1522
+     * @param string $method
1523
+     * @param array  $parameters
1524
+     *
1525
+     * @return mixed
1526
+     */
1527
+    public function __call($method, $parameters)
1528
+    {
1529
+        if (static::hasMacro($method)) {
1530
+            return $this->callMacro($method, $parameters);
1531
+        }
1532
+
1533
+        $first = count($parameters) >= 1 ? $parameters[0] : null;
1534
+        $second = count($parameters) >= 2 ? $parameters[1] : null;
1535
+
1536
+        switch ($method) {
1537
+            case 'start':
1538
+            case 'since':
1539
+                return $this->setStartDate($first, $second);
1540
+
1541
+            case 'sinceNow':
1542
+                return $this->setStartDate(new Carbon, $first);
1543
+
1544
+            case 'end':
1545
+            case 'until':
1546
+                return $this->setEndDate($first, $second);
1547
+
1548
+            case 'untilNow':
1549
+                return $this->setEndDate(new Carbon, $first);
1550
+
1551
+            case 'dates':
1552
+            case 'between':
1553
+                return $this->setDates($first, $second);
1554
+
1555
+            case 'recurrences':
1556
+            case 'times':
1557
+                return $this->setRecurrences($first);
1558
+
1559
+            case 'options':
1560
+                return $this->setOptions($first);
1561
+
1562
+            case 'toggle':
1563
+                return $this->toggleOptions($first, $second);
1564
+
1565
+            case 'filter':
1566
+            case 'push':
1567
+                return $this->addFilter($first, $second);
1568
+
1569
+            case 'prepend':
1570
+                return $this->prependFilter($first, $second);
1571
+
1572
+            case 'filters':
1573
+                return $this->setFilters($first ?: []);
1574
+
1575
+            case 'interval':
1576
+            case 'each':
1577
+            case 'every':
1578
+            case 'step':
1579
+            case 'stepBy':
1580
+                return $this->setDateInterval($first);
1581
+
1582
+            case 'invert':
1583
+                return $this->invertDateInterval();
1584
+
1585
+            case 'years':
1586
+            case 'year':
1587
+            case 'months':
1588
+            case 'month':
1589
+            case 'weeks':
1590
+            case 'week':
1591
+            case 'days':
1592
+            case 'dayz':
1593
+            case 'day':
1594
+            case 'hours':
1595
+            case 'hour':
1596
+            case 'minutes':
1597
+            case 'minute':
1598
+            case 'seconds':
1599
+            case 'second':
1600
+                return $this->setDateInterval(call_user_func(
1601
+                    // Override default P1D when instantiating via fluent setters.
1602
+                    [$this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method],
1603
+                    count($parameters) === 0 ? 1 : $first
1604
+                ));
1605
+        }
1606
+
1607
+        if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) {
1608
+            throw new BadMethodCallException("Method $method does not exist.");
1609
+        }
1610
+
1611
+        return $this;
1612
+    }
1613
+
1614
+    /**
1615
+     * Set the instance's timezone from a string or object and add/subtract the offset difference.
1616
+     *
1617
+     * @param \DateTimeZone|string $timezone
1618
+     *
1619
+     * @return static
1620
+     */
1621
+    public function shiftTimezone($timezone)
1622
+    {
1623
+        $this->tzName = $timezone;
1624
+        $this->timezone = $timezone;
1625
+
1626
+        return $this;
1627
+    }
1628
+
1629
+    /**
1630
+     * Returns the end is set, else calculated from start an recurrences.
1631
+     *
1632
+     * @return CarbonInterface
1633
+     */
1634
+    public function calculateEnd()
1635
+    {
1636
+        if ($end = $this->getEndDate()) {
1637
+            return $end;
1638
+        }
1639
+
1640
+        $dates = iterator_to_array($this);
1641
+
1642
+        return end($dates);
1643
+    }
1644
+
1645
+    /**
1646
+     * Returns true if the current period overlaps the given one (if 1 parameter passed)
1647
+     * or the period between 2 dates (if 2 parameters passed).
1648
+     *
1649
+     * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart
1650
+     * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null         $rangeEnd
1651
+     *
1652
+     * @return bool
1653
+     */
1654
+    public function overlaps($rangeOrRangeStart, $rangeEnd = null)
1655
+    {
1656
+        $range = $rangeEnd ? static::create($rangeOrRangeStart, $rangeEnd) : $rangeOrRangeStart;
1657
+
1658
+        if (!($range instanceof self)) {
1659
+            $range = static::create($range);
1660
+        }
1661
+
1662
+        return $this->calculateEnd() > $range->getStartDate() && $range->calculateEnd() > $this->getStartDate();
1663
+    }
1664
+
1665
+    /**
1666
+     * Execute a given function on each date of the period.
1667
+     *
1668
+     * @example
1669
+     * ```
1670
+     * Carbon::create('2020-11-29')->daysUntil('2020-12-24')->forEach(function (Carbon $date) {
1671
+     *   echo $date->diffInDays('2020-12-25')." days before Christmas!\n";
1672
+     * });
1673
+     * ```
1674
+     *
1675
+     * @param callable $callback
1676
+     */
1677
+    public function forEach(callable $callback)
1678
+    {
1679
+        foreach ($this as $date) {
1680
+            $callback($date);
1681
+        }
1682
+    }
1683
+
1684
+    /**
1685
+     * Execute a given function on each date of the period and yield the result of this function.
1686
+     *
1687
+     * @example
1688
+     * ```
1689
+     * $period = Carbon::create('2020-11-29')->daysUntil('2020-12-24');
1690
+     * echo implode("\n", iterator_to_array($period->map(function (Carbon $date) {
1691
+     *   return $date->diffInDays('2020-12-25').' days before Christmas!';
1692
+     * })));
1693
+     * ```
1694
+     *
1695
+     * @param callable $callback
1696
+     *
1697
+     * @return \Generator
1698
+     */
1699
+    public function map(callable $callback)
1700
+    {
1701
+        foreach ($this as $date) {
1702
+            yield $callback($date);
1703
+        }
1704
+    }
1705
+
1706
+    /**
1707
+     * Determines if the instance is equal to another
1708
+     *
1709
+     * @param mixed $period
1710
+     *
1711
+     * @see equalTo()
1712
+     *
1713
+     * @return bool
1714
+     */
1715
+    public function eq($period): bool
1716
+    {
1717
+        return $this->equalTo($period);
1718
+    }
1719
+
1720
+    /**
1721
+     * Determines if the instance is equal to another
1722
+     *
1723
+     * @param mixed $period
1724
+     *
1725
+     * @return bool
1726
+     */
1727
+    public function equalTo($period): bool
1728
+    {
1729
+        if (!($period instanceof self)) {
1730
+            $period = self::make($period);
1731
+        }
1732
+
1733
+        return $period !== null
1734
+            && $this->getDateInterval()->eq($period->getDateInterval())
1735
+            && $this->getStartDate()->eq($period->getStartDate())
1736
+            && $this->getEndDate()->eq($period->getEndDate())
1737
+            && $this->getOptions() === $period->getOptions();
1738
+    }
1739
+
1740
+    /**
1741
+     * Determines if the instance is not equal to another
1742
+     *
1743
+     * @param mixed $period
1744
+     *
1745
+     * @see notEqualTo()
1746
+     *
1747
+     * @return bool
1748
+     */
1749
+    public function ne($period): bool
1750
+    {
1751
+        return $this->notEqualTo($period);
1752
+    }
1753
+
1754
+    /**
1755
+     * Determines if the instance is not equal to another
1756
+     *
1757
+     * @param mixed $period
1758
+     *
1759
+     * @return bool
1760
+     */
1761
+    public function notEqualTo($period): bool
1762
+    {
1763
+        return !$this->eq($period);
1764
+    }
1765
+}