composer package updates
[openemr.git] / vendor / nesbot / carbon / src / Carbon / CarbonPeriod.php
blobae52c0b8b4da0c6fc1cffd1586a3e031ab0989a6
1 <?php
3 /*
4 * This file is part of the Carbon package.
6 * (c) Brian Nesbitt <brian@nesbot.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Carbon;
14 use BadMethodCallException;
15 use Closure;
16 use Countable;
17 use DateInterval;
18 use DateTime;
19 use DateTimeInterface;
20 use InvalidArgumentException;
21 use Iterator;
22 use ReflectionClass;
23 use ReflectionFunction;
24 use ReflectionMethod;
25 use RuntimeException;
27 /**
28 * Substitution of DatePeriod with some modifications and many more features.
29 * Fully compatible with PHP 5.3+!
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 = array()) 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().
101 class CarbonPeriod implements Iterator, Countable
104 * Built-in filters.
106 * @var string
108 const RECURRENCES_FILTER = 'Carbon\CarbonPeriod::filterRecurrences';
109 const END_DATE_FILTER = 'Carbon\CarbonPeriod::filterEndDate';
112 * Special value which can be returned by filters to end iteration. Also a filter.
114 * @var string
116 const END_ITERATION = 'Carbon\CarbonPeriod::endIteration';
119 * Available options.
121 * @var int
123 const EXCLUDE_START_DATE = 1;
124 const EXCLUDE_END_DATE = 2;
127 * Number of maximum attempts before giving up on finding next valid date.
129 * @var int
131 const NEXT_MAX_ATTEMPTS = 1000;
134 * The registered macros.
136 * @var array
138 protected static $macros = array();
141 * Underlying date interval instance. Always present, one day by default.
143 * @var CarbonInterval
145 protected $dateInterval;
148 * Whether current date interval was set by default.
150 * @var bool
152 protected $isDefaultInterval;
155 * The filters stack.
157 * @var array
159 protected $filters = array();
162 * Period start date. Applied on rewind. Always present, now by default.
164 * @var Carbon
166 protected $startDate;
169 * Period end date. For inverted interval should be before the start date. Applied via a filter.
171 * @var Carbon|null
173 protected $endDate;
176 * Limit for number of recurrences. Applied via a filter.
178 * @var int|null
180 protected $recurrences;
183 * Iteration options.
185 * @var int
187 protected $options;
190 * Index of current date. Always sequential, even if some dates are skipped by filters.
191 * Equal to null only before the first iteration.
193 * @var int
195 protected $key;
198 * Current date. May temporarily hold unaccepted value when looking for a next valid date.
199 * Equal to null only before the first iteration.
201 * @var Carbon
203 protected $current;
206 * Timezone of current date. Taken from the start date.
208 * @var \DateTimeZone|null
210 protected $timezone;
213 * The cached validation result for current date.
215 * @var bool|string|null
217 protected $validationResult;
220 * Create a new instance.
222 * @return static
224 public static function create()
226 return static::createFromArray(func_get_args());
230 * Create a new instance from an array of parameters.
232 * @param array $params
234 * @return static
236 public static function createFromArray(array $params)
238 // PHP 5.3 equivalent of new static(...$params).
239 $reflection = new ReflectionClass(get_class());
240 /** @var static $instance */
241 $instance = $reflection->newInstanceArgs($params);
243 return $instance;
247 * Create CarbonPeriod from ISO 8601 string.
249 * @param string $iso
250 * @param int|null $options
252 * @return static
254 public static function createFromIso($iso, $options = null)
256 $params = static::parseIso8601($iso);
258 $instance = static::createFromArray($params);
260 if ($options !== null) {
261 $instance->setOptions($options);
264 return $instance;
268 * Return whether given interval contains non zero value of any time unit.
270 * @param \DateInterval $interval
272 * @return bool
274 protected static function intervalHasTime(DateInterval $interval)
276 // The array_key_exists and get_object_vars are used as a workaround to check microsecond support.
277 // Both isset and property_exists will fail on PHP 7.0.14 - 7.0.21 due to the following bug:
278 // https://bugs.php.net/bug.php?id=74852
279 return $interval->h || $interval->i || $interval->s || array_key_exists('f', get_object_vars($interval)) && $interval->f;
283 * Return whether given callable is a string pointing to one of Carbon's is* methods
284 * and should be automatically converted to a filter callback.
286 * @param callable $callable
288 * @return bool
290 protected static function isCarbonPredicateMethod($callable)
292 return is_string($callable) && substr($callable, 0, 2) === 'is' && (method_exists('Carbon\Carbon', $callable) || Carbon::hasMacro($callable));
296 * Return whether given variable is an ISO 8601 specification.
298 * Note: Check is very basic, as actual validation will be done later when parsing.
299 * We just want to ensure that variable is not any other type of a valid parameter.
301 * @param mixed $var
303 * @return bool
305 protected static function isIso8601($var)
307 if (!is_string($var)) {
308 return false;
311 // Match slash but not within a timezone name.
312 $part = '[a-z]+(?:[_-][a-z]+)*';
314 preg_match("#\b$part/$part\b|(/)#i", $var, $match);
316 return isset($match[1]);
320 * Parse given ISO 8601 string into an array of arguments.
322 * @param string $iso
324 * @return array
326 protected static function parseIso8601($iso)
328 $result = array();
330 $interval = null;
331 $start = null;
332 $end = null;
334 foreach (explode('/', $iso) as $key => $part) {
335 if ($key === 0 && preg_match('/^R([0-9]*)$/', $part, $match)) {
336 $parsed = strlen($match[1]) ? (int) $match[1] : null;
337 } elseif ($interval === null && $parsed = CarbonInterval::make($part)) {
338 $interval = $part;
339 } elseif ($start === null && $parsed = Carbon::make($part)) {
340 $start = $part;
341 } elseif ($end === null && $parsed = Carbon::make(static::addMissingParts($start, $part))) {
342 $end = $part;
343 } else {
344 throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso.");
347 $result[] = $parsed;
350 return $result;
354 * Add missing parts of the target date from the soure date.
356 * @param string $source
357 * @param string $target
359 * @return string
361 protected static function addMissingParts($source, $target)
363 $pattern = '/'.preg_replace('/[0-9]+/', '[0-9]+', preg_quote($target, '/')).'$/';
365 $result = preg_replace($pattern, $target, $source, 1, $count);
367 return $count ? $result : $target;
371 * Register a custom macro.
373 * @param string $name
374 * @param object|callable $macro
376 * @return void
378 public static function macro($name, $macro)
380 static::$macros[$name] = $macro;
384 * Register macros from a mixin object.
386 * @param object $mixin
388 * @throws \ReflectionException
390 * @return void
392 public static function mixin($mixin)
394 $reflection = new ReflectionClass($mixin);
396 $methods = $reflection->getMethods(
397 ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
400 foreach ($methods as $method) {
401 $method->setAccessible(true);
403 static::macro($method->name, $method->invoke($mixin));
408 * Check if macro is registered.
410 * @param string $name
412 * @return bool
414 public static function hasMacro($name)
416 return isset(static::$macros[$name]);
420 * Provide static proxy for instance aliases.
422 * @param string $method
423 * @param array $parameters
425 * @return mixed
427 public static function __callStatic($method, $parameters)
429 return call_user_func_array(
430 array(new static, $method), $parameters
435 * CarbonPeriod constructor.
437 * @throws InvalidArgumentException
439 public function __construct()
441 // Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
442 // which will be first parsed into parts and then processed the same way.
443 $arguments = func_get_args();
445 if (count($arguments) && static::isIso8601($iso = $arguments[0])) {
446 array_splice($arguments, 0, 1, static::parseIso8601($iso));
449 foreach ($arguments as $argument) {
450 if ($this->dateInterval === null && $parsed = CarbonInterval::make($argument)) {
451 $this->setDateInterval($parsed);
452 } elseif ($this->startDate === null && $parsed = Carbon::make($argument)) {
453 $this->setStartDate($parsed);
454 } elseif ($this->endDate === null && $parsed = Carbon::make($argument)) {
455 $this->setEndDate($parsed);
456 } elseif ($this->recurrences === null && $this->endDate === null && is_numeric($argument)) {
457 $this->setRecurrences($argument);
458 } elseif ($this->options === null && (is_int($argument) || $argument === null)) {
459 $this->setOptions($argument);
460 } else {
461 throw new InvalidArgumentException('Invalid constructor parameters.');
465 if ($this->startDate === null) {
466 $this->setStartDate(Carbon::now());
469 if ($this->dateInterval === null) {
470 $this->setDateInterval(CarbonInterval::day());
472 $this->isDefaultInterval = true;
475 if ($this->options === null) {
476 $this->setOptions(0);
481 * Change the period date interval.
483 * @param DateInterval|string $interval
485 * @throws \InvalidArgumentException
487 * @return $this
489 public function setDateInterval($interval)
491 if (!$interval = CarbonInterval::make($interval)) {
492 throw new InvalidArgumentException('Invalid interval.');
495 if ($interval->spec() === 'PT0S') {
496 throw new InvalidArgumentException('Empty interval is not accepted.');
499 $this->dateInterval = $interval;
501 $this->isDefaultInterval = false;
503 $this->handleChangedParameters();
505 return $this;
509 * Invert the period date interval.
511 * @return $this
513 public function invertDateInterval()
515 $interval = $this->dateInterval->invert();
517 return $this->setDateInterval($interval);
521 * Set start and end date.
523 * @param DateTime|DateTimeInterface|string $start
524 * @param DateTime|DateTimeInterface|string|null $end
526 * @return $this
528 public function setDates($start, $end)
530 $this->setStartDate($start);
531 $this->setEndDate($end);
533 return $this;
537 * Change the period options.
539 * @param int|null $options
541 * @throws \InvalidArgumentException
543 * @return $this
545 public function setOptions($options)
547 if (!is_int($options) && !is_null($options)) {
548 throw new InvalidArgumentException('Invalid options.');
551 $this->options = $options ?: 0;
553 $this->handleChangedParameters();
555 return $this;
559 * Get the period options.
561 * @return int
563 public function getOptions()
565 return $this->options;
569 * Toggle given options on or off.
571 * @param int $options
572 * @param bool|null $state
574 * @throws \InvalidArgumentException
576 * @return $this
578 public function toggleOptions($options, $state = null)
580 if ($state === null) {
581 $state = ($this->options & $options) !== $options;
584 return $this->setOptions($state ?
585 $this->options | $options :
586 $this->options & ~$options
591 * Toggle EXCLUDE_START_DATE option.
593 * @param bool $state
595 * @return $this
597 public function excludeStartDate($state = true)
599 return $this->toggleOptions(static::EXCLUDE_START_DATE, $state);
603 * Toggle EXCLUDE_END_DATE option.
605 * @param bool $state
607 * @return $this
609 public function excludeEndDate($state = true)
611 return $this->toggleOptions(static::EXCLUDE_END_DATE, $state);
615 * Get the underlying date interval.
617 * @return CarbonInterval
619 public function getDateInterval()
621 return $this->dateInterval->copy();
625 * Get start date of the period.
627 * @return Carbon
629 public function getStartDate()
631 return $this->startDate->copy();
635 * Get end date of the period.
637 * @return Carbon|null
639 public function getEndDate()
641 if ($this->endDate) {
642 return $this->endDate->copy();
647 * Get number of recurrences.
649 * @return int|null
651 public function getRecurrences()
653 return $this->recurrences;
657 * Returns true if the start date should be excluded.
659 * @return bool
661 public function isStartExcluded()
663 return ($this->options & static::EXCLUDE_START_DATE) !== 0;
667 * Returns true if the end date should be excluded.
669 * @return bool
671 public function isEndExcluded()
673 return ($this->options & static::EXCLUDE_END_DATE) !== 0;
677 * Add a filter to the stack.
679 * @param callable $callback
680 * @param string $name
682 * @return $this
684 public function addFilter($callback, $name = null)
686 $tuple = $this->createFilterTuple(func_get_args());
688 $this->filters[] = $tuple;
690 $this->handleChangedParameters();
692 return $this;
696 * Prepend a filter to the stack.
698 * @param callable $callback
699 * @param string $name
701 * @return $this
703 public function prependFilter($callback, $name = null)
705 $tuple = $this->createFilterTuple(func_get_args());
707 array_unshift($this->filters, $tuple);
709 $this->handleChangedParameters();
711 return $this;
715 * Create a filter tuple from raw parameters.
717 * Will create an automatic filter callback for one of Carbon's is* methods.
719 * @param array $parameters
721 * @return array
723 protected function createFilterTuple(array $parameters)
725 $method = array_shift($parameters);
727 if (!$this->isCarbonPredicateMethod($method)) {
728 return array($method, array_shift($parameters));
731 return array(function ($date) use ($method, $parameters) {
732 return call_user_func_array(array($date, $method), $parameters);
733 }, $method);
737 * Remove a filter by instance or name.
739 * @param callable|string $filter
741 * @return $this
743 public function removeFilter($filter)
745 $key = is_callable($filter) ? 0 : 1;
747 $this->filters = array_values(array_filter(
748 $this->filters,
749 function ($tuple) use ($key, $filter) {
750 return $tuple[$key] !== $filter;
754 $this->updateInternalState();
756 $this->handleChangedParameters();
758 return $this;
762 * Return whether given instance or name is in the filter stack.
764 * @param callable|string $filter
766 * @return bool
768 public function hasFilter($filter)
770 $key = is_callable($filter) ? 0 : 1;
772 foreach ($this->filters as $tuple) {
773 if ($tuple[$key] === $filter) {
774 return true;
778 return false;
782 * Get filters stack.
784 * @return array
786 public function getFilters()
788 return $this->filters;
792 * Set filters stack.
794 * @param array $filters
796 * @return $this
798 public function setFilters(array $filters)
800 $this->filters = $filters;
802 $this->updateInternalState();
804 $this->handleChangedParameters();
806 return $this;
810 * Reset filters stack.
812 * @return $this
814 public function resetFilters()
816 $this->filters = array();
818 if ($this->endDate !== null) {
819 $this->filters[] = array(static::END_DATE_FILTER, null);
822 if ($this->recurrences !== null) {
823 $this->filters[] = array(static::RECURRENCES_FILTER, null);
826 $this->handleChangedParameters();
828 return $this;
832 * Update properties after removing built-in filters.
834 * @return void
836 protected function updateInternalState()
838 if (!$this->hasFilter(static::END_DATE_FILTER)) {
839 $this->endDate = null;
842 if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
843 $this->recurrences = null;
848 * Add a recurrences filter (set maximum number of recurrences).
850 * @param int|null $recurrences
852 * @throws \InvalidArgumentException
854 * @return $this
856 public function setRecurrences($recurrences)
858 if (!is_numeric($recurrences) && !is_null($recurrences) || $recurrences < 0) {
859 throw new InvalidArgumentException('Invalid number of recurrences.');
862 if ($recurrences === null) {
863 return $this->removeFilter(static::RECURRENCES_FILTER);
866 $this->recurrences = (int) $recurrences;
868 if (!$this->hasFilter(static::RECURRENCES_FILTER)) {
869 return $this->addFilter(static::RECURRENCES_FILTER);
872 $this->handleChangedParameters();
874 return $this;
878 * Recurrences filter callback (limits number of recurrences).
880 * @param \Carbon\Carbon $current
881 * @param int $key
883 * @return bool|string
885 protected function filterRecurrences($current, $key)
887 if ($key < $this->recurrences) {
888 return true;
891 return static::END_ITERATION;
895 * Change the period start date.
897 * @param DateTime|DateTimeInterface|string $date
898 * @param bool|null $inclusive
900 * @throws \InvalidArgumentException
902 * @return $this
904 public function setStartDate($date, $inclusive = null)
906 if (!$date = Carbon::make($date)) {
907 throw new InvalidArgumentException('Invalid start date.');
910 $this->startDate = $date;
912 if ($inclusive !== null) {
913 $this->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
916 return $this;
920 * Change the period end date.
922 * @param DateTime|DateTimeInterface|string|null $date
923 * @param bool|null $inclusive
925 * @throws \InvalidArgumentException
927 * @return $this
929 public function setEndDate($date, $inclusive = null)
931 if (!is_null($date) && !$date = Carbon::make($date)) {
932 throw new InvalidArgumentException('Invalid end date.');
935 if (!$date) {
936 return $this->removeFilter(static::END_DATE_FILTER);
939 $this->endDate = $date;
941 if ($inclusive !== null) {
942 $this->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
945 if (!$this->hasFilter(static::END_DATE_FILTER)) {
946 return $this->addFilter(static::END_DATE_FILTER);
949 $this->handleChangedParameters();
951 return $this;
955 * End date filter callback.
957 * @param \Carbon\Carbon $current
959 * @return bool|string
961 protected function filterEndDate($current)
963 if (!$this->isEndExcluded() && $current == $this->endDate) {
964 return true;
967 if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
968 return true;
971 return static::END_ITERATION;
975 * End iteration filter callback.
977 * @return string
979 protected function endIteration()
981 return static::END_ITERATION;
985 * Handle change of the parameters.
987 protected function handleChangedParameters()
989 $this->validationResult = null;
993 * Validate current date and stop iteration when necessary.
995 * Returns true when current date is valid, false if it is not, or static::END_ITERATION
996 * when iteration should be stopped.
998 * @return bool|static::END_ITERATION
1000 protected function validateCurrentDate()
1002 if ($this->current === null) {
1003 $this->rewind();
1006 // Check after the first rewind to avoid repeating the initial validation.
1007 if ($this->validationResult !== null) {
1008 return $this->validationResult;
1011 return $this->validationResult = $this->checkFilters();
1015 * Check whether current value and key pass all the filters.
1017 * @return bool|string
1019 protected function checkFilters()
1021 $current = $this->prepareForReturn($this->current);
1023 foreach ($this->filters as $tuple) {
1024 $result = call_user_func(
1025 $tuple[0], $current->copy(), $this->key, $this
1028 if ($result === static::END_ITERATION) {
1029 return static::END_ITERATION;
1032 if (!$result) {
1033 return false;
1037 return true;
1041 * Prepare given date to be returned to the external logic.
1043 * @param Carbon $date
1045 * @return Carbon
1047 protected function prepareForReturn(Carbon $date)
1049 $date = $date->copy();
1051 if ($this->timezone) {
1052 $date->setTimezone($this->timezone);
1055 return $date;
1059 * Check if the current position is valid.
1061 * @return bool
1063 public function valid()
1065 return $this->validateCurrentDate() === true;
1069 * Return the current key.
1071 * @return int|null
1073 public function key()
1075 if ($this->valid()) {
1076 return $this->key;
1081 * Return the current date.
1083 * @return Carbon|null
1085 public function current()
1087 if ($this->valid()) {
1088 return $this->prepareForReturn($this->current);
1093 * Move forward to the next date.
1095 * @throws \RuntimeException
1097 * @return void
1099 public function next()
1101 if ($this->current === null) {
1102 $this->rewind();
1105 if ($this->validationResult !== static::END_ITERATION) {
1106 $this->key++;
1108 $this->incrementCurrentDateUntilValid();
1113 * Rewind to the start date.
1115 * Iterating over a date in the UTC timezone avoids bug during backward DST change.
1117 * @see https://bugs.php.net/bug.php?id=72255
1118 * @see https://bugs.php.net/bug.php?id=74274
1119 * @see https://wiki.php.net/rfc/datetime_and_daylight_saving_time
1121 * @throws \RuntimeException
1123 * @return void
1125 public function rewind()
1127 $this->key = 0;
1128 $this->current = $this->startDate->copy();
1129 $this->timezone = static::intervalHasTime($this->dateInterval) ? $this->current->getTimezone() : null;
1131 if ($this->timezone) {
1132 $this->current->setTimezone('UTC');
1135 $this->validationResult = null;
1137 if ($this->isStartExcluded() || $this->validateCurrentDate() === false) {
1138 $this->incrementCurrentDateUntilValid();
1143 * Skip iterations and returns iteration state (false if ended, true if still valid).
1145 * @param int $count steps number to skip (1 by default)
1147 * @return bool
1149 public function skip($count = 1)
1151 for ($i = $count; $this->valid() && $i > 0; $i--) {
1152 $this->next();
1155 return $this->valid();
1159 * Keep incrementing the current date until a valid date is found or the iteration is ended.
1161 * @throws \RuntimeException
1163 * @return void
1165 protected function incrementCurrentDateUntilValid()
1167 $attempts = 0;
1169 do {
1170 $this->current->add($this->dateInterval);
1172 $this->validationResult = null;
1174 if (++$attempts > static::NEXT_MAX_ATTEMPTS) {
1175 throw new RuntimeException('Could not find next valid date.');
1177 } while ($this->validateCurrentDate() === false);
1181 * Format the date period as ISO 8601.
1183 * @return string
1185 public function toIso8601String()
1187 $parts = array();
1189 if ($this->recurrences !== null) {
1190 $parts[] = 'R'.$this->recurrences;
1193 $parts[] = $this->startDate->toIso8601String();
1195 $parts[] = $this->dateInterval->spec();
1197 if ($this->endDate !== null) {
1198 $parts[] = $this->endDate->toIso8601String();
1201 return implode('/', $parts);
1205 * Convert the date period into a string.
1207 * @return string
1209 public function toString()
1211 $translator = Carbon::getTranslator();
1213 $parts = array();
1215 $format = !$this->startDate->isStartOfDay() || $this->endDate && !$this->endDate->isStartOfDay()
1216 ? 'Y-m-d H:i:s'
1217 : 'Y-m-d';
1219 if ($this->recurrences !== null) {
1220 $parts[] = $translator->transChoice('period_recurrences', $this->recurrences, array(':count' => $this->recurrences));
1223 $parts[] = $translator->trans('period_interval', array(':interval' => $this->dateInterval->forHumans()));
1225 $parts[] = $translator->trans('period_start_date', array(':date' => $this->startDate->format($format)));
1227 if ($this->endDate !== null) {
1228 $parts[] = $translator->trans('period_end_date', array(':date' => $this->endDate->format($format)));
1231 $result = implode(' ', $parts);
1233 return mb_strtoupper(mb_substr($result, 0, 1)).mb_substr($result, 1);
1237 * Format the date period as ISO 8601.
1239 * @return string
1241 public function spec()
1243 return $this->toIso8601String();
1247 * Convert the date period into an array without changing current iteration state.
1249 * @return array
1251 public function toArray()
1253 $state = array(
1254 $this->key,
1255 $this->current ? $this->current->copy() : null,
1256 $this->validationResult,
1259 $result = iterator_to_array($this);
1261 list(
1262 $this->key,
1263 $this->current,
1264 $this->validationResult
1265 ) = $state;
1267 return $result;
1271 * Count dates in the date period.
1273 * @return int
1275 public function count()
1277 return count($this->toArray());
1281 * Return the first date in the date period.
1283 * @return Carbon|null
1285 public function first()
1287 if ($array = $this->toArray()) {
1288 return $array[0];
1293 * Return the last date in the date period.
1295 * @return Carbon|null
1297 public function last()
1299 if ($array = $this->toArray()) {
1300 return $array[count($array) - 1];
1305 * Call given macro.
1307 * @param string $name
1308 * @param array $parameters
1310 * @return mixed
1312 protected function callMacro($name, $parameters)
1314 $macro = static::$macros[$name];
1316 $reflection = new ReflectionFunction($macro);
1318 $reflectionParameters = $reflection->getParameters();
1320 $expectedCount = count($reflectionParameters);
1321 $actualCount = count($parameters);
1323 if ($expectedCount > $actualCount && $reflectionParameters[$expectedCount - 1]->name === 'self') {
1324 for ($i = $actualCount; $i < $expectedCount - 1; $i++) {
1325 $parameters[] = $reflectionParameters[$i]->getDefaultValue();
1328 $parameters[] = $this;
1331 if ($macro instanceof Closure && method_exists($macro, 'bindTo')) {
1332 $macro = $macro->bindTo($this, get_class($this));
1335 return call_user_func_array($macro, $parameters);
1339 * Convert the date period into a string.
1341 * @return string
1343 public function __toString()
1345 return $this->toString();
1349 * Add aliases for setters.
1351 * CarbonPeriod::days(3)->hours(5)->invert()
1352 * ->sinceNow()->until('2010-01-10')
1353 * ->filter(...)
1354 * ->count()
1356 * Note: We use magic method to let static and instance aliases with the same names.
1358 * @param string $method
1359 * @param array $parameters
1361 * @return mixed
1363 public function __call($method, $parameters)
1365 if (static::hasMacro($method)) {
1366 return $this->callMacro($method, $parameters);
1369 $first = count($parameters) >= 1 ? $parameters[0] : null;
1370 $second = count($parameters) >= 2 ? $parameters[1] : null;
1372 switch ($method) {
1373 case 'start':
1374 case 'since':
1375 return $this->setStartDate($first, $second);
1377 case 'sinceNow':
1378 return $this->setStartDate(new Carbon, $first);
1380 case 'end':
1381 case 'until':
1382 return $this->setEndDate($first, $second);
1384 case 'untilNow':
1385 return $this->setEndDate(new Carbon, $first);
1387 case 'dates':
1388 case 'between':
1389 return $this->setDates($first, $second);
1391 case 'recurrences':
1392 case 'times':
1393 return $this->setRecurrences($first);
1395 case 'options':
1396 return $this->setOptions($first);
1398 case 'toggle':
1399 return $this->toggleOptions($first, $second);
1401 case 'filter':
1402 case 'push':
1403 return $this->addFilter($first, $second);
1405 case 'prepend':
1406 return $this->prependFilter($first, $second);
1408 case 'filters':
1409 return $this->setFilters($first ?: array());
1411 case 'interval':
1412 case 'each':
1413 case 'every':
1414 case 'step':
1415 case 'stepBy':
1416 return $this->setDateInterval($first);
1418 case 'invert':
1419 return $this->invertDateInterval();
1421 case 'years':
1422 case 'year':
1423 case 'months':
1424 case 'month':
1425 case 'weeks':
1426 case 'week':
1427 case 'days':
1428 case 'dayz':
1429 case 'day':
1430 case 'hours':
1431 case 'hour':
1432 case 'minutes':
1433 case 'minute':
1434 case 'seconds':
1435 case 'second':
1436 return $this->setDateInterval(call_user_func(
1437 // Override default P1D when instantiating via fluent setters.
1438 array($this->isDefaultInterval ? new CarbonInterval('PT0S') : $this->dateInterval, $method),
1439 count($parameters) === 0 ? 1 : $first
1443 throw new BadMethodCallException("Method $method does not exist.");