CarbonPeriod.php 77 KB

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