blob: 7168fa4b43345f36d43152248d2efda755fb1826 [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3/**
4 * This file is part of the Carbon package.
5 *
6 * (c) Brian Nesbitt <brian@nesbot.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11namespace Carbon;
12
13use Carbon\Exceptions\BadFluentConstructorException;
14use Carbon\Exceptions\BadFluentSetterException;
15use Carbon\Exceptions\InvalidCastException;
16use Carbon\Exceptions\InvalidIntervalException;
17use Carbon\Exceptions\ParseErrorException;
18use Carbon\Exceptions\UnitNotConfiguredException;
19use Carbon\Exceptions\UnknownGetterException;
20use Carbon\Exceptions\UnknownSetterException;
21use Carbon\Exceptions\UnknownUnitException;
22use Carbon\Traits\IntervalRounding;
23use Carbon\Traits\IntervalStep;
24use Carbon\Traits\Mixin;
25use Carbon\Traits\Options;
26use Closure;
27use DateInterval;
28use Exception;
29use ReflectionException;
30use ReturnTypeWillChange;
31use Throwable;
32
33/**
34 * A simple API extension for DateInterval.
35 * The implementation provides helpers to handle weeks but only days are saved.
36 * Weeks are calculated based on the total days of the current instance.
37 *
38 * @property int $years Total years of the current interval.
39 * @property int $months Total months of the current interval.
40 * @property int $weeks Total weeks of the current interval calculated from the days.
41 * @property int $dayz Total days of the current interval (weeks * 7 + days).
42 * @property int $hours Total hours of the current interval.
43 * @property int $minutes Total minutes of the current interval.
44 * @property int $seconds Total seconds of the current interval.
45 * @property int $microseconds Total microseconds of the current interval.
46 * @property int $milliseconds Total microseconds of the current interval.
47 * @property int $microExcludeMilli Remaining microseconds without the milliseconds.
48 * @property int $dayzExcludeWeeks Total days remaining in the final week of the current instance (days % 7).
49 * @property int $daysExcludeWeeks alias of dayzExcludeWeeks
50 * @property-read float $totalYears Number of years equivalent to the interval.
51 * @property-read float $totalMonths Number of months equivalent to the interval.
52 * @property-read float $totalWeeks Number of weeks equivalent to the interval.
53 * @property-read float $totalDays Number of days equivalent to the interval.
54 * @property-read float $totalDayz Alias for totalDays.
55 * @property-read float $totalHours Number of hours equivalent to the interval.
56 * @property-read float $totalMinutes Number of minutes equivalent to the interval.
57 * @property-read float $totalSeconds Number of seconds equivalent to the interval.
58 * @property-read float $totalMilliseconds Number of milliseconds equivalent to the interval.
59 * @property-read float $totalMicroseconds Number of microseconds equivalent to the interval.
60 * @property-read string $locale locale of the current instance
61 *
62 * @method static CarbonInterval years($years = 1) Create instance specifying a number of years or modify the number of years if called on an instance.
63 * @method static CarbonInterval year($years = 1) Alias for years()
64 * @method static CarbonInterval months($months = 1) Create instance specifying a number of months or modify the number of months if called on an instance.
65 * @method static CarbonInterval month($months = 1) Alias for months()
66 * @method static CarbonInterval weeks($weeks = 1) Create instance specifying a number of weeks or modify the number of weeks if called on an instance.
67 * @method static CarbonInterval week($weeks = 1) Alias for weeks()
68 * @method static CarbonInterval days($days = 1) Create instance specifying a number of days or modify the number of days if called on an instance.
69 * @method static CarbonInterval dayz($days = 1) Alias for days()
70 * @method static CarbonInterval daysExcludeWeeks($days = 1) Create instance specifying a number of days or modify the number of days (keeping the current number of weeks) if called on an instance.
71 * @method static CarbonInterval dayzExcludeWeeks($days = 1) Alias for daysExcludeWeeks()
72 * @method static CarbonInterval day($days = 1) Alias for days()
73 * @method static CarbonInterval hours($hours = 1) Create instance specifying a number of hours or modify the number of hours if called on an instance.
74 * @method static CarbonInterval hour($hours = 1) Alias for hours()
75 * @method static CarbonInterval minutes($minutes = 1) Create instance specifying a number of minutes or modify the number of minutes if called on an instance.
76 * @method static CarbonInterval minute($minutes = 1) Alias for minutes()
77 * @method static CarbonInterval seconds($seconds = 1) Create instance specifying a number of seconds or modify the number of seconds if called on an instance.
78 * @method static CarbonInterval second($seconds = 1) Alias for seconds()
79 * @method static CarbonInterval milliseconds($milliseconds = 1) Create instance specifying a number of milliseconds or modify the number of milliseconds if called on an instance.
80 * @method static CarbonInterval millisecond($milliseconds = 1) Alias for milliseconds()
81 * @method static CarbonInterval microseconds($microseconds = 1) Create instance specifying a number of microseconds or modify the number of microseconds if called on an instance.
82 * @method static CarbonInterval microsecond($microseconds = 1) Alias for microseconds()
83 * @method $this addYears(int $years) Add given number of years to the current interval
84 * @method $this subYears(int $years) Subtract given number of years to the current interval
85 * @method $this addMonths(int $months) Add given number of months to the current interval
86 * @method $this subMonths(int $months) Subtract given number of months to the current interval
87 * @method $this addWeeks(int|float $weeks) Add given number of weeks to the current interval
88 * @method $this subWeeks(int|float $weeks) Subtract given number of weeks to the current interval
89 * @method $this addDays(int|float $days) Add given number of days to the current interval
90 * @method $this subDays(int|float $days) Subtract given number of days to the current interval
91 * @method $this addHours(int|float $hours) Add given number of hours to the current interval
92 * @method $this subHours(int|float $hours) Subtract given number of hours to the current interval
93 * @method $this addMinutes(int|float $minutes) Add given number of minutes to the current interval
94 * @method $this subMinutes(int|float $minutes) Subtract given number of minutes to the current interval
95 * @method $this addSeconds(int|float $seconds) Add given number of seconds to the current interval
96 * @method $this subSeconds(int|float $seconds) Subtract given number of seconds to the current interval
97 * @method $this addMilliseconds(int|float $milliseconds) Add given number of milliseconds to the current interval
98 * @method $this subMilliseconds(int|float $milliseconds) Subtract given number of milliseconds to the current interval
99 * @method $this addMicroseconds(int|float $microseconds) Add given number of microseconds to the current interval
100 * @method $this subMicroseconds(int|float $microseconds) Subtract given number of microseconds to the current interval
101 * @method $this roundYear(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
102 * @method $this roundYears(int|float $precision = 1, string $function = "round") Round the current instance year with given precision using the given function.
103 * @method $this floorYear(int|float $precision = 1) Truncate the current instance year with given precision.
104 * @method $this floorYears(int|float $precision = 1) Truncate the current instance year with given precision.
105 * @method $this ceilYear(int|float $precision = 1) Ceil the current instance year with given precision.
106 * @method $this ceilYears(int|float $precision = 1) Ceil the current instance year with given precision.
107 * @method $this roundMonth(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
108 * @method $this roundMonths(int|float $precision = 1, string $function = "round") Round the current instance month with given precision using the given function.
109 * @method $this floorMonth(int|float $precision = 1) Truncate the current instance month with given precision.
110 * @method $this floorMonths(int|float $precision = 1) Truncate the current instance month with given precision.
111 * @method $this ceilMonth(int|float $precision = 1) Ceil the current instance month with given precision.
112 * @method $this ceilMonths(int|float $precision = 1) Ceil the current instance month with given precision.
113 * @method $this roundWeek(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
114 * @method $this roundWeeks(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
115 * @method $this floorWeek(int|float $precision = 1) Truncate the current instance day with given precision.
116 * @method $this floorWeeks(int|float $precision = 1) Truncate the current instance day with given precision.
117 * @method $this ceilWeek(int|float $precision = 1) Ceil the current instance day with given precision.
118 * @method $this ceilWeeks(int|float $precision = 1) Ceil the current instance day with given precision.
119 * @method $this roundDay(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
120 * @method $this roundDays(int|float $precision = 1, string $function = "round") Round the current instance day with given precision using the given function.
121 * @method $this floorDay(int|float $precision = 1) Truncate the current instance day with given precision.
122 * @method $this floorDays(int|float $precision = 1) Truncate the current instance day with given precision.
123 * @method $this ceilDay(int|float $precision = 1) Ceil the current instance day with given precision.
124 * @method $this ceilDays(int|float $precision = 1) Ceil the current instance day with given precision.
125 * @method $this roundHour(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
126 * @method $this roundHours(int|float $precision = 1, string $function = "round") Round the current instance hour with given precision using the given function.
127 * @method $this floorHour(int|float $precision = 1) Truncate the current instance hour with given precision.
128 * @method $this floorHours(int|float $precision = 1) Truncate the current instance hour with given precision.
129 * @method $this ceilHour(int|float $precision = 1) Ceil the current instance hour with given precision.
130 * @method $this ceilHours(int|float $precision = 1) Ceil the current instance hour with given precision.
131 * @method $this roundMinute(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
132 * @method $this roundMinutes(int|float $precision = 1, string $function = "round") Round the current instance minute with given precision using the given function.
133 * @method $this floorMinute(int|float $precision = 1) Truncate the current instance minute with given precision.
134 * @method $this floorMinutes(int|float $precision = 1) Truncate the current instance minute with given precision.
135 * @method $this ceilMinute(int|float $precision = 1) Ceil the current instance minute with given precision.
136 * @method $this ceilMinutes(int|float $precision = 1) Ceil the current instance minute with given precision.
137 * @method $this roundSecond(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
138 * @method $this roundSeconds(int|float $precision = 1, string $function = "round") Round the current instance second with given precision using the given function.
139 * @method $this floorSecond(int|float $precision = 1) Truncate the current instance second with given precision.
140 * @method $this floorSeconds(int|float $precision = 1) Truncate the current instance second with given precision.
141 * @method $this ceilSecond(int|float $precision = 1) Ceil the current instance second with given precision.
142 * @method $this ceilSeconds(int|float $precision = 1) Ceil the current instance second with given precision.
143 * @method $this roundMillennium(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
144 * @method $this roundMillennia(int|float $precision = 1, string $function = "round") Round the current instance millennium with given precision using the given function.
145 * @method $this floorMillennium(int|float $precision = 1) Truncate the current instance millennium with given precision.
146 * @method $this floorMillennia(int|float $precision = 1) Truncate the current instance millennium with given precision.
147 * @method $this ceilMillennium(int|float $precision = 1) Ceil the current instance millennium with given precision.
148 * @method $this ceilMillennia(int|float $precision = 1) Ceil the current instance millennium with given precision.
149 * @method $this roundCentury(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
150 * @method $this roundCenturies(int|float $precision = 1, string $function = "round") Round the current instance century with given precision using the given function.
151 * @method $this floorCentury(int|float $precision = 1) Truncate the current instance century with given precision.
152 * @method $this floorCenturies(int|float $precision = 1) Truncate the current instance century with given precision.
153 * @method $this ceilCentury(int|float $precision = 1) Ceil the current instance century with given precision.
154 * @method $this ceilCenturies(int|float $precision = 1) Ceil the current instance century with given precision.
155 * @method $this roundDecade(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
156 * @method $this roundDecades(int|float $precision = 1, string $function = "round") Round the current instance decade with given precision using the given function.
157 * @method $this floorDecade(int|float $precision = 1) Truncate the current instance decade with given precision.
158 * @method $this floorDecades(int|float $precision = 1) Truncate the current instance decade with given precision.
159 * @method $this ceilDecade(int|float $precision = 1) Ceil the current instance decade with given precision.
160 * @method $this ceilDecades(int|float $precision = 1) Ceil the current instance decade with given precision.
161 * @method $this roundQuarter(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
162 * @method $this roundQuarters(int|float $precision = 1, string $function = "round") Round the current instance quarter with given precision using the given function.
163 * @method $this floorQuarter(int|float $precision = 1) Truncate the current instance quarter with given precision.
164 * @method $this floorQuarters(int|float $precision = 1) Truncate the current instance quarter with given precision.
165 * @method $this ceilQuarter(int|float $precision = 1) Ceil the current instance quarter with given precision.
166 * @method $this ceilQuarters(int|float $precision = 1) Ceil the current instance quarter with given precision.
167 * @method $this roundMillisecond(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
168 * @method $this roundMilliseconds(int|float $precision = 1, string $function = "round") Round the current instance millisecond with given precision using the given function.
169 * @method $this floorMillisecond(int|float $precision = 1) Truncate the current instance millisecond with given precision.
170 * @method $this floorMilliseconds(int|float $precision = 1) Truncate the current instance millisecond with given precision.
171 * @method $this ceilMillisecond(int|float $precision = 1) Ceil the current instance millisecond with given precision.
172 * @method $this ceilMilliseconds(int|float $precision = 1) Ceil the current instance millisecond with given precision.
173 * @method $this roundMicrosecond(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
174 * @method $this roundMicroseconds(int|float $precision = 1, string $function = "round") Round the current instance microsecond with given precision using the given function.
175 * @method $this floorMicrosecond(int|float $precision = 1) Truncate the current instance microsecond with given precision.
176 * @method $this floorMicroseconds(int|float $precision = 1) Truncate the current instance microsecond with given precision.
177 * @method $this ceilMicrosecond(int|float $precision = 1) Ceil the current instance microsecond with given precision.
178 * @method $this ceilMicroseconds(int|float $precision = 1) Ceil the current instance microsecond with given precision.
179 */
180class CarbonInterval extends DateInterval implements CarbonConverterInterface
181{
182 use IntervalRounding;
183 use IntervalStep;
184 use Mixin {
185 Mixin::mixin as baseMixin;
186 }
187 use Options;
188
189 /**
190 * Interval spec period designators
191 */
192 const PERIOD_PREFIX = 'P';
193 const PERIOD_YEARS = 'Y';
194 const PERIOD_MONTHS = 'M';
195 const PERIOD_DAYS = 'D';
196 const PERIOD_TIME_PREFIX = 'T';
197 const PERIOD_HOURS = 'H';
198 const PERIOD_MINUTES = 'M';
199 const PERIOD_SECONDS = 'S';
200
201 /**
202 * A translator to ... er ... translate stuff
203 *
204 * @var \Symfony\Component\Translation\TranslatorInterface
205 */
206 protected static $translator;
207
208 /**
209 * @var array|null
210 */
211 protected static $cascadeFactors;
212
213 /**
214 * @var array
215 */
216 protected static $formats = [
217 'y' => 'y',
218 'Y' => 'y',
219 'o' => 'y',
220 'm' => 'm',
221 'n' => 'm',
222 'W' => 'weeks',
223 'd' => 'd',
224 'j' => 'd',
225 'z' => 'd',
226 'h' => 'h',
227 'g' => 'h',
228 'H' => 'h',
229 'G' => 'h',
230 'i' => 'i',
231 's' => 's',
232 'u' => 'micro',
233 'v' => 'milli',
234 ];
235
236 /**
237 * @var array|null
238 */
239 private static $flipCascadeFactors;
240
241 /**
242 * The registered macros.
243 *
244 * @var array
245 */
246 protected static $macros = [];
247
248 /**
249 * Timezone handler for settings() method.
250 *
251 * @var mixed
252 */
253 protected $tzName;
254
255 /**
256 * Set the instance's timezone from a string or object and add/subtract the offset difference.
257 *
258 * @param \DateTimeZone|string $tzName
259 *
260 * @return static
261 */
262 public function shiftTimezone($tzName)
263 {
264 $this->tzName = $tzName;
265
266 return $this;
267 }
268
269 /**
270 * Mapping of units and factors for cascading.
271 *
272 * Should only be modified by changing the factors or referenced constants.
273 *
274 * @return array
275 */
276 public static function getCascadeFactors()
277 {
278 return static::$cascadeFactors ?: [
279 'milliseconds' => [Carbon::MICROSECONDS_PER_MILLISECOND, 'microseconds'],
280 'seconds' => [Carbon::MILLISECONDS_PER_SECOND, 'milliseconds'],
281 'minutes' => [Carbon::SECONDS_PER_MINUTE, 'seconds'],
282 'hours' => [Carbon::MINUTES_PER_HOUR, 'minutes'],
283 'dayz' => [Carbon::HOURS_PER_DAY, 'hours'],
284 'weeks' => [Carbon::DAYS_PER_WEEK, 'dayz'],
285 'months' => [Carbon::WEEKS_PER_MONTH, 'weeks'],
286 'years' => [Carbon::MONTHS_PER_YEAR, 'months'],
287 ];
288 }
289
290 private static function standardizeUnit($unit)
291 {
292 $unit = rtrim($unit, 'sz').'s';
293
294 return $unit === 'days' ? 'dayz' : $unit;
295 }
296
297 private static function getFlipCascadeFactors()
298 {
299 if (!self::$flipCascadeFactors) {
300 self::$flipCascadeFactors = [];
301
302 foreach (static::getCascadeFactors() as $to => [$factor, $from]) {
303 self::$flipCascadeFactors[self::standardizeUnit($from)] = [self::standardizeUnit($to), $factor];
304 }
305 }
306
307 return self::$flipCascadeFactors;
308 }
309
310 /**
311 * Set default cascading factors for ->cascade() method.
312 *
313 * @param array $cascadeFactors
314 */
315 public static function setCascadeFactors(array $cascadeFactors)
316 {
317 self::$flipCascadeFactors = null;
318 static::$cascadeFactors = $cascadeFactors;
319 }
320
321 ///////////////////////////////////////////////////////////////////
322 //////////////////////////// CONSTRUCTORS /////////////////////////
323 ///////////////////////////////////////////////////////////////////
324
325 /**
326 * Create a new CarbonInterval instance.
327 *
328 * @param int|null $years
329 * @param int|null $months
330 * @param int|null $weeks
331 * @param int|null $days
332 * @param int|null $hours
333 * @param int|null $minutes
334 * @param int|null $seconds
335 * @param int|null $microseconds
336 *
337 * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval.
338 */
339 public function __construct($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null)
340 {
341 if ($years instanceof Closure) {
342 $this->step = $years;
343 $years = null;
344 }
345
346 if ($years instanceof DateInterval) {
347 parent::__construct(static::getDateIntervalSpec($years));
348 $this->f = $years->f;
349 static::copyNegativeUnits($years, $this);
350
351 return;
352 }
353
354 $spec = $years;
355
356 if (!\is_string($spec) || (float) $years || preg_match('/^[0-9.]/', $years)) {
357 $spec = static::PERIOD_PREFIX;
358
359 $spec .= $years > 0 ? $years.static::PERIOD_YEARS : '';
360 $spec .= $months > 0 ? $months.static::PERIOD_MONTHS : '';
361
362 $specDays = 0;
363 $specDays += $weeks > 0 ? $weeks * static::getDaysPerWeek() : 0;
364 $specDays += $days > 0 ? $days : 0;
365
366 $spec .= $specDays > 0 ? $specDays.static::PERIOD_DAYS : '';
367
368 if ($hours > 0 || $minutes > 0 || $seconds > 0) {
369 $spec .= static::PERIOD_TIME_PREFIX;
370 $spec .= $hours > 0 ? $hours.static::PERIOD_HOURS : '';
371 $spec .= $minutes > 0 ? $minutes.static::PERIOD_MINUTES : '';
372 $spec .= $seconds > 0 ? $seconds.static::PERIOD_SECONDS : '';
373 }
374
375 if ($spec === static::PERIOD_PREFIX) {
376 // Allow the zero interval.
377 $spec .= '0'.static::PERIOD_YEARS;
378 }
379 }
380
381 parent::__construct($spec);
382
383 if ($microseconds !== null) {
384 $this->f = $microseconds / Carbon::MICROSECONDS_PER_SECOND;
385 }
386 }
387
388 /**
389 * Returns the factor for a given source-to-target couple.
390 *
391 * @param string $source
392 * @param string $target
393 *
394 * @return int|null
395 */
396 public static function getFactor($source, $target)
397 {
398 $source = self::standardizeUnit($source);
399 $target = self::standardizeUnit($target);
400 $factors = static::getFlipCascadeFactors();
401
402 if (isset($factors[$source])) {
403 [$to, $factor] = $factors[$source];
404
405 if ($to === $target) {
406 return $factor;
407 }
408
409 return $factor * static::getFactor($to, $target);
410 }
411
412 return null;
413 }
414
415 /**
416 * Returns current config for days per week.
417 *
418 * @return int
419 */
420 public static function getDaysPerWeek()
421 {
422 return static::getFactor('dayz', 'weeks') ?: Carbon::DAYS_PER_WEEK;
423 }
424
425 /**
426 * Returns current config for hours per day.
427 *
428 * @return int
429 */
430 public static function getHoursPerDay()
431 {
432 return static::getFactor('hours', 'dayz') ?: Carbon::HOURS_PER_DAY;
433 }
434
435 /**
436 * Returns current config for minutes per hour.
437 *
438 * @return int
439 */
440 public static function getMinutesPerHour()
441 {
442 return static::getFactor('minutes', 'hours') ?: Carbon::MINUTES_PER_HOUR;
443 }
444
445 /**
446 * Returns current config for seconds per minute.
447 *
448 * @return int
449 */
450 public static function getSecondsPerMinute()
451 {
452 return static::getFactor('seconds', 'minutes') ?: Carbon::SECONDS_PER_MINUTE;
453 }
454
455 /**
456 * Returns current config for microseconds per second.
457 *
458 * @return int
459 */
460 public static function getMillisecondsPerSecond()
461 {
462 return static::getFactor('milliseconds', 'seconds') ?: Carbon::MILLISECONDS_PER_SECOND;
463 }
464
465 /**
466 * Returns current config for microseconds per second.
467 *
468 * @return int
469 */
470 public static function getMicrosecondsPerMillisecond()
471 {
472 return static::getFactor('microseconds', 'milliseconds') ?: Carbon::MICROSECONDS_PER_MILLISECOND;
473 }
474
475 /**
476 * Create a new CarbonInterval instance from specific values.
477 * This is an alias for the constructor that allows better fluent
478 * syntax as it allows you to do CarbonInterval::create(1)->fn() rather than
479 * (new CarbonInterval(1))->fn().
480 *
481 * @param int $years
482 * @param int $months
483 * @param int $weeks
484 * @param int $days
485 * @param int $hours
486 * @param int $minutes
487 * @param int $seconds
488 * @param int $microseconds
489 *
490 * @throws Exception when the interval_spec (passed as $years) cannot be parsed as an interval.
491 *
492 * @return static
493 */
494 public static function create($years = 1, $months = null, $weeks = null, $days = null, $hours = null, $minutes = null, $seconds = null, $microseconds = null)
495 {
496 return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $microseconds);
497 }
498
499 /**
500 * Parse a string into a new CarbonInterval object according to the specified format.
501 *
502 * @example
503 * ```
504 * echo Carboninterval::createFromFormat('H:i', '1:30');
505 * ```
506 *
507 * @param string $format Format of the $interval input string
508 * @param string $interval Input string to convert into an interval
509 *
510 * @throws Exception when the $interval cannot be parsed as an interval.
511 *
512 * @return static
513 */
514 public static function createFromFormat(string $format, ?string $interval)
515 {
516 $instance = new static(0);
517 $length = mb_strlen($format);
518
519 if (preg_match('/s([,.])([uv])$/', $format, $match)) {
520 $interval = explode($match[1], $interval);
521 $index = \count($interval) - 1;
522 $interval[$index] = str_pad($interval[$index], $match[2] === 'v' ? 3 : 6, '0');
523 $interval = implode($match[1], $interval);
524 }
525
526 $interval = $interval ?? '';
527
528 for ($index = 0; $index < $length; $index++) {
529 $expected = mb_substr($format, $index, 1);
530 $nextCharacter = mb_substr($interval, 0, 1);
531 $unit = static::$formats[$expected] ?? null;
532
533 if ($unit) {
534 if (!preg_match('/^-?\d+/', $interval, $match)) {
535 throw new ParseErrorException('number', $nextCharacter);
536 }
537
538 $interval = mb_substr($interval, mb_strlen($match[0]));
539 $instance->$unit += (int) ($match[0]);
540
541 continue;
542 }
543
544 if ($nextCharacter !== $expected) {
545 throw new ParseErrorException(
546 "'$expected'",
547 $nextCharacter,
548 'Allowed substitutes for interval formats are '.implode(', ', array_keys(static::$formats))."\n".
549 'See https://php.net/manual/en/function.date.php for their meaning'
550 );
551 }
552
553 $interval = mb_substr($interval, 1);
554 }
555
556 if ($interval !== '') {
557 throw new ParseErrorException(
558 'end of string',
559 $interval
560 );
561 }
562
563 return $instance;
564 }
565
566 /**
567 * Get a copy of the instance.
568 *
569 * @return static
570 */
571 public function copy()
572 {
573 $date = new static(0);
574 $date->copyProperties($this);
575 $date->step = $this->step;
576
577 return $date;
578 }
579
580 /**
581 * Get a copy of the instance.
582 *
583 * @return static
584 */
585 public function clone()
586 {
587 return $this->copy();
588 }
589
590 /**
591 * Provide static helpers to create instances. Allows CarbonInterval::years(3).
592 *
593 * Note: This is done using the magic method to allow static and instance methods to
594 * have the same names.
595 *
596 * @param string $method magic method name called
597 * @param array $parameters parameters list
598 *
599 * @return static|null
600 */
601 public static function __callStatic($method, $parameters)
602 {
603 try {
604 $interval = new static(0);
605 $localStrictModeEnabled = $interval->localStrictModeEnabled;
606 $interval->localStrictModeEnabled = true;
607
608 $result = static::hasMacro($method)
609 ? static::bindMacroContext(null, function () use (&$method, &$parameters, &$interval) {
610 return $interval->callMacro($method, $parameters);
611 })
612 : $interval->$method(...$parameters);
613
614 $interval->localStrictModeEnabled = $localStrictModeEnabled;
615
616 return $result;
617 } catch (BadFluentSetterException $exception) {
618 if (Carbon::isStrictModeEnabled()) {
619 throw new BadFluentConstructorException($method, 0, $exception);
620 }
621
622 return null;
623 }
624 }
625
626 /**
627 * Return the current context from inside a macro callee or a new one if static.
628 *
629 * @return static
630 */
631 protected static function this()
632 {
633 return end(static::$macroContextStack) ?: new static(0);
634 }
635
636 /**
637 * Creates a CarbonInterval from string.
638 *
639 * Format:
640 *
641 * Suffix | Unit | Example | DateInterval expression
642 * -------|---------|---------|------------------------
643 * y | years | 1y | P1Y
644 * mo | months | 3mo | P3M
645 * w | weeks | 2w | P2W
646 * d | days | 28d | P28D
647 * h | hours | 4h | PT4H
648 * m | minutes | 12m | PT12M
649 * s | seconds | 59s | PT59S
650 *
651 * e. g. `1w 3d 4h 32m 23s` is converted to 10 days 4 hours 32 minutes and 23 seconds.
652 *
653 * Special cases:
654 * - An empty string will return a zero interval
655 * - Fractions are allowed for weeks, days, hours and minutes and will be converted
656 * and rounded to the next smaller value (caution: 0.5w = 4d)
657 *
658 * @param string $intervalDefinition
659 *
660 * @return static
661 */
662 public static function fromString($intervalDefinition)
663 {
664 if (empty($intervalDefinition)) {
665 return new static(0);
666 }
667
668 $years = 0;
669 $months = 0;
670 $weeks = 0;
671 $days = 0;
672 $hours = 0;
673 $minutes = 0;
674 $seconds = 0;
675 $milliseconds = 0;
676 $microseconds = 0;
677
678 $pattern = '/(\d+(?:\.\d+)?)\h*([^\d\h]*)/i';
679 preg_match_all($pattern, $intervalDefinition, $parts, PREG_SET_ORDER);
680
681 while ([$part, $value, $unit] = array_shift($parts)) {
682 $intValue = (int) $value;
683 $fraction = (float) $value - $intValue;
684
685 // Fix calculation precision
686 switch (round($fraction, 6)) {
687 case 1:
688 $fraction = 0;
689 $intValue++;
690
691 break;
692 case 0:
693 $fraction = 0;
694
695 break;
696 }
697
698 switch ($unit === 'µs' ? 'µs' : strtolower($unit)) {
699 case 'millennia':
700 case 'millennium':
701 $years += $intValue * CarbonInterface::YEARS_PER_MILLENNIUM;
702
703 break;
704
705 case 'century':
706 case 'centuries':
707 $years += $intValue * CarbonInterface::YEARS_PER_CENTURY;
708
709 break;
710
711 case 'decade':
712 case 'decades':
713 $years += $intValue * CarbonInterface::YEARS_PER_DECADE;
714
715 break;
716
717 case 'year':
718 case 'years':
719 case 'y':
720 $years += $intValue;
721
722 break;
723
724 case 'quarter':
725 case 'quarters':
726 $months += $intValue * CarbonInterface::MONTHS_PER_QUARTER;
727
728 break;
729
730 case 'month':
731 case 'months':
732 case 'mo':
733 $months += $intValue;
734
735 break;
736
737 case 'week':
738 case 'weeks':
739 case 'w':
740 $weeks += $intValue;
741
742 if ($fraction) {
743 $parts[] = [null, $fraction * static::getDaysPerWeek(), 'd'];
744 }
745
746 break;
747
748 case 'day':
749 case 'days':
750 case 'd':
751 $days += $intValue;
752
753 if ($fraction) {
754 $parts[] = [null, $fraction * static::getHoursPerDay(), 'h'];
755 }
756
757 break;
758
759 case 'hour':
760 case 'hours':
761 case 'h':
762 $hours += $intValue;
763
764 if ($fraction) {
765 $parts[] = [null, $fraction * static::getMinutesPerHour(), 'm'];
766 }
767
768 break;
769
770 case 'minute':
771 case 'minutes':
772 case 'm':
773 $minutes += $intValue;
774
775 if ($fraction) {
776 $parts[] = [null, $fraction * static::getSecondsPerMinute(), 's'];
777 }
778
779 break;
780
781 case 'second':
782 case 'seconds':
783 case 's':
784 $seconds += $intValue;
785
786 if ($fraction) {
787 $parts[] = [null, $fraction * static::getMillisecondsPerSecond(), 'ms'];
788 }
789
790 break;
791
792 case 'millisecond':
793 case 'milliseconds':
794 case 'milli':
795 case 'ms':
796 $milliseconds += $intValue;
797
798 if ($fraction) {
799 $microseconds += round($fraction * static::getMicrosecondsPerMillisecond());
800 }
801
802 break;
803
804 case 'microsecond':
805 case 'microseconds':
806 case 'micro':
807 case 'µs':
808 $microseconds += $intValue;
809
810 break;
811
812 default:
813 throw new InvalidIntervalException(
814 sprintf('Invalid part %s in definition %s', $part, $intervalDefinition)
815 );
816 }
817 }
818
819 return new static($years, $months, $weeks, $days, $hours, $minutes, $seconds, $milliseconds * Carbon::MICROSECONDS_PER_MILLISECOND + $microseconds);
820 }
821
822 /**
823 * Creates a CarbonInterval from string using a different locale.
824 *
825 * @param string $interval interval string in the given language (may also contain English).
826 * @param string|null $locale if locale is null or not specified, current global locale will be used instead.
827 *
828 * @return static
829 */
830 public static function parseFromLocale($interval, $locale = null)
831 {
832 return static::fromString(Carbon::translateTimeString($interval, $locale ?: static::getLocale(), 'en'));
833 }
834
835 private static function castIntervalToClass(DateInterval $interval, string $className)
836 {
837 $mainClass = DateInterval::class;
838
839 if (!is_a($className, $mainClass, true)) {
840 throw new InvalidCastException("$className is not a sub-class of $mainClass.");
841 }
842
843 $microseconds = $interval->f;
844 $instance = new $className(static::getDateIntervalSpec($interval));
845
846 if ($microseconds) {
847 $instance->f = $microseconds;
848 }
849
850 if ($interval instanceof self && is_a($className, self::class, true)) {
851 static::copyStep($interval, $instance);
852 }
853
854 static::copyNegativeUnits($interval, $instance);
855
856 return $instance;
857 }
858
859 private static function copyNegativeUnits(DateInterval $from, DateInterval $to): void
860 {
861 $to->invert = $from->invert;
862
863 foreach (['y', 'm', 'd', 'h', 'i', 's'] as $unit) {
864 if ($from->$unit < 0) {
865 $to->$unit *= -1;
866 }
867 }
868 }
869
870 private static function copyStep(self $from, self $to): void
871 {
872 $to->setStep($from->getStep());
873 }
874
875 /**
876 * Cast the current instance into the given class.
877 *
878 * @param string $className The $className::instance() method will be called to cast the current object.
879 *
880 * @return DateInterval
881 */
882 public function cast(string $className)
883 {
884 return self::castIntervalToClass($this, $className);
885 }
886
887 /**
888 * Create a CarbonInterval instance from a DateInterval one. Can not instance
889 * DateInterval objects created from DateTime::diff() as you can't externally
890 * set the $days field.
891 *
892 * @param DateInterval $interval
893 *
894 * @return static
895 */
896 public static function instance(DateInterval $interval)
897 {
898 return self::castIntervalToClass($interval, static::class);
899 }
900
901 /**
902 * Make a CarbonInterval instance from given variable if possible.
903 *
904 * Always return a new instance. Parse only strings and only these likely to be intervals (skip dates
905 * and recurrences). Throw an exception for invalid format, but otherwise return null.
906 *
907 * @param mixed|int|DateInterval|string|Closure|null $interval interval or number of the given $unit
908 * @param string|null $unit if specified, $interval must be an integer
909 *
910 * @return static|null
911 */
912 public static function make($interval, $unit = null)
913 {
914 if ($unit) {
915 $interval = "$interval ".Carbon::pluralUnit($unit);
916 }
917
918 if ($interval instanceof DateInterval) {
919 return static::instance($interval);
920 }
921
922 if ($interval instanceof Closure) {
923 return new static($interval);
924 }
925
926 if (!\is_string($interval)) {
927 return null;
928 }
929
930 return static::makeFromString($interval);
931 }
932
933 protected static function makeFromString(string $interval)
934 {
935 $interval = preg_replace('/\s+/', ' ', trim($interval));
936
937 if (preg_match('/^P[T0-9]/', $interval)) {
938 return new static($interval);
939 }
940
941 if (preg_match('/^(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+$/i', $interval)) {
942 return static::fromString($interval);
943 }
944
945 /** @var static $interval */
946 $interval = static::createFromDateString($interval);
947
948 return !$interval || $interval->isEmpty() ? null : $interval;
949 }
950
951 protected function resolveInterval($interval)
952 {
953 if (!($interval instanceof self)) {
954 return self::make($interval);
955 }
956
957 return $interval;
958 }
959
960 /**
961 * Sets up a DateInterval from the relative parts of the string.
962 *
963 * @param string $time
964 *
965 * @return static
966 *
967 * @link https://php.net/manual/en/dateinterval.createfromdatestring.php
968 */
969 #[ReturnTypeWillChange]
970 public static function createFromDateString($time)
971 {
972 $interval = @parent::createFromDateString(strtr($time, [
973 ',' => ' ',
974 ' and ' => ' ',
975 ]));
976
977 if ($interval instanceof DateInterval) {
978 $interval = static::instance($interval);
979 }
980
981 return $interval;
982 }
983
984 ///////////////////////////////////////////////////////////////////
985 ///////////////////////// GETTERS AND SETTERS /////////////////////
986 ///////////////////////////////////////////////////////////////////
987
988 /**
989 * Get a part of the CarbonInterval object.
990 *
991 * @param string $name
992 *
993 * @throws UnknownGetterException
994 *
995 * @return int|float|string
996 */
997 public function get($name)
998 {
999 if (str_starts_with($name, 'total')) {
1000 return $this->total(substr($name, 5));
1001 }
1002
1003 switch ($name) {
1004 case 'years':
1005 return $this->y;
1006
1007 case 'months':
1008 return $this->m;
1009
1010 case 'dayz':
1011 return $this->d;
1012
1013 case 'hours':
1014 return $this->h;
1015
1016 case 'minutes':
1017 return $this->i;
1018
1019 case 'seconds':
1020 return $this->s;
1021
1022 case 'milli':
1023 case 'milliseconds':
1024 return (int) (round($this->f * Carbon::MICROSECONDS_PER_SECOND) / Carbon::MICROSECONDS_PER_MILLISECOND);
1025
1026 case 'micro':
1027 case 'microseconds':
1028 return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND);
1029
1030 case 'microExcludeMilli':
1031 return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND;
1032
1033 case 'weeks':
1034 return (int) ($this->d / static::getDaysPerWeek());
1035
1036 case 'daysExcludeWeeks':
1037 case 'dayzExcludeWeeks':
1038 return $this->d % static::getDaysPerWeek();
1039
1040 case 'locale':
1041 return $this->getTranslatorLocale();
1042
1043 default:
1044 throw new UnknownGetterException($name);
1045 }
1046 }
1047
1048 /**
1049 * Get a part of the CarbonInterval object.
1050 *
1051 * @param string $name
1052 *
1053 * @throws UnknownGetterException
1054 *
1055 * @return int|float|string
1056 */
1057 public function __get($name)
1058 {
1059 return $this->get($name);
1060 }
1061
1062 /**
1063 * Set a part of the CarbonInterval object.
1064 *
1065 * @param string|array $name
1066 * @param int $value
1067 *
1068 * @throws UnknownSetterException
1069 *
1070 * @return $this
1071 */
1072 public function set($name, $value = null)
1073 {
1074 $properties = \is_array($name) ? $name : [$name => $value];
1075
1076 foreach ($properties as $key => $value) {
1077 switch (Carbon::singularUnit(rtrim($key, 'z'))) {
1078 case 'year':
1079 $this->y = $value;
1080
1081 break;
1082
1083 case 'month':
1084 $this->m = $value;
1085
1086 break;
1087
1088 case 'week':
1089 $this->d = $value * static::getDaysPerWeek();
1090
1091 break;
1092
1093 case 'day':
1094 $this->d = $value;
1095
1096 break;
1097
1098 case 'daysexcludeweek':
1099 case 'dayzexcludeweek':
1100 $this->d = $this->weeks * static::getDaysPerWeek() + $value;
1101
1102 break;
1103
1104 case 'hour':
1105 $this->h = $value;
1106
1107 break;
1108
1109 case 'minute':
1110 $this->i = $value;
1111
1112 break;
1113
1114 case 'second':
1115 $this->s = $value;
1116
1117 break;
1118
1119 case 'milli':
1120 case 'millisecond':
1121 $this->microseconds = $value * Carbon::MICROSECONDS_PER_MILLISECOND + $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND;
1122
1123 break;
1124
1125 case 'micro':
1126 case 'microsecond':
1127 $this->f = $value / Carbon::MICROSECONDS_PER_SECOND;
1128
1129 break;
1130
1131 default:
1132 if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) {
1133 throw new UnknownSetterException($key);
1134 }
1135
1136 $this->$key = $value;
1137 }
1138 }
1139
1140 return $this;
1141 }
1142
1143 /**
1144 * Set a part of the CarbonInterval object.
1145 *
1146 * @param string $name
1147 * @param int $value
1148 *
1149 * @throws UnknownSetterException
1150 */
1151 public function __set($name, $value)
1152 {
1153 $this->set($name, $value);
1154 }
1155
1156 /**
1157 * Allow setting of weeks and days to be cumulative.
1158 *
1159 * @param int $weeks Number of weeks to set
1160 * @param int $days Number of days to set
1161 *
1162 * @return static
1163 */
1164 public function weeksAndDays($weeks, $days)
1165 {
1166 $this->dayz = ($weeks * static::getDaysPerWeek()) + $days;
1167
1168 return $this;
1169 }
1170
1171 /**
1172 * Returns true if the interval is empty for each unit.
1173 *
1174 * @return bool
1175 */
1176 public function isEmpty()
1177 {
1178 return $this->years === 0 &&
1179 $this->months === 0 &&
1180 $this->dayz === 0 &&
1181 !$this->days &&
1182 $this->hours === 0 &&
1183 $this->minutes === 0 &&
1184 $this->seconds === 0 &&
1185 $this->microseconds === 0;
1186 }
1187
1188 /**
1189 * Register a custom macro.
1190 *
1191 * @example
1192 * ```
1193 * CarbonInterval::macro('twice', function () {
1194 * return $this->times(2);
1195 * });
1196 * echo CarbonInterval::hours(2)->twice();
1197 * ```
1198 *
1199 * @param string $name
1200 * @param object|callable $macro
1201 *
1202 * @return void
1203 */
1204 public static function macro($name, $macro)
1205 {
1206 static::$macros[$name] = $macro;
1207 }
1208
1209 /**
1210 * Register macros from a mixin object.
1211 *
1212 * @example
1213 * ```
1214 * CarbonInterval::mixin(new class {
1215 * public function daysToHours() {
1216 * return function () {
1217 * $this->hours += $this->days;
1218 * $this->days = 0;
1219 *
1220 * return $this;
1221 * };
1222 * }
1223 * public function hoursToDays() {
1224 * return function () {
1225 * $this->days += $this->hours;
1226 * $this->hours = 0;
1227 *
1228 * return $this;
1229 * };
1230 * }
1231 * });
1232 * echo CarbonInterval::hours(5)->hoursToDays() . "\n";
1233 * echo CarbonInterval::days(5)->daysToHours() . "\n";
1234 * ```
1235 *
1236 * @param object|string $mixin
1237 *
1238 * @throws ReflectionException
1239 *
1240 * @return void
1241 */
1242 public static function mixin($mixin)
1243 {
1244 static::baseMixin($mixin);
1245 }
1246
1247 /**
1248 * Check if macro is registered.
1249 *
1250 * @param string $name
1251 *
1252 * @return bool
1253 */
1254 public static function hasMacro($name)
1255 {
1256 return isset(static::$macros[$name]);
1257 }
1258
1259 /**
1260 * Call given macro.
1261 *
1262 * @param string $name
1263 * @param array $parameters
1264 *
1265 * @return mixed
1266 */
1267 protected function callMacro($name, $parameters)
1268 {
1269 $macro = static::$macros[$name];
1270
1271 if ($macro instanceof Closure) {
1272 $boundMacro = @$macro->bindTo($this, static::class) ?: @$macro->bindTo(null, static::class);
1273
1274 return ($boundMacro ?: $macro)(...$parameters);
1275 }
1276
1277 return $macro(...$parameters);
1278 }
1279
1280 /**
1281 * Allow fluent calls on the setters... CarbonInterval::years(3)->months(5)->day().
1282 *
1283 * Note: This is done using the magic method to allow static and instance methods to
1284 * have the same names.
1285 *
1286 * @param string $method magic method name called
1287 * @param array $parameters parameters list
1288 *
1289 * @throws BadFluentSetterException|Throwable
1290 *
1291 * @return static
1292 */
1293 public function __call($method, $parameters)
1294 {
1295 if (static::hasMacro($method)) {
1296 return static::bindMacroContext($this, function () use (&$method, &$parameters) {
1297 return $this->callMacro($method, $parameters);
1298 });
1299 }
1300
1301 $roundedValue = $this->callRoundMethod($method, $parameters);
1302
1303 if ($roundedValue !== null) {
1304 return $roundedValue;
1305 }
1306
1307 if (preg_match('/^(?<method>add|sub)(?<unit>[A-Z].*)$/', $method, $match)) {
1308 return $this->{$match['method']}($parameters[0], $match['unit']);
1309 }
1310
1311 try {
1312 $this->set($method, \count($parameters) === 0 ? 1 : $parameters[0]);
1313 } catch (UnknownSetterException $exception) {
1314 if ($this->localStrictModeEnabled ?? Carbon::isStrictModeEnabled()) {
1315 throw new BadFluentSetterException($method, 0, $exception);
1316 }
1317 }
1318
1319 return $this;
1320 }
1321
1322 protected function getForHumansInitialVariables($syntax, $short)
1323 {
1324 if (\is_array($syntax)) {
1325 return $syntax;
1326 }
1327
1328 if (\is_int($short)) {
1329 return [
1330 'parts' => $short,
1331 'short' => false,
1332 ];
1333 }
1334
1335 if (\is_bool($syntax)) {
1336 return [
1337 'short' => $syntax,
1338 'syntax' => CarbonInterface::DIFF_ABSOLUTE,
1339 ];
1340 }
1341
1342 return [];
1343 }
1344
1345 /**
1346 * @param mixed $syntax
1347 * @param mixed $short
1348 * @param mixed $parts
1349 * @param mixed $options
1350 *
1351 * @return array
1352 */
1353 protected function getForHumansParameters($syntax = null, $short = false, $parts = -1, $options = null)
1354 {
1355 $optionalSpace = ' ';
1356 $default = $this->getTranslationMessage('list.0') ?? $this->getTranslationMessage('list') ?? ' ';
1357 $join = $default === '' ? '' : ' ';
1358 $altNumbers = false;
1359 $aUnit = false;
1360 $minimumUnit = 's';
1361 extract($this->getForHumansInitialVariables($syntax, $short));
1362
1363 if ($syntax === null) {
1364 $syntax = CarbonInterface::DIFF_ABSOLUTE;
1365 }
1366
1367 if ($parts === -1) {
1368 $parts = INF;
1369 }
1370
1371 if ($options === null) {
1372 $options = static::getHumanDiffOptions();
1373 }
1374
1375 if ($join === false) {
1376 $join = ' ';
1377 } elseif ($join === true) {
1378 $join = [
1379 $default,
1380 $this->getTranslationMessage('list.1') ?? $default,
1381 ];
1382 }
1383
1384 if ($altNumbers) {
1385 if ($altNumbers !== true) {
1386 $language = new Language($this->locale);
1387 $altNumbers = \in_array($language->getCode(), (array) $altNumbers);
1388 }
1389 }
1390
1391 if (\is_array($join)) {
1392 [$default, $last] = $join;
1393
1394 if ($default !== ' ') {
1395 $optionalSpace = '';
1396 }
1397
1398 $join = function ($list) use ($default, $last) {
1399 if (\count($list) < 2) {
1400 return implode('', $list);
1401 }
1402
1403 $end = array_pop($list);
1404
1405 return implode($default, $list).$last.$end;
1406 };
1407 }
1408
1409 if (\is_string($join)) {
1410 if ($join !== ' ') {
1411 $optionalSpace = '';
1412 }
1413
1414 $glue = $join;
1415 $join = function ($list) use ($glue) {
1416 return implode($glue, $list);
1417 };
1418 }
1419
1420 $interpolations = [
1421 ':optional-space' => $optionalSpace,
1422 ];
1423
1424 return [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit];
1425 }
1426
1427 protected static function getRoundingMethodFromOptions(int $options): ?string
1428 {
1429 if ($options & CarbonInterface::ROUND) {
1430 return 'round';
1431 }
1432
1433 if ($options & CarbonInterface::CEIL) {
1434 return 'ceil';
1435 }
1436
1437 if ($options & CarbonInterface::FLOOR) {
1438 return 'floor';
1439 }
1440
1441 return null;
1442 }
1443
1444 /**
1445 * Returns interval values as an array where key are the unit names and values the counts.
1446 *
1447 * @return int[]
1448 */
1449 public function toArray()
1450 {
1451 return [
1452 'years' => $this->years,
1453 'months' => $this->months,
1454 'weeks' => $this->weeks,
1455 'days' => $this->daysExcludeWeeks,
1456 'hours' => $this->hours,
1457 'minutes' => $this->minutes,
1458 'seconds' => $this->seconds,
1459 'microseconds' => $this->microseconds,
1460 ];
1461 }
1462
1463 /**
1464 * Returns interval non-zero values as an array where key are the unit names and values the counts.
1465 *
1466 * @return int[]
1467 */
1468 public function getNonZeroValues()
1469 {
1470 return array_filter($this->toArray(), 'intval');
1471 }
1472
1473 /**
1474 * Returns interval values as an array where key are the unit names and values the counts
1475 * from the biggest non-zero one the the smallest non-zero one.
1476 *
1477 * @return int[]
1478 */
1479 public function getValuesSequence()
1480 {
1481 $nonZeroValues = $this->getNonZeroValues();
1482
1483 if ($nonZeroValues === []) {
1484 return [];
1485 }
1486
1487 $keys = array_keys($nonZeroValues);
1488 $firstKey = $keys[0];
1489 $lastKey = $keys[\count($keys) - 1];
1490 $values = [];
1491 $record = false;
1492
1493 foreach ($this->toArray() as $unit => $count) {
1494 if ($unit === $firstKey) {
1495 $record = true;
1496 }
1497
1498 if ($record) {
1499 $values[$unit] = $count;
1500 }
1501
1502 if ($unit === $lastKey) {
1503 $record = false;
1504 }
1505 }
1506
1507 return $values;
1508 }
1509
1510 /**
1511 * Get the current interval in a human readable format in the current locale.
1512 *
1513 * @example
1514 * ```
1515 * echo CarbonInterval::fromString('4d 3h 40m')->forHumans() . "\n";
1516 * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 2]) . "\n";
1517 * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['parts' => 3, 'join' => true]) . "\n";
1518 * echo CarbonInterval::fromString('4d 3h 40m')->forHumans(['short' => true]) . "\n";
1519 * echo CarbonInterval::fromString('1d 24h')->forHumans(['join' => ' or ']) . "\n";
1520 * echo CarbonInterval::fromString('1d 24h')->forHumans(['minimumUnit' => 'hour']) . "\n";
1521 * ```
1522 *
1523 * @param int|array $syntax if array passed, parameters will be extracted from it, the array may contains:
1524 * - 'syntax' entry (see below)
1525 * - 'short' entry (see below)
1526 * - 'parts' entry (see below)
1527 * - 'options' entry (see below)
1528 * - 'aUnit' entry, prefer "an hour" over "1 hour" if true
1529 * - 'join' entry determines how to join multiple parts of the string
1530 * ` - if $join is a string, it's used as a joiner glue
1531 * ` - if $join is a callable/closure, it get the list of string and should return a string
1532 * ` - if $join is an array, the first item will be the default glue, and the second item
1533 * ` will be used instead of the glue for the last item
1534 * ` - if $join is true, it will be guessed from the locale ('list' translation file entry)
1535 * ` - if $join is missing, a space will be used as glue
1536 * - 'minimumUnit' entry determines the smallest unit of time to display can be long or
1537 * ` short form of the units, e.g. 'hour' or 'h' (default value: s)
1538 * if int passed, it add modifiers:
1539 * Possible values:
1540 * - CarbonInterface::DIFF_ABSOLUTE no modifiers
1541 * - CarbonInterface::DIFF_RELATIVE_TO_NOW add ago/from now modifier
1542 * - CarbonInterface::DIFF_RELATIVE_TO_OTHER add before/after modifier
1543 * Default value: CarbonInterface::DIFF_ABSOLUTE
1544 * @param bool $short displays short format of time units
1545 * @param int $parts maximum number of parts to display (default value: -1: no limits)
1546 * @param int $options human diff options
1547 *
1548 * @throws Exception
1549 *
1550 * @return string
1551 */
1552 public function forHumans($syntax = null, $short = false, $parts = -1, $options = null)
1553 {
1554 [$syntax, $short, $parts, $options, $join, $aUnit, $altNumbers, $interpolations, $minimumUnit] = $this->getForHumansParameters($syntax, $short, $parts, $options);
1555
1556 $interval = [];
1557
1558 $syntax = (int) ($syntax === null ? CarbonInterface::DIFF_ABSOLUTE : $syntax);
1559 $absolute = $syntax === CarbonInterface::DIFF_ABSOLUTE;
1560 $relativeToNow = $syntax === CarbonInterface::DIFF_RELATIVE_TO_NOW;
1561 $count = 1;
1562 $unit = $short ? 's' : 'second';
1563 $isFuture = $this->invert === 1;
1564 $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before');
1565
1566 /** @var \Symfony\Component\Translation\Translator $translator */
1567 $translator = $this->getLocalTranslator();
1568
1569 $handleDeclensions = function ($unit, $count) use ($interpolations, $transId, $translator, $altNumbers, $absolute) {
1570 if (!$absolute) {
1571 // Some languages have special pluralization for past and future tense.
1572 $key = $unit.'_'.$transId;
1573 $result = $this->translate($key, $interpolations, $count, $translator, $altNumbers);
1574
1575 if ($result !== $key) {
1576 return $result;
1577 }
1578 }
1579
1580 $result = $this->translate($unit, $interpolations, $count, $translator, $altNumbers);
1581
1582 if ($result !== $unit) {
1583 return $result;
1584 }
1585
1586 return null;
1587 };
1588
1589 $intervalValues = $this;
1590 $method = static::getRoundingMethodFromOptions($options);
1591
1592 if ($method) {
1593 $previousCount = INF;
1594
1595 while (
1596 \count($intervalValues->getNonZeroValues()) > $parts &&
1597 ($count = \count($keys = array_keys($intervalValues->getValuesSequence()))) > 1
1598 ) {
1599 $intervalValues = $this->copy()->roundUnit(
1600 $keys[min($count, $previousCount - 1) - 2],
1601 1,
1602 $method
1603 );
1604 $previousCount = $count;
1605 }
1606 }
1607
1608 $diffIntervalArray = [
1609 ['value' => $intervalValues->years, 'unit' => 'year', 'unitShort' => 'y'],
1610 ['value' => $intervalValues->months, 'unit' => 'month', 'unitShort' => 'm'],
1611 ['value' => $intervalValues->weeks, 'unit' => 'week', 'unitShort' => 'w'],
1612 ['value' => $intervalValues->daysExcludeWeeks, 'unit' => 'day', 'unitShort' => 'd'],
1613 ['value' => $intervalValues->hours, 'unit' => 'hour', 'unitShort' => 'h'],
1614 ['value' => $intervalValues->minutes, 'unit' => 'minute', 'unitShort' => 'min'],
1615 ['value' => $intervalValues->seconds, 'unit' => 'second', 'unitShort' => 's'],
1616 ['value' => $intervalValues->milliseconds, 'unit' => 'millisecond', 'unitShort' => 'ms'],
1617 ['value' => $intervalValues->microExcludeMilli, 'unit' => 'microsecond', 'unitShort' => 'µs'],
1618 ];
1619
1620 $transChoice = function ($short, $unitData) use ($absolute, $handleDeclensions, $translator, $aUnit, $altNumbers, $interpolations) {
1621 $count = $unitData['value'];
1622
1623 if ($short) {
1624 $result = $handleDeclensions($unitData['unitShort'], $count);
1625
1626 if ($result !== null) {
1627 return $result;
1628 }
1629 } elseif ($aUnit) {
1630 $result = $handleDeclensions('a_'.$unitData['unit'], $count);
1631
1632 if ($result !== null) {
1633 return $result;
1634 }
1635 }
1636
1637 if (!$absolute) {
1638 return $handleDeclensions($unitData['unit'], $count);
1639 }
1640
1641 return $this->translate($unitData['unit'], $interpolations, $count, $translator, $altNumbers);
1642 };
1643
1644 $fallbackUnit = ['second', 's'];
1645 foreach ($diffIntervalArray as $diffIntervalData) {
1646 if ($diffIntervalData['value'] > 0) {
1647 $unit = $short ? $diffIntervalData['unitShort'] : $diffIntervalData['unit'];
1648 $count = $diffIntervalData['value'];
1649 $interval[] = $transChoice($short, $diffIntervalData);
1650 } elseif ($options & CarbonInterface::SEQUENTIAL_PARTS_ONLY && \count($interval) > 0) {
1651 break;
1652 }
1653
1654 // break the loop after we get the required number of parts in array
1655 if (\count($interval) >= $parts) {
1656 break;
1657 }
1658
1659 // break the loop after we have reached the minimum unit
1660 if (\in_array($minimumUnit, [$diffIntervalData['unit'], $diffIntervalData['unitShort']])) {
1661 $fallbackUnit = [$diffIntervalData['unit'], $diffIntervalData['unitShort']];
1662
1663 break;
1664 }
1665 }
1666
1667 if (\count($interval) === 0) {
1668 if ($relativeToNow && $options & CarbonInterface::JUST_NOW) {
1669 $key = 'diff_now';
1670 $translation = $this->translate($key, $interpolations, null, $translator);
1671
1672 if ($translation !== $key) {
1673 return $translation;
1674 }
1675 }
1676
1677 $count = $options & CarbonInterface::NO_ZERO_DIFF ? 1 : 0;
1678 $unit = $fallbackUnit[$short ? 1 : 0];
1679 $interval[] = $this->translate($unit, $interpolations, $count, $translator, $altNumbers);
1680 }
1681
1682 // join the interval parts by a space
1683 $time = $join($interval);
1684
1685 unset($diffIntervalArray, $interval);
1686
1687 if ($absolute) {
1688 return $time;
1689 }
1690
1691 $isFuture = $this->invert === 1;
1692
1693 $transId = $relativeToNow ? ($isFuture ? 'from_now' : 'ago') : ($isFuture ? 'after' : 'before');
1694
1695 if ($parts === 1) {
1696 if ($relativeToNow && $unit === 'day') {
1697 if ($count === 1 && $options & CarbonInterface::ONE_DAY_WORDS) {
1698 $key = $isFuture ? 'diff_tomorrow' : 'diff_yesterday';
1699 $translation = $this->translate($key, $interpolations, null, $translator);
1700
1701 if ($translation !== $key) {
1702 return $translation;
1703 }
1704 }
1705
1706 if ($count === 2 && $options & CarbonInterface::TWO_DAY_WORDS) {
1707 $key = $isFuture ? 'diff_after_tomorrow' : 'diff_before_yesterday';
1708 $translation = $this->translate($key, $interpolations, null, $translator);
1709
1710 if ($translation !== $key) {
1711 return $translation;
1712 }
1713 }
1714 }
1715
1716 $aTime = $aUnit ? $handleDeclensions('a_'.$unit, $count) : null;
1717
1718 $time = $aTime ?: $handleDeclensions($unit, $count) ?: $time;
1719 }
1720
1721 $time = [':time' => $time];
1722
1723 return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator);
1724 }
1725
1726 /**
1727 * Format the instance as a string using the forHumans() function.
1728 *
1729 * @throws Exception
1730 *
1731 * @return string
1732 */
1733 public function __toString()
1734 {
1735 $format = $this->localToStringFormat;
1736
1737 if ($format) {
1738 if ($format instanceof Closure) {
1739 return $format($this);
1740 }
1741
1742 return $this->format($format);
1743 }
1744
1745 return $this->forHumans();
1746 }
1747
1748 /**
1749 * Return native DateInterval PHP object matching the current instance.
1750 *
1751 * @example
1752 * ```
1753 * var_dump(CarbonInterval::hours(2)->toDateInterval());
1754 * ```
1755 *
1756 * @return DateInterval
1757 */
1758 public function toDateInterval()
1759 {
1760 return self::castIntervalToClass($this, DateInterval::class);
1761 }
1762
1763 /**
1764 * Convert the interval to a CarbonPeriod.
1765 *
1766 * @param array ...$params Start date, [end date or recurrences] and optional settings.
1767 *
1768 * @return CarbonPeriod
1769 */
1770 public function toPeriod(...$params)
1771 {
1772 return CarbonPeriod::create($this, ...$params);
1773 }
1774
1775 /**
1776 * Invert the interval.
1777 *
1778 * @param bool|int $inverted if a parameter is passed, the passed value casted as 1 or 0 is used
1779 * as the new value of the ->invert property.
1780 *
1781 * @return $this
1782 */
1783 public function invert($inverted = null)
1784 {
1785 $this->invert = (\func_num_args() === 0 ? !$this->invert : $inverted) ? 1 : 0;
1786
1787 return $this;
1788 }
1789
1790 protected function solveNegativeInterval()
1791 {
1792 if (!$this->isEmpty() && $this->years <= 0 && $this->months <= 0 && $this->dayz <= 0 && $this->hours <= 0 && $this->minutes <= 0 && $this->seconds <= 0 && $this->microseconds <= 0) {
1793 $this->years *= -1;
1794 $this->months *= -1;
1795 $this->dayz *= -1;
1796 $this->hours *= -1;
1797 $this->minutes *= -1;
1798 $this->seconds *= -1;
1799 $this->microseconds *= -1;
1800 $this->invert();
1801 }
1802
1803 return $this;
1804 }
1805
1806 /**
1807 * Add the passed interval to the current instance.
1808 *
1809 * @param string|DateInterval $unit
1810 * @param int|float $value
1811 *
1812 * @return $this
1813 */
1814 public function add($unit, $value = 1)
1815 {
1816 if (is_numeric($unit)) {
1817 [$value, $unit] = [$unit, $value];
1818 }
1819
1820 if (\is_string($unit) && !preg_match('/^\s*\d/', $unit)) {
1821 $unit = "$value $unit";
1822 $value = 1;
1823 }
1824
1825 $interval = static::make($unit);
1826
1827 if (!$interval) {
1828 throw new InvalidIntervalException('This type of data cannot be added/subtracted.');
1829 }
1830
1831 if ($value !== 1) {
1832 $interval->times($value);
1833 }
1834
1835 $sign = ($this->invert === 1) !== ($interval->invert === 1) ? -1 : 1;
1836 $this->years += $interval->y * $sign;
1837 $this->months += $interval->m * $sign;
1838 $this->dayz += ($interval->days === false ? $interval->d : $interval->days) * $sign;
1839 $this->hours += $interval->h * $sign;
1840 $this->minutes += $interval->i * $sign;
1841 $this->seconds += $interval->s * $sign;
1842 $this->microseconds += $interval->microseconds * $sign;
1843
1844 $this->solveNegativeInterval();
1845
1846 return $this;
1847 }
1848
1849 /**
1850 * Subtract the passed interval to the current instance.
1851 *
1852 * @param string|DateInterval $unit
1853 * @param int|float $value
1854 *
1855 * @return $this
1856 */
1857 public function sub($unit, $value = 1)
1858 {
1859 if (is_numeric($unit)) {
1860 [$value, $unit] = [$unit, $value];
1861 }
1862
1863 return $this->add($unit, -(float) $value);
1864 }
1865
1866 /**
1867 * Subtract the passed interval to the current instance.
1868 *
1869 * @param string|DateInterval $unit
1870 * @param int|float $value
1871 *
1872 * @return $this
1873 */
1874 public function subtract($unit, $value = 1)
1875 {
1876 return $this->sub($unit, $value);
1877 }
1878
1879 /**
1880 * Add given parameters to the current interval.
1881 *
1882 * @param int $years
1883 * @param int $months
1884 * @param int|float $weeks
1885 * @param int|float $days
1886 * @param int|float $hours
1887 * @param int|float $minutes
1888 * @param int|float $seconds
1889 * @param int|float $microseconds
1890 *
1891 * @return $this
1892 */
1893 public function plus(
1894 $years = 0,
1895 $months = 0,
1896 $weeks = 0,
1897 $days = 0,
1898 $hours = 0,
1899 $minutes = 0,
1900 $seconds = 0,
1901 $microseconds = 0
1902 ): self {
1903 return $this->add("
1904 $years years $months months $weeks weeks $days days
1905 $hours hours $minutes minutes $seconds seconds $microseconds microseconds
1906 ");
1907 }
1908
1909 /**
1910 * Add given parameters to the current interval.
1911 *
1912 * @param int $years
1913 * @param int $months
1914 * @param int|float $weeks
1915 * @param int|float $days
1916 * @param int|float $hours
1917 * @param int|float $minutes
1918 * @param int|float $seconds
1919 * @param int|float $microseconds
1920 *
1921 * @return $this
1922 */
1923 public function minus(
1924 $years = 0,
1925 $months = 0,
1926 $weeks = 0,
1927 $days = 0,
1928 $hours = 0,
1929 $minutes = 0,
1930 $seconds = 0,
1931 $microseconds = 0
1932 ): self {
1933 return $this->sub("
1934 $years years $months months $weeks weeks $days days
1935 $hours hours $minutes minutes $seconds seconds $microseconds microseconds
1936 ");
1937 }
1938
1939 /**
1940 * Multiply current instance given number of times. times() is naive, it multiplies each unit
1941 * (so day can be greater than 31, hour can be greater than 23, etc.) and the result is rounded
1942 * separately for each unit.
1943 *
1944 * Use times() when you want a fast and approximated calculation that does not cascade units.
1945 *
1946 * For a precise and cascaded calculation,
1947 *
1948 * @see multiply()
1949 *
1950 * @param float|int $factor
1951 *
1952 * @return $this
1953 */
1954 public function times($factor)
1955 {
1956 if ($factor < 0) {
1957 $this->invert = $this->invert ? 0 : 1;
1958 $factor = -$factor;
1959 }
1960
1961 $this->years = (int) round($this->years * $factor);
1962 $this->months = (int) round($this->months * $factor);
1963 $this->dayz = (int) round($this->dayz * $factor);
1964 $this->hours = (int) round($this->hours * $factor);
1965 $this->minutes = (int) round($this->minutes * $factor);
1966 $this->seconds = (int) round($this->seconds * $factor);
1967 $this->microseconds = (int) round($this->microseconds * $factor);
1968
1969 return $this;
1970 }
1971
1972 /**
1973 * Divide current instance by a given divider. shares() is naive, it divides each unit separately
1974 * and the result is rounded for each unit. So 5 hours and 20 minutes shared by 3 becomes 2 hours
1975 * and 7 minutes.
1976 *
1977 * Use shares() when you want a fast and approximated calculation that does not cascade units.
1978 *
1979 * For a precise and cascaded calculation,
1980 *
1981 * @see divide()
1982 *
1983 * @param float|int $divider
1984 *
1985 * @return $this
1986 */
1987 public function shares($divider)
1988 {
1989 return $this->times(1 / $divider);
1990 }
1991
1992 protected function copyProperties(self $interval, $ignoreSign = false)
1993 {
1994 $this->years = $interval->years;
1995 $this->months = $interval->months;
1996 $this->dayz = $interval->dayz;
1997 $this->hours = $interval->hours;
1998 $this->minutes = $interval->minutes;
1999 $this->seconds = $interval->seconds;
2000 $this->microseconds = $interval->microseconds;
2001
2002 if (!$ignoreSign) {
2003 $this->invert = $interval->invert;
2004 }
2005
2006 return $this;
2007 }
2008
2009 /**
2010 * Multiply and cascade current instance by a given factor.
2011 *
2012 * @param float|int $factor
2013 *
2014 * @return $this
2015 */
2016 public function multiply($factor)
2017 {
2018 if ($factor < 0) {
2019 $this->invert = $this->invert ? 0 : 1;
2020 $factor = -$factor;
2021 }
2022
2023 $yearPart = (int) floor($this->years * $factor); // Split calculation to prevent imprecision
2024
2025 if ($yearPart) {
2026 $this->years -= $yearPart / $factor;
2027 }
2028
2029 return $this->copyProperties(
2030 static::create($yearPart)
2031 ->microseconds(abs($this->totalMicroseconds) * $factor)
2032 ->cascade(),
2033 true
2034 );
2035 }
2036
2037 /**
2038 * Divide and cascade current instance by a given divider.
2039 *
2040 * @param float|int $divider
2041 *
2042 * @return $this
2043 */
2044 public function divide($divider)
2045 {
2046 return $this->multiply(1 / $divider);
2047 }
2048
2049 /**
2050 * Get the interval_spec string of a date interval.
2051 *
2052 * @param DateInterval $interval
2053 *
2054 * @return string
2055 */
2056 public static function getDateIntervalSpec(DateInterval $interval)
2057 {
2058 $date = array_filter([
2059 static::PERIOD_YEARS => abs($interval->y),
2060 static::PERIOD_MONTHS => abs($interval->m),
2061 static::PERIOD_DAYS => abs($interval->d),
2062 ]);
2063
2064 $time = array_filter([
2065 static::PERIOD_HOURS => abs($interval->h),
2066 static::PERIOD_MINUTES => abs($interval->i),
2067 static::PERIOD_SECONDS => abs($interval->s),
2068 ]);
2069
2070 $specString = static::PERIOD_PREFIX;
2071
2072 foreach ($date as $key => $value) {
2073 $specString .= $value.$key;
2074 }
2075
2076 if (\count($time) > 0) {
2077 $specString .= static::PERIOD_TIME_PREFIX;
2078 foreach ($time as $key => $value) {
2079 $specString .= $value.$key;
2080 }
2081 }
2082
2083 return $specString === static::PERIOD_PREFIX ? 'PT0S' : $specString;
2084 }
2085
2086 /**
2087 * Get the interval_spec string.
2088 *
2089 * @return string
2090 */
2091 public function spec()
2092 {
2093 return static::getDateIntervalSpec($this);
2094 }
2095
2096 /**
2097 * Comparing 2 date intervals.
2098 *
2099 * @param DateInterval $first
2100 * @param DateInterval $second
2101 *
2102 * @return int
2103 */
2104 public static function compareDateIntervals(DateInterval $first, DateInterval $second)
2105 {
2106 $current = Carbon::now();
2107 $passed = $current->avoidMutation()->add($second);
2108 $current->add($first);
2109
2110 if ($current < $passed) {
2111 return -1;
2112 }
2113 if ($current > $passed) {
2114 return 1;
2115 }
2116
2117 return 0;
2118 }
2119
2120 /**
2121 * Comparing with passed interval.
2122 *
2123 * @param DateInterval $interval
2124 *
2125 * @return int
2126 */
2127 public function compare(DateInterval $interval)
2128 {
2129 return static::compareDateIntervals($this, $interval);
2130 }
2131
2132 private function invertCascade(array $values)
2133 {
2134 return $this->set(array_map(function ($value) {
2135 return -$value;
2136 }, $values))->doCascade(true)->invert();
2137 }
2138
2139 private function doCascade(bool $deep)
2140 {
2141 $originalData = $this->toArray();
2142 $originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond());
2143 $originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond();
2144 $originalData['daysExcludeWeeks'] = $originalData['days'];
2145 unset($originalData['days']);
2146 $newData = $originalData;
2147
2148 foreach (static::getFlipCascadeFactors() as $source => [$target, $factor]) {
2149 foreach (['source', 'target'] as $key) {
2150 if ($$key === 'dayz') {
2151 $$key = 'daysExcludeWeeks';
2152 }
2153 }
2154
2155 $value = $newData[$source];
2156 $modulo = ($factor + ($value % $factor)) % $factor;
2157 $newData[$source] = $modulo;
2158 $newData[$target] += ($value - $modulo) / $factor;
2159 }
2160
2161 $positive = null;
2162
2163 if (!$deep) {
2164 foreach ($newData as $value) {
2165 if ($value) {
2166 if ($positive === null) {
2167 $positive = ($value > 0);
2168
2169 continue;
2170 }
2171
2172 if (($value > 0) !== $positive) {
2173 return $this->invertCascade($originalData)
2174 ->solveNegativeInterval();
2175 }
2176 }
2177 }
2178 }
2179
2180 return $this->set($newData)
2181 ->solveNegativeInterval();
2182 }
2183
2184 /**
2185 * Convert overflowed values into bigger units.
2186 *
2187 * @return $this
2188 */
2189 public function cascade()
2190 {
2191 return $this->doCascade(false);
2192 }
2193
2194 public function hasNegativeValues(): bool
2195 {
2196 foreach ($this->toArray() as $value) {
2197 if ($value < 0) {
2198 return true;
2199 }
2200 }
2201
2202 return false;
2203 }
2204
2205 public function hasPositiveValues(): bool
2206 {
2207 foreach ($this->toArray() as $value) {
2208 if ($value > 0) {
2209 return true;
2210 }
2211 }
2212
2213 return false;
2214 }
2215
2216 /**
2217 * Get amount of given unit equivalent to the interval.
2218 *
2219 * @param string $unit
2220 *
2221 * @throws UnknownUnitException|UnitNotConfiguredException
2222 *
2223 * @return float
2224 */
2225 public function total($unit)
2226 {
2227 $realUnit = $unit = strtolower($unit);
2228
2229 if (\in_array($unit, ['days', 'weeks'])) {
2230 $realUnit = 'dayz';
2231 } elseif (!\in_array($unit, ['microseconds', 'milliseconds', 'seconds', 'minutes', 'hours', 'dayz', 'months', 'years'])) {
2232 throw new UnknownUnitException($unit);
2233 }
2234
2235 $result = 0;
2236 $cumulativeFactor = 0;
2237 $unitFound = false;
2238 $factors = static::getFlipCascadeFactors();
2239 $daysPerWeek = static::getDaysPerWeek();
2240
2241 $values = [
2242 'years' => $this->years,
2243 'months' => $this->months,
2244 'weeks' => (int) ($this->d / $daysPerWeek),
2245 'dayz' => $this->d % $daysPerWeek,
2246 'hours' => $this->hours,
2247 'minutes' => $this->minutes,
2248 'seconds' => $this->seconds,
2249 'milliseconds' => (int) ($this->microseconds / Carbon::MICROSECONDS_PER_MILLISECOND),
2250 'microseconds' => $this->microseconds % Carbon::MICROSECONDS_PER_MILLISECOND,
2251 ];
2252
2253 if (isset($factors['dayz']) && $factors['dayz'][0] !== 'weeks') {
2254 $values['dayz'] += $values['weeks'] * $daysPerWeek;
2255 $values['weeks'] = 0;
2256 }
2257
2258 foreach ($factors as $source => [$target, $factor]) {
2259 if ($source === $realUnit) {
2260 $unitFound = true;
2261 $value = $values[$source];
2262 $result += $value;
2263 $cumulativeFactor = 1;
2264 }
2265
2266 if ($factor === false) {
2267 if ($unitFound) {
2268 break;
2269 }
2270
2271 $result = 0;
2272 $cumulativeFactor = 0;
2273
2274 continue;
2275 }
2276
2277 if ($target === $realUnit) {
2278 $unitFound = true;
2279 }
2280
2281 if ($cumulativeFactor) {
2282 $cumulativeFactor *= $factor;
2283 $result += $values[$target] * $cumulativeFactor;
2284
2285 continue;
2286 }
2287
2288 $value = $values[$source];
2289
2290 $result = ($result + $value) / $factor;
2291 }
2292
2293 if (isset($target) && !$cumulativeFactor) {
2294 $result += $values[$target];
2295 }
2296
2297 if (!$unitFound) {
2298 throw new UnitNotConfiguredException($unit);
2299 }
2300
2301 if ($this->invert) {
2302 $result *= -1;
2303 }
2304
2305 if ($unit === 'weeks') {
2306 return $result / $daysPerWeek;
2307 }
2308
2309 return $result;
2310 }
2311
2312 /**
2313 * Determines if the instance is equal to another
2314 *
2315 * @param CarbonInterval|DateInterval|mixed $interval
2316 *
2317 * @see equalTo()
2318 *
2319 * @return bool
2320 */
2321 public function eq($interval): bool
2322 {
2323 return $this->equalTo($interval);
2324 }
2325
2326 /**
2327 * Determines if the instance is equal to another
2328 *
2329 * @param CarbonInterval|DateInterval|mixed $interval
2330 *
2331 * @return bool
2332 */
2333 public function equalTo($interval): bool
2334 {
2335 $interval = $this->resolveInterval($interval);
2336
2337 return $interval !== null && $this->totalMicroseconds === $interval->totalMicroseconds;
2338 }
2339
2340 /**
2341 * Determines if the instance is not equal to another
2342 *
2343 * @param CarbonInterval|DateInterval|mixed $interval
2344 *
2345 * @see notEqualTo()
2346 *
2347 * @return bool
2348 */
2349 public function ne($interval): bool
2350 {
2351 return $this->notEqualTo($interval);
2352 }
2353
2354 /**
2355 * Determines if the instance is not equal to another
2356 *
2357 * @param CarbonInterval|DateInterval|mixed $interval
2358 *
2359 * @return bool
2360 */
2361 public function notEqualTo($interval): bool
2362 {
2363 return !$this->eq($interval);
2364 }
2365
2366 /**
2367 * Determines if the instance is greater (longer) than another
2368 *
2369 * @param CarbonInterval|DateInterval|mixed $interval
2370 *
2371 * @see greaterThan()
2372 *
2373 * @return bool
2374 */
2375 public function gt($interval): bool
2376 {
2377 return $this->greaterThan($interval);
2378 }
2379
2380 /**
2381 * Determines if the instance is greater (longer) than another
2382 *
2383 * @param CarbonInterval|DateInterval|mixed $interval
2384 *
2385 * @return bool
2386 */
2387 public function greaterThan($interval): bool
2388 {
2389 $interval = $this->resolveInterval($interval);
2390
2391 return $interval === null || $this->totalMicroseconds > $interval->totalMicroseconds;
2392 }
2393
2394 /**
2395 * Determines if the instance is greater (longer) than or equal to another
2396 *
2397 * @param CarbonInterval|DateInterval|mixed $interval
2398 *
2399 * @see greaterThanOrEqualTo()
2400 *
2401 * @return bool
2402 */
2403 public function gte($interval): bool
2404 {
2405 return $this->greaterThanOrEqualTo($interval);
2406 }
2407
2408 /**
2409 * Determines if the instance is greater (longer) than or equal to another
2410 *
2411 * @param CarbonInterval|DateInterval|mixed $interval
2412 *
2413 * @return bool
2414 */
2415 public function greaterThanOrEqualTo($interval): bool
2416 {
2417 return $this->greaterThan($interval) || $this->equalTo($interval);
2418 }
2419
2420 /**
2421 * Determines if the instance is less (shorter) than another
2422 *
2423 * @param CarbonInterval|DateInterval|mixed $interval
2424 *
2425 * @see lessThan()
2426 *
2427 * @return bool
2428 */
2429 public function lt($interval): bool
2430 {
2431 return $this->lessThan($interval);
2432 }
2433
2434 /**
2435 * Determines if the instance is less (shorter) than another
2436 *
2437 * @param CarbonInterval|DateInterval|mixed $interval
2438 *
2439 * @return bool
2440 */
2441 public function lessThan($interval): bool
2442 {
2443 $interval = $this->resolveInterval($interval);
2444
2445 return $interval !== null && $this->totalMicroseconds < $interval->totalMicroseconds;
2446 }
2447
2448 /**
2449 * Determines if the instance is less (shorter) than or equal to another
2450 *
2451 * @param CarbonInterval|DateInterval|mixed $interval
2452 *
2453 * @see lessThanOrEqualTo()
2454 *
2455 * @return bool
2456 */
2457 public function lte($interval): bool
2458 {
2459 return $this->lessThanOrEqualTo($interval);
2460 }
2461
2462 /**
2463 * Determines if the instance is less (shorter) than or equal to another
2464 *
2465 * @param CarbonInterval|DateInterval|mixed $interval
2466 *
2467 * @return bool
2468 */
2469 public function lessThanOrEqualTo($interval): bool
2470 {
2471 return $this->lessThan($interval) || $this->equalTo($interval);
2472 }
2473
2474 /**
2475 * Determines if the instance is between two others.
2476 *
2477 * The third argument allow you to specify if bounds are included or not (true by default)
2478 * but for when you including/excluding bounds may produce different results in your application,
2479 * we recommend to use the explicit methods ->betweenIncluded() or ->betweenExcluded() instead.
2480 *
2481 * @example
2482 * ```
2483 * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(3)); // true
2484 * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::hours(36)); // false
2485 * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2)); // true
2486 * CarbonInterval::hours(48)->between(CarbonInterval::day(), CarbonInterval::days(2), false); // false
2487 * ```
2488 *
2489 * @param CarbonInterval|DateInterval|mixed $interval1
2490 * @param CarbonInterval|DateInterval|mixed $interval2
2491 * @param bool $equal Indicates if an equal to comparison should be done
2492 *
2493 * @return bool
2494 */
2495 public function between($interval1, $interval2, $equal = true): bool
2496 {
2497 return $equal
2498 ? $this->greaterThanOrEqualTo($interval1) && $this->lessThanOrEqualTo($interval2)
2499 : $this->greaterThan($interval1) && $this->lessThan($interval2);
2500 }
2501
2502 /**
2503 * Determines if the instance is between two others, bounds excluded.
2504 *
2505 * @example
2506 * ```
2507 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true
2508 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false
2509 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // true
2510 * ```
2511 *
2512 * @param CarbonInterval|DateInterval|mixed $interval1
2513 * @param CarbonInterval|DateInterval|mixed $interval2
2514 *
2515 * @return bool
2516 */
2517 public function betweenIncluded($interval1, $interval2): bool
2518 {
2519 return $this->between($interval1, $interval2, true);
2520 }
2521
2522 /**
2523 * Determines if the instance is between two others, bounds excluded.
2524 *
2525 * @example
2526 * ```
2527 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(3)); // true
2528 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::hours(36)); // false
2529 * CarbonInterval::hours(48)->betweenExcluded(CarbonInterval::day(), CarbonInterval::days(2)); // false
2530 * ```
2531 *
2532 * @param CarbonInterval|DateInterval|mixed $interval1
2533 * @param CarbonInterval|DateInterval|mixed $interval2
2534 *
2535 * @return bool
2536 */
2537 public function betweenExcluded($interval1, $interval2): bool
2538 {
2539 return $this->between($interval1, $interval2, false);
2540 }
2541
2542 /**
2543 * Determines if the instance is between two others
2544 *
2545 * @example
2546 * ```
2547 * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(3)); // true
2548 * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::hours(36)); // false
2549 * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2)); // true
2550 * CarbonInterval::hours(48)->isBetween(CarbonInterval::day(), CarbonInterval::days(2), false); // false
2551 * ```
2552 *
2553 * @param CarbonInterval|DateInterval|mixed $interval1
2554 * @param CarbonInterval|DateInterval|mixed $interval2
2555 * @param bool $equal Indicates if an equal to comparison should be done
2556 *
2557 * @return bool
2558 */
2559 public function isBetween($interval1, $interval2, $equal = true): bool
2560 {
2561 return $this->between($interval1, $interval2, $equal);
2562 }
2563
2564 /**
2565 * Round the current instance at the given unit with given precision if specified and the given function.
2566 *
2567 * @param string $unit
2568 * @param float|int|string|DateInterval|null $precision
2569 * @param string $function
2570 *
2571 * @throws Exception
2572 *
2573 * @return $this
2574 */
2575 public function roundUnit($unit, $precision = 1, $function = 'round')
2576 {
2577 $base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC')
2578 ->roundUnit($unit, $precision, $function);
2579 $next = $base->add($this);
2580 $inverted = $next < $base;
2581
2582 if ($inverted) {
2583 $next = $base->sub($this);
2584 }
2585
2586 $this->copyProperties(
2587 $next
2588 ->roundUnit($unit, $precision, $function)
2589 ->diffAsCarbonInterval($base)
2590 );
2591
2592 return $this->invert($inverted);
2593 }
2594
2595 /**
2596 * Truncate the current instance at the given unit with given precision if specified.
2597 *
2598 * @param string $unit
2599 * @param float|int|string|DateInterval|null $precision
2600 *
2601 * @throws Exception
2602 *
2603 * @return $this
2604 */
2605 public function floorUnit($unit, $precision = 1)
2606 {
2607 return $this->roundUnit($unit, $precision, 'floor');
2608 }
2609
2610 /**
2611 * Ceil the current instance at the given unit with given precision if specified.
2612 *
2613 * @param string $unit
2614 * @param float|int|string|DateInterval|null $precision
2615 *
2616 * @throws Exception
2617 *
2618 * @return $this
2619 */
2620 public function ceilUnit($unit, $precision = 1)
2621 {
2622 return $this->roundUnit($unit, $precision, 'ceil');
2623 }
2624
2625 /**
2626 * Round the current instance second with given precision if specified.
2627 *
2628 * @param float|int|string|DateInterval|null $precision
2629 * @param string $function
2630 *
2631 * @throws Exception
2632 *
2633 * @return $this
2634 */
2635 public function round($precision = 1, $function = 'round')
2636 {
2637 return $this->roundWith($precision, $function);
2638 }
2639
2640 /**
2641 * Round the current instance second with given precision if specified.
2642 *
2643 * @param float|int|string|DateInterval|null $precision
2644 *
2645 * @throws Exception
2646 *
2647 * @return $this
2648 */
2649 public function floor($precision = 1)
2650 {
2651 return $this->round($precision, 'floor');
2652 }
2653
2654 /**
2655 * Ceil the current instance second with given precision if specified.
2656 *
2657 * @param float|int|string|DateInterval|null $precision
2658 *
2659 * @throws Exception
2660 *
2661 * @return $this
2662 */
2663 public function ceil($precision = 1)
2664 {
2665 return $this->round($precision, 'ceil');
2666 }
2667}