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.
14 use BadMethodCallException
;
19 use DateTimeInterface
;
20 use InvalidArgumentException
;
23 use ReflectionFunction
;
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
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.
116 const END_ITERATION
= 'Carbon\CarbonPeriod::endIteration';
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.
131 const NEXT_MAX_ATTEMPTS
= 1000;
134 * The registered macros.
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.
152 protected $isDefaultInterval;
159 protected $filters = array();
162 * Period start date. Applied on rewind. Always present, now by default.
166 protected $startDate;
169 * Period end date. For inverted interval should be before the start date. Applied via a filter.
176 * Limit for number of recurrences. Applied via a filter.
180 protected $recurrences;
190 * Index of current date. Always sequential, even if some dates are skipped by filters.
191 * Equal to null only before the first iteration.
198 * Current date. May temporarily hold unaccepted value when looking for a next valid date.
199 * Equal to null only before the first iteration.
206 * Timezone of current date. Taken from the start date.
208 * @var \DateTimeZone|null
213 * The cached validation result for current date.
215 * @var bool|string|null
217 protected $validationResult;
220 * Create a new instance.
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
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);
247 * Create CarbonPeriod from ISO 8601 string.
250 * @param int|null $options
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);
268 * Return whether given interval contains non zero value of any time unit.
270 * @param \DateInterval $interval
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
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.
305 protected static function isIso8601($var)
307 if (!is_string($var)) {
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.
326 protected static function parseIso8601($iso)
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)) {
339 } elseif ($start === null && $parsed = Carbon
::make($part)) {
341 } elseif ($end === null && $parsed = Carbon
::make(static::addMissingParts($start, $part))) {
344 throw new InvalidArgumentException("Invalid ISO 8601 specification: $iso.");
354 * Add missing parts of the target date from the soure date.
356 * @param string $source
357 * @param string $target
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
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
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
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
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);
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
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();
509 * Invert the period date interval.
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
528 public function setDates($start, $end)
530 $this->setStartDate($start);
531 $this->setEndDate($end);
537 * Change the period options.
539 * @param int|null $options
541 * @throws \InvalidArgumentException
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();
559 * Get the period options.
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
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.
597 public function excludeStartDate($state = true)
599 return $this->toggleOptions(static::EXCLUDE_START_DATE
, $state);
603 * Toggle EXCLUDE_END_DATE option.
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.
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.
651 public function getRecurrences()
653 return $this->recurrences
;
657 * Returns true if the start date should be excluded.
661 public function isStartExcluded()
663 return ($this->options
& static::EXCLUDE_START_DATE
) !== 0;
667 * Returns true if the end date should be excluded.
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
684 public function addFilter($callback, $name = null)
686 $tuple = $this->createFilterTuple(func_get_args());
688 $this->filters
[] = $tuple;
690 $this->handleChangedParameters();
696 * Prepend a filter to the stack.
698 * @param callable $callback
699 * @param string $name
703 public function prependFilter($callback, $name = null)
705 $tuple = $this->createFilterTuple(func_get_args());
707 array_unshift($this->filters
, $tuple);
709 $this->handleChangedParameters();
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
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);
737 * Remove a filter by instance or name.
739 * @param callable|string $filter
743 public function removeFilter($filter)
745 $key = is_callable($filter) ?
0 : 1;
747 $this->filters
= array_values(array_filter(
749 function ($tuple) use ($key, $filter) {
750 return $tuple[$key] !== $filter;
754 $this->updateInternalState();
756 $this->handleChangedParameters();
762 * Return whether given instance or name is in the filter stack.
764 * @param callable|string $filter
768 public function hasFilter($filter)
770 $key = is_callable($filter) ?
0 : 1;
772 foreach ($this->filters
as $tuple) {
773 if ($tuple[$key] === $filter) {
786 public function getFilters()
788 return $this->filters
;
794 * @param array $filters
798 public function setFilters(array $filters)
800 $this->filters
= $filters;
802 $this->updateInternalState();
804 $this->handleChangedParameters();
810 * Reset filters stack.
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();
832 * Update properties after removing built-in filters.
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
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();
878 * Recurrences filter callback (limits number of recurrences).
880 * @param \Carbon\Carbon $current
883 * @return bool|string
885 protected function filterRecurrences($current, $key)
887 if ($key < $this->recurrences
) {
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
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);
920 * Change the period end date.
922 * @param DateTime|DateTimeInterface|string|null $date
923 * @param bool|null $inclusive
925 * @throws \InvalidArgumentException
929 public function setEndDate($date, $inclusive = null)
931 if (!is_null($date) && !$date = Carbon
::make($date)) {
932 throw new InvalidArgumentException('Invalid end 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();
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
) {
967 if ($this->dateInterval
->invert ?
$current > $this->endDate
: $current < $this->endDate
) {
971 return static::END_ITERATION
;
975 * End iteration filter callback.
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) {
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
;
1041 * Prepare given date to be returned to the external logic.
1043 * @param Carbon $date
1047 protected function prepareForReturn(Carbon
$date)
1049 $date = $date->copy();
1051 if ($this->timezone
) {
1052 $date->setTimezone($this->timezone
);
1059 * Check if the current position is valid.
1063 public function valid()
1065 return $this->validateCurrentDate() === true;
1069 * Return the current key.
1073 public function key()
1075 if ($this->valid()) {
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
1099 public function next()
1101 if ($this->current
=== null) {
1105 if ($this->validationResult
!== static::END_ITERATION
) {
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
1125 public function rewind()
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)
1149 public function skip($count = 1)
1151 for ($i = $count; $this->valid() && $i > 0; $i--) {
1155 return $this->valid();
1159 * Keep incrementing the current date until a valid date is found or the iteration is ended.
1161 * @throws \RuntimeException
1165 protected function incrementCurrentDateUntilValid()
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.
1185 public function toIso8601String()
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.
1209 public function toString()
1211 $translator = Carbon
::getTranslator();
1215 $format = !$this->startDate
->isStartOfDay() ||
$this->endDate
&& !$this->endDate
->isStartOfDay()
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.
1241 public function spec()
1243 return $this->toIso8601String();
1247 * Convert the date period into an array without changing current iteration state.
1251 public function toArray()
1255 $this->current ?
$this->current
->copy() : null,
1256 $this->validationResult
,
1259 $result = iterator_to_array($this);
1264 $this->validationResult
1271 * Count dates in the date period.
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()) {
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];
1307 * @param string $name
1308 * @param array $parameters
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.
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')
1356 * Note: We use magic method to let static and instance aliases with the same names.
1358 * @param string $method
1359 * @param array $parameters
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;
1375 return $this->setStartDate($first, $second);
1378 return $this->setStartDate(new Carbon
, $first);
1382 return $this->setEndDate($first, $second);
1385 return $this->setEndDate(new Carbon
, $first);
1389 return $this->setDates($first, $second);
1393 return $this->setRecurrences($first);
1396 return $this->setOptions($first);
1399 return $this->toggleOptions($first, $second);
1403 return $this->addFilter($first, $second);
1406 return $this->prependFilter($first, $second);
1409 return $this->setFilters($first ?
: array());
1416 return $this->setDateInterval($first);
1419 return $this->invertDateInterval();
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.");