Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

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