Aggiornato Composer

This commit is contained in:
Paolo A
2024-05-17 12:24:19 +00:00
parent 4ac62108b5
commit ec201d75b2
2238 changed files with 38684 additions and 59785 deletions

View File

@@ -75,6 +75,9 @@ trait Comparison
*/
public function equalTo($date): bool
{
$this->discourageNull($date);
$this->discourageBoolean($date);
return $this == $this->resolveCarbon($date);
}
@@ -155,6 +158,9 @@ trait Comparison
*/
public function greaterThan($date): bool
{
$this->discourageNull($date);
$this->discourageBoolean($date);
return $this > $this->resolveCarbon($date);
}
@@ -216,7 +222,10 @@ trait Comparison
*/
public function greaterThanOrEqualTo($date): bool
{
return $this >= $date;
$this->discourageNull($date);
$this->discourageBoolean($date);
return $this >= $this->resolveCarbon($date);
}
/**
@@ -256,6 +265,9 @@ trait Comparison
*/
public function lessThan($date): bool
{
$this->discourageNull($date);
$this->discourageBoolean($date);
return $this < $this->resolveCarbon($date);
}
@@ -317,7 +329,10 @@ trait Comparison
*/
public function lessThanOrEqualTo($date): bool
{
return $this <= $date;
$this->discourageNull($date);
$this->discourageBoolean($date);
return $this <= $this->resolveCarbon($date);
}
/**
@@ -351,10 +366,10 @@ trait Comparison
}
if ($equal) {
return $this->greaterThanOrEqualTo($date1) && $this->lessThanOrEqualTo($date2);
return $this >= $date1 && $this <= $date2;
}
return $this->greaterThan($date1) && $this->lessThan($date2);
return $this > $date1 && $this < $date2;
}
/**
@@ -448,7 +463,7 @@ trait Comparison
*/
public function isWeekend()
{
return \in_array($this->dayOfWeek, static::$weekendDays);
return \in_array($this->dayOfWeek, static::$weekendDays, true);
}
/**
@@ -548,12 +563,17 @@ trait Comparison
}
/**
* Determines if the instance is a long year
* Determines if the instance is a long year (using calendar year).
*
* ⚠️ This method completely ignores month and day to use the numeric year number,
* it's not correct if the exact date matters. For instance as `2019-12-30` is already
* in the first week of the 2020 year, if you want to know from this date if ISO week
* year 2020 is a long year, use `isLongIsoYear` instead.
*
* @example
* ```
* Carbon::parse('2015-01-01')->isLongYear(); // true
* Carbon::parse('2016-01-01')->isLongYear(); // false
* Carbon::create(2015)->isLongYear(); // true
* Carbon::create(2016)->isLongYear(); // false
* ```
*
* @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates
@@ -565,6 +585,27 @@ trait Comparison
return static::create($this->year, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53;
}
/**
* Determines if the instance is a long year (using ISO 8601 year).
*
* @example
* ```
* Carbon::parse('2015-01-01')->isLongIsoYear(); // true
* Carbon::parse('2016-01-01')->isLongIsoYear(); // true
* Carbon::parse('2016-01-03')->isLongIsoYear(); // false
* Carbon::parse('2019-12-29')->isLongIsoYear(); // false
* Carbon::parse('2019-12-30')->isLongIsoYear(); // true
* ```
*
* @see https://en.wikipedia.org/wiki/ISO_8601#Week_dates
*
* @return bool
*/
public function isLongIsoYear()
{
return static::create($this->isoWeekYear, 12, 28, 0, 0, 0, $this->tz)->weekOfYear === 53;
}
/**
* Compares the formatted values of the two dates.
*
@@ -621,19 +662,19 @@ trait Comparison
'microsecond' => 'Y-m-d H:i:s.u',
];
if (!isset($units[$unit])) {
if (isset($this->$unit)) {
return $this->resolveCarbon($date)->$unit === $this->$unit;
}
if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) {
throw new BadComparisonUnitException($unit);
}
return false;
if (isset($units[$unit])) {
return $this->isSameAs($units[$unit], $date);
}
return $this->isSameAs($units[$unit], $date);
if (isset($this->$unit)) {
return $this->resolveCarbon($date)->$unit === $this->$unit;
}
if ($this->localStrictModeEnabled ?? static::isStrictModeEnabled()) {
throw new BadComparisonUnitException($unit);
}
return false;
}
/**
@@ -981,12 +1022,12 @@ trait Comparison
return $current->startOfMinute()->eq($other);
}
if (preg_match('/\d(h|am|pm)$/', $tester)) {
if (preg_match('/\d(?:h|am|pm)$/', $tester)) {
return $current->startOfHour()->eq($other);
}
if (preg_match(
'/^(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d+$/i',
'/^(?:january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+\d+)?$/i',
$tester
)) {
return $current->startOfMonth()->eq($other->startOfMonth());
@@ -1067,4 +1108,18 @@ trait Comparison
{
return $this->endOfTime ?? false;
}
private function discourageNull($value): void
{
if ($value === null) {
@trigger_error("Since 2.61.0, it's deprecated to compare a date to null, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate null values.", \E_USER_DEPRECATED);
}
}
private function discourageBoolean($value): void
{
if (\is_bool($value)) {
@trigger_error("Since 2.61.0, it's deprecated to compare a date to true or false, meaning of such comparison is ambiguous and will no longer be possible in 3.0.0, you should explicitly pass 'now' or make an other check to eliminate boolean values.", \E_USER_DEPRECATED);
}
}
}

View File

@@ -16,6 +16,7 @@ use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Carbon\CarbonInterval;
use Carbon\CarbonPeriod;
use Carbon\CarbonPeriodImmutable;
use Carbon\Exceptions\UnitException;
use Closure;
use DateTime;
@@ -34,39 +35,7 @@ use ReturnTypeWillChange;
*/
trait Converter
{
/**
* Format to use for __toString method when type juggling occurs.
*
* @var string|Closure|null
*/
protected static $toStringFormat;
/**
* Reset the format used to the default when type juggling a Carbon instance to a string
*
* @return void
*/
public static function resetToStringFormat()
{
static::setToStringFormat(null);
}
/**
* @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
* You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and
* use other method or custom format passed to format() method if you need to dump an other string
* format.
*
* Set the default format used when type juggling a Carbon instance to a string
*
* @param string|Closure|null $format
*
* @return void
*/
public static function setToStringFormat($format)
{
static::$toStringFormat = $format;
}
use ToStringFormat;
/**
* Returns the formatted date string on success or FALSE on failure.
@@ -110,7 +79,7 @@ trait Converter
*
* @example
* ```
* echo Carbon::now(); // Carbon instances can be casted to string
* echo Carbon::now(); // Carbon instances can be cast to string
* ```
*
* @return string
@@ -158,6 +127,21 @@ trait Converter
return $this->rawFormat('M j, Y');
}
/**
* Format the instance with the day, and a readable date
*
* @example
* ```
* echo Carbon::now()->toFormattedDayDateString();
* ```
*
* @return string
*/
public function toFormattedDayDateString(): string
{
return $this->rawFormat('D, M j, Y');
}
/**
* Format the instance as time
*
@@ -622,16 +606,18 @@ trait Converter
$interval = CarbonInterval::make("$interval ".static::pluralUnit($unit));
}
$period = (new CarbonPeriod())->setDateClass(static::class)->setStartDate($this);
$period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable())
->setDateClass(static::class)
->setStartDate($this);
if ($interval) {
$period->setDateInterval($interval);
$period = $period->setDateInterval($interval);
}
if (\is_int($end) || \is_string($end) && ctype_digit($end)) {
$period->setRecurrences($end);
if (\is_int($end) || (\is_string($end) && ctype_digit($end))) {
$period = $period->setRecurrences($end);
} elseif ($end) {
$period->setEndDate($end);
$period = $period->setEndDate($end);
}
return $period;

View File

@@ -19,6 +19,8 @@ use Carbon\Exceptions\InvalidFormatException;
use Carbon\Exceptions\OutOfRangeException;
use Carbon\Translator;
use Closure;
use DateMalformedStringException;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Exception;
@@ -79,8 +81,8 @@ trait Creator
// Work-around for PHP bug https://bugs.php.net/bug.php?id=67127
if (!str_contains((string) .1, '.')) {
$locale = setlocale(LC_NUMERIC, '0');
setlocale(LC_NUMERIC, 'C');
$locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore
setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore
}
try {
@@ -92,7 +94,7 @@ trait Creator
$this->constructedObjectId = spl_object_hash($this);
if (isset($locale)) {
setlocale(LC_NUMERIC, $locale);
setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore
}
self::setLastErrors(parent::getLastErrors());
@@ -112,7 +114,7 @@ trait Creator
$safeTz = static::safeCreateDateTimeZone($tz);
if ($safeTz) {
return $date->setTimezone($safeTz);
return ($date instanceof DateTimeImmutable ? $date : clone $date)->setTimezone($safeTz);
}
return $date;
@@ -148,7 +150,7 @@ trait Creator
$instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone());
if ($date instanceof CarbonInterface || $date instanceof Options) {
if ($date instanceof CarbonInterface) {
$settings = $date->getSettings();
if (!$date->hasLocalTranslator()) {
@@ -184,7 +186,13 @@ trait Creator
try {
return new static($time, $tz);
} catch (Exception $exception) {
$date = @static::now($tz)->change($time);
// @codeCoverageIgnoreStart
try {
$date = @static::now($tz)->change($time);
} catch (DateMalformedStringException $ignoredException) {
$date = null;
}
// @codeCoverageIgnoreEnd
if (!$date) {
throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception);
@@ -354,13 +362,13 @@ trait Creator
* If $hour is not null then the default values for $minute and $second
* will be 0.
*
* @param int|null $year
* @param int|null $month
* @param int|null $day
* @param int|null $hour
* @param int|null $minute
* @param int|null $second
* @param DateTimeZone|string|null $tz
* @param DateTimeInterface|int|null $year
* @param int|null $month
* @param int|null $day
* @param int|null $hour
* @param int|null $minute
* @param int|null $second
* @param DateTimeZone|string|null $tz
*
* @throws InvalidFormatException
*
@@ -368,7 +376,7 @@ trait Creator
*/
public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null)
{
if (\is_string($year) && !is_numeric($year) || $year instanceof DateTimeInterface) {
if ((\is_string($year) && !is_numeric($year)) || $year instanceof DateTimeInterface) {
return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null));
}
@@ -634,6 +642,10 @@ trait Creator
$time = preg_replace('/^(.*)(am|pm|AM|PM)(.*)$/U', '$1$3 $2', $time);
}
if ($tz === false) {
$tz = null;
}
// First attempt to create an instance, so that error messages are based on the unmodified format.
$date = self::createFromFormatAndTimezone($format, $time, $tz);
$lastErrors = parent::getLastErrors();
@@ -651,12 +663,14 @@ trait Creator
$tz = clone $mock->getTimezone();
}
// Set microseconds to zero to match behavior of DateTime::createFromFormat()
// See https://bugs.php.net/bug.php?id=74332
$mock = $mock->copy()->microsecond(0);
$mock = $mock->copy();
// Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag.
if (!preg_match("/{$nonEscaped}[!|]/", $format)) {
if (preg_match('/[HhGgisvuB]/', $format)) {
$mock = $mock->setTime(0, 0);
}
$format = static::MOCK_DATETIME_FORMAT.' '.$format;
$time = ($mock instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $mock->format(static::MOCK_DATETIME_FORMAT)).' '.$time;
}
@@ -862,6 +876,19 @@ trait Creator
*/
public static function createFromLocaleFormat($format, $locale, $time, $tz = null)
{
$format = preg_replace_callback(
'/(?:\\\\[a-zA-Z]|[bfkqCEJKQRV]){2,}/',
static function (array $match) use ($locale): string {
$word = str_replace('\\', '', $match[0]);
$translatedWord = static::translateTimeString($word, $locale, 'en');
return $word === $translatedWord
? $match[0]
: preg_replace('/[a-zA-Z]/', '\\\\$0', $translatedWord);
},
$format
);
return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz);
}
@@ -907,9 +934,9 @@ trait Creator
if (\is_string($var)) {
$var = trim($var);
if (!preg_match('/^P[0-9T]/', $var) &&
!preg_match('/^R[0-9]/', $var) &&
preg_match('/[a-z0-9]/i', $var)
if (!preg_match('/^P[\dT]/', $var) &&
!preg_match('/^R\d/', $var) &&
preg_match('/[a-z\d]/i', $var)
) {
$date = static::parse($var);
}
@@ -921,13 +948,20 @@ trait Creator
/**
* Set last errors.
*
* @param array $lastErrors
* @param array|bool $lastErrors
*
* @return void
*/
private static function setLastErrors(array $lastErrors)
private static function setLastErrors($lastErrors)
{
static::$lastErrors = $lastErrors;
if (\is_array($lastErrors) || $lastErrors === false) {
static::$lastErrors = \is_array($lastErrors) ? $lastErrors : [
'warning_count' => 0,
'warnings' => [],
'error_count' => 0,
'errors' => [],
];
}
}
/**

View File

@@ -532,6 +532,7 @@ trait Date
use Creator;
use Difference;
use Macro;
use MagicParameter;
use Modifiers;
use Mutability;
use ObjectInitialisation;
@@ -641,9 +642,11 @@ trait Date
/**
* List of minimum and maximums for each unit.
*
* @param int $daysInMonth
*
* @return array
*/
protected static function getRangesByUnit()
protected static function getRangesByUnit(int $daysInMonth = 31): array
{
return [
// @call roundUnit
@@ -651,7 +654,7 @@ trait Date
// @call roundUnit
'month' => [1, static::MONTHS_PER_YEAR],
// @call roundUnit
'day' => [1, 31],
'day' => [1, $daysInMonth],
// @call roundUnit
'hour' => [0, static::HOURS_PER_DAY - 1],
// @call roundUnit
@@ -940,7 +943,7 @@ trait Date
case $name === 'millisecond':
// @property int
case $name === 'milli':
return (int) floor($this->rawFormat('u') / 1000);
return (int) floor(((int) $this->rawFormat('u')) / 1000);
// @property int 1 through 53
case $name === 'week':
@@ -1250,7 +1253,7 @@ trait Date
protected function getTranslatedFormByRegExp($baseKey, $keySuffix, $context, $subKey, $defaultValue)
{
$key = $baseKey.$keySuffix;
$standaloneKey = "${key}_standalone";
$standaloneKey = "{$key}_standalone";
$baseTranslation = $this->getTranslationMessage($key);
if ($baseTranslation instanceof Closure) {
@@ -1259,7 +1262,7 @@ trait Date
if (
$this->getTranslationMessage("$standaloneKey.$subKey") &&
(!$context || ($regExp = $this->getTranslationMessage("${baseKey}_regexp")) && !preg_match($regExp, $context))
(!$context || (($regExp = $this->getTranslationMessage("{$baseKey}_regexp")) && !preg_match($regExp, $context)))
) {
$key = $standaloneKey;
}
@@ -1354,9 +1357,14 @@ trait Date
*/
public function weekday($value = null)
{
$dayOfWeek = ($this->dayOfWeek + 7 - (int) ($this->getTranslationMessage('first_day_of_week') ?? 0)) % 7;
if ($value === null) {
return $this->dayOfWeek;
}
return $value === null ? $dayOfWeek : $this->addDays($value - $dayOfWeek);
$firstDay = (int) ($this->getTranslationMessage('first_day_of_week') ?? 0);
$dayOfWeek = ($this->dayOfWeek + 7 - $firstDay) % 7;
return $this->addDays((($value + 7 - $firstDay) % 7) - $dayOfWeek);
}
/**
@@ -1373,6 +1381,39 @@ trait Date
return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso);
}
/**
* Return the number of days since the start of the week (using the current locale or the first parameter
* if explicitly given).
*
* @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week,
* if not provided, start of week is inferred from the locale
* (Sunday for en_US, Monday for de_DE, etc.)
*
* @return int
*/
public function getDaysFromStartOfWeek(int $weekStartsAt = null): int
{
$firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0);
return ($this->dayOfWeek + 7 - $firstDay) % 7;
}
/**
* Set the day (keeping the current time) to the start of the week + the number of days passed as the first
* parameter. First day of week is driven by the locale unless explicitly set with the second parameter.
*
* @param int $numberOfDays number of days to add after the start of the current week
* @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week,
* if not provided, start of week is inferred from the locale
* (Sunday for en_US, Monday for de_DE, etc.)
*
* @return static
*/
public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null)
{
return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt));
}
/**
* Set any unit to a new value without overflowing current other unit given.
*
@@ -1848,9 +1889,18 @@ trait Date
$format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format); // @codeCoverageIgnore
}
$formatted = strftime($format, strtotime($this->toDateTimeString()));
$time = strtotime($this->toDateTimeString());
$formatted = ($this->localStrictModeEnabled ?? static::isStrictModeEnabled())
? strftime($format, $time)
: @strftime($format, $time);
return static::$utf8 ? utf8_encode($formatted) : $formatted;
return static::$utf8
? (
\function_exists('mb_convert_encoding')
? mb_convert_encoding($formatted, 'UTF-8', mb_list_encodings())
: utf8_encode($formatted)
)
: $formatted;
}
/**
@@ -1869,6 +1919,10 @@ trait Date
'LL' => $this->getTranslationMessage('formats.LL', $locale, 'MMMM D, YYYY'),
'LLL' => $this->getTranslationMessage('formats.LLL', $locale, 'MMMM D, YYYY h:mm A'),
'LLLL' => $this->getTranslationMessage('formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'),
'l' => $this->getTranslationMessage('formats.l', $locale),
'll' => $this->getTranslationMessage('formats.ll', $locale),
'lll' => $this->getTranslationMessage('formats.lll', $locale),
'llll' => $this->getTranslationMessage('formats.llll', $locale),
];
}
@@ -2152,7 +2206,7 @@ trait Date
$input = mb_substr($format, $i);
if (preg_match('/^(LTS|LT|[Ll]{1,4})/', $input, $match)) {
if (preg_match('/^(LTS|LT|l{1,4}|L{1,4})/', $input, $match)) {
if ($formats === null) {
$formats = $this->getIsoFormats();
}
@@ -2256,6 +2310,7 @@ trait Date
'c' => true,
'r' => true,
'U' => true,
'T' => true,
];
}
@@ -2359,7 +2414,7 @@ trait Date
$symbol = $second < 0 ? '-' : '+';
$minute = abs($second) / static::SECONDS_PER_MINUTE;
$hour = str_pad((string) floor($minute / static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT);
$minute = str_pad((string) ($minute % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT);
$minute = str_pad((string) (((int) $minute) % static::MINUTES_PER_HOUR), 2, '0', STR_PAD_LEFT);
return "$symbol$hour$separator$minute";
}
@@ -2479,7 +2534,7 @@ trait Date
return 'millennia';
}
return "${unit}s";
return "{$unit}s";
}
protected function executeCallable($macro, ...$parameters)
@@ -2566,7 +2621,7 @@ trait Date
if (str_starts_with($unit, 'is')) {
$word = substr($unit, 2);
if (\in_array($word, static::$days)) {
if (\in_array($word, static::$days, true)) {
return $this->isDayOfWeek($word);
}
@@ -2594,7 +2649,7 @@ trait Date
$unit = strtolower(substr($unit, 3));
}
if (\in_array($unit, static::$units)) {
if (\in_array($unit, static::$units, true)) {
return $this->setUnit($unit, ...$parameters);
}
@@ -2604,7 +2659,7 @@ trait Date
if (str_starts_with($unit, 'Real')) {
$unit = static::singularUnit(substr($unit, 4));
return $this->{"${action}RealUnit"}($unit, ...$parameters);
return $this->{"{$action}RealUnit"}($unit, ...$parameters);
}
if (preg_match('/^(Month|Quarter|Year|Decade|Century|Centurie|Millennium|Millennia)s?(No|With|Without|WithNo)Overflow$/', $unit, $match)) {
@@ -2616,7 +2671,7 @@ trait Date
}
if (static::isModifiableUnit($unit)) {
return $this->{"${action}Unit"}($unit, $parameters[0] ?? 1, $overflow);
return $this->{"{$action}Unit"}($unit, $this->getMagicParameter($parameters, 0, 'value', 1), $overflow);
}
$sixFirstLetters = substr($unit, 0, 6);
@@ -2655,7 +2710,11 @@ trait Date
try {
$unit = static::singularUnit(substr($method, 0, -5));
return $this->range($parameters[0] ?? $this, $parameters[1] ?? 1, $unit);
return $this->range(
$this->getMagicParameter($parameters, 0, 'endDate', $this),
$this->getMagicParameter($parameters, 1, 'factor', 1),
$unit
);
} catch (InvalidArgumentException $exception) {
// Try macros
}

View File

@@ -83,9 +83,9 @@ trait Difference
*
* @return CarbonInterval
*/
protected static function fixDiffInterval(DateInterval $diff, $absolute)
protected static function fixDiffInterval(DateInterval $diff, $absolute, array $skip = [])
{
$diff = CarbonInterval::instance($diff);
$diff = CarbonInterval::instance($diff, $skip);
// Work-around for https://bugs.php.net/bug.php?id=77145
// @codeCoverageIgnoreStart
@@ -148,9 +148,9 @@ trait Difference
*
* @return CarbonInterval
*/
public function diffAsCarbonInterval($date = null, $absolute = true)
public function diffAsCarbonInterval($date = null, $absolute = true, array $skip = [])
{
return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute);
return static::fixDiffInterval($this->diff($this->resolveCarbon($date), $absolute), $absolute, $skip);
}
/**
@@ -189,9 +189,21 @@ trait Difference
*/
public function diffInMonths($date = null, $absolute = true)
{
$date = $this->resolveCarbon($date);
$date = $this->resolveCarbon($date)->avoidMutation()->tz($this->tz);
return $this->diffInYears($date, $absolute) * static::MONTHS_PER_YEAR + (int) $this->diff($date, $absolute)->format('%r%m');
[$yearStart, $monthStart, $dayStart] = explode('-', $this->format('Y-m-dHisu'));
[$yearEnd, $monthEnd, $dayEnd] = explode('-', $date->format('Y-m-dHisu'));
$diff = (((int) $yearEnd) - ((int) $yearStart)) * static::MONTHS_PER_YEAR +
((int) $monthEnd) - ((int) $monthStart);
if ($diff > 0) {
$diff -= ($dayStart > $dayEnd ? 1 : 0);
} elseif ($diff < 0) {
$diff += ($dayStart < $dayEnd ? 1 : 0);
}
return $absolute ? abs($diff) : $diff;
}
/**
@@ -286,9 +298,9 @@ trait Difference
*/
public function diffInWeekdays($date = null, $absolute = true)
{
return $this->diffInDaysFiltered(function (CarbonInterface $date) {
return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
return $date->isWeekday();
}, $date, $absolute);
}, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute);
}
/**
@@ -301,9 +313,9 @@ trait Difference
*/
public function diffInWeekendDays($date = null, $absolute = true)
{
return $this->diffInDaysFiltered(function (CarbonInterface $date) {
return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
return $date->isWeekend();
}, $date, $absolute);
}, $this->resolveCarbon($date)->avoidMutation()->modify($this->format('H:i:s.u')), $absolute);
}
/**
@@ -472,7 +484,7 @@ trait Difference
*/
public function floatDiffInSeconds($date = null, $absolute = true)
{
return $this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND;
return (float) ($this->diffInMicroseconds($date, $absolute) / static::MICROSECONDS_PER_SECOND);
}
/**
@@ -830,8 +842,9 @@ trait Difference
$intSyntax = $intSyntax === static::DIFF_RELATIVE_AUTO && $other === null ? static::DIFF_RELATIVE_TO_NOW : $intSyntax;
$parts = min(7, max(1, (int) $parts));
$skip = \is_array($syntax) ? ($syntax['skip'] ?? []) : [];
return $this->diffAsCarbonInterval($other, false)
return $this->diffAsCarbonInterval($other, false, (array) $skip)
->setLocalTranslator($this->getLocalTranslator())
->forHumans($syntax, (bool) $short, $parts, $options ?? $this->localHumanDiffOptions ?? static::getHumanDiffOptions());
}
@@ -1161,7 +1174,7 @@ trait Difference
version_compare(PHP_VERSION, '8.1.0-dev', '<') &&
abs($interval->d - $daysDiff) === 1
) {
$daysDiff = abs($interval->d);
$daysDiff = abs($interval->d); // @codeCoverageIgnore
}
return $daysDiff * $sign;

View File

@@ -40,7 +40,7 @@ trait IntervalRounding
$unit = 'second';
if ($precision instanceof DateInterval) {
$precision = (string) CarbonInterval::instance($precision);
$precision = (string) CarbonInterval::instance($precision, [], true);
}
if (\is_string($precision) && preg_match('/^\s*(?<precision>\d+)?\s*(?<unit>\w+)(?<other>\W.*)?$/', $precision, $match)) {

View File

@@ -23,12 +23,16 @@ use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface;
if (!interface_exists('Symfony\\Component\\Translation\\TranslatorInterface')) {
// @codeCoverageIgnoreStart
if (interface_exists('Symfony\\Contracts\\Translation\\TranslatorInterface') &&
!interface_exists('Symfony\\Component\\Translation\\TranslatorInterface')
) {
class_alias(
'Symfony\\Contracts\\Translation\\TranslatorInterface',
'Symfony\\Component\\Translation\\TranslatorInterface'
);
}
// @codeCoverageIgnoreEnd
/**
* Trait Localization.
@@ -355,6 +359,13 @@ trait Localization
$weekdays = $messages['weekdays'] ?? [];
$meridiem = $messages['meridiem'] ?? ['AM', 'PM'];
if (isset($messages['ordinal_words'])) {
$timeString = self::replaceOrdinalWords(
$timeString,
$key === 'from' ? array_flip($messages['ordinal_words']) : $messages['ordinal_words']
);
}
if ($key === 'from') {
foreach (['months', 'weekdays'] as $variable) {
$list = $messages[$variable.'_standalone'] ?? null;
@@ -454,7 +465,7 @@ trait Localization
}
}
$this->setLocalTranslator($translator);
$this->localTranslator = $translator;
}
return $this;
@@ -555,17 +566,13 @@ trait Localization
public static function localeHasShortUnits($locale)
{
return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
return $newLocale &&
(
($y = static::translateWith($translator, 'y')) !== 'y' &&
$y !== static::translateWith($translator, 'year')
) || (
($y = static::translateWith($translator, 'd')) !== 'd' &&
return ($newLocale && (($y = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year'))) || (
($y = static::translateWith($translator, 'd')) !== 'd' &&
$y !== static::translateWith($translator, 'day')
) || (
($y = static::translateWith($translator, 'h')) !== 'h' &&
) || (
($y = static::translateWith($translator, 'h')) !== 'h' &&
$y !== static::translateWith($translator, 'hour')
);
);
});
}
@@ -734,7 +741,7 @@ trait Localization
}
if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) {
throw new NotLocaleAwareException($translator);
throw new NotLocaleAwareException($translator); // @codeCoverageIgnore
}
return $translator;
@@ -823,4 +830,11 @@ trait Localization
return $list;
}
private static function replaceOrdinalWords(string $timeString, array $ordinalWords): string
{
return preg_replace_callback('/(?<![a-z])[a-z]+(?![a-z])/i', function (array $match) use ($ordinalWords) {
return $ordinalWords[mb_strtolower($match[0])] ?? $match[0];
}, $timeString);
}
}

View File

@@ -11,6 +11,9 @@
namespace Carbon\Traits;
use Carbon\CarbonInterface;
use Carbon\CarbonInterval;
use Carbon\CarbonPeriod;
use Closure;
use Generator;
use ReflectionClass;
@@ -99,12 +102,13 @@ trait Mixin
{
$context = eval(self::getAnonymousClassCodeForTrait($trait));
$className = \get_class($context);
$baseClass = static::class;
foreach (self::getMixableMethods($context) as $name) {
$closureBase = Closure::fromCallable([$context, $name]);
static::macro($name, function () use ($closureBase, $className) {
/** @phpstan-ignore-next-line */
static::macro($name, function (...$parameters) use ($closureBase, $className, $baseClass) {
$downContext = isset($this) ? ($this) : new $baseClass();
$context = isset($this) ? $this->cast($className) : new $className();
try {
@@ -117,7 +121,48 @@ trait Mixin
// in case of errors not converted into exceptions
$closure = $closure ?: $closureBase;
return $closure(...\func_get_args());
$result = $closure(...$parameters);
if (!($result instanceof $className)) {
return $result;
}
if ($downContext instanceof CarbonInterface && $result instanceof CarbonInterface) {
if ($context !== $result) {
$downContext = $downContext->copy();
}
return $downContext
->setTimezone($result->getTimezone())
->modify($result->format('Y-m-d H:i:s.u'))
->settings($result->getSettings());
}
if ($downContext instanceof CarbonInterval && $result instanceof CarbonInterval) {
if ($context !== $result) {
$downContext = $downContext->copy();
}
$downContext->copyProperties($result);
self::copyStep($downContext, $result);
self::copyNegativeUnits($downContext, $result);
return $downContext->settings($result->getSettings());
}
if ($downContext instanceof CarbonPeriod && $result instanceof CarbonPeriod) {
if ($context !== $result) {
$downContext = $downContext->copy();
}
return $downContext
->setDates($result->getStartDate(), $result->getEndDate())
->setRecurrences($result->getRecurrences())
->setOptions($result->getOptions())
->settings($result->getSettings());
}
return $result;
});
}
}
@@ -151,22 +196,12 @@ trait Mixin
protected static function bindMacroContext($context, callable $callable)
{
static::$macroContextStack[] = $context;
$exception = null;
$result = null;
try {
$result = $callable();
} catch (Throwable $throwable) {
$exception = $throwable;
return $callable();
} finally {
array_pop(static::$macroContextStack);
}
array_pop(static::$macroContextStack);
if ($exception) {
throw $exception;
}
return $result;
}
/**

View File

@@ -75,7 +75,7 @@ trait Modifiers
*
* @param string|int|null $modifier
*
* @return static
* @return static|false
*/
public function next($modifier = null)
{
@@ -157,7 +157,7 @@ trait Modifiers
*
* @param string|int|null $modifier
*
* @return static
* @return static|false
*/
public function previous($modifier = null)
{
@@ -451,7 +451,7 @@ trait Modifiers
*
* @param string $modifier
*
* @return static
* @return static|false
*/
public function change($modifier)
{

View File

@@ -22,7 +22,7 @@ use Throwable;
*
* Depends on the following methods:
*
* @method \Carbon\Carbon|\Carbon\CarbonImmutable shiftTimezone($timezone) Set the timezone
* @method static shiftTimezone($timezone) Set the timezone
*/
trait Options
{
@@ -422,7 +422,7 @@ trait Options
foreach ($map as $property => $key) {
$value = $this->$property ?? null;
if ($value !== null) {
if ($value !== null && ($key !== 'locale' || $value !== 'en' || $this->localTranslator)) {
$settings[$key] = $value;
}
}
@@ -437,11 +437,11 @@ trait Options
*/
public function __debugInfo()
{
$infos = array_filter(get_object_vars($this), function ($var) {
$infos = array_filter(get_object_vars($this), static function ($var) {
return $var;
});
foreach (['dumpProperties', 'constructedObjectId'] as $property) {
foreach (['dumpProperties', 'constructedObjectId', 'constructed'] as $property) {
if (isset($infos[$property])) {
unset($infos[$property]);
}

View File

@@ -52,12 +52,11 @@ trait Rounding
'millisecond' => [1000, 'microsecond'],
];
$normalizedUnit = static::singularUnit($unit);
$ranges = array_merge(static::getRangesByUnit(), [
$ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [
// @call roundUnit
'microsecond' => [0, 999999],
]);
$factor = 1;
$initialMonth = $this->month;
if ($normalizedUnit === 'week') {
$normalizedUnit = 'day';
@@ -77,12 +76,15 @@ trait Rounding
$found = false;
$fraction = 0;
$arguments = null;
$initialValue = null;
$factor = $this->year < 0 ? -1 : 1;
$changes = [];
$minimumInc = null;
foreach ($ranges as $unit => [$minimum, $maximum]) {
if ($normalizedUnit === $unit) {
$arguments = [$this->$unit, $minimum];
$initialValue = $this->$unit;
$fraction = $precision - floor($precision);
$found = true;
@@ -93,7 +95,23 @@ trait Rounding
$delta = $maximum + 1 - $minimum;
$factor /= $delta;
$fraction *= $delta;
$arguments[0] += $this->$unit * $factor;
$inc = ($this->$unit - $minimum) * $factor;
if ($inc !== 0.0) {
$minimumInc = $minimumInc ?? ($arguments[0] / pow(2, 52));
// If value is still the same when adding a non-zero increment/decrement,
// it means precision got lost in the addition
if (abs($inc) < $minimumInc) {
$inc = $minimumInc * ($inc < 0 ? -1 : 1);
}
// If greater than $precision, assume precision loss caused an overflow
if ($function !== 'floor' || abs($arguments[0] + $inc - $initialValue) >= $precision) {
$arguments[0] += $inc;
}
}
$changes[$unit] = round(
$minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0)
);
@@ -111,16 +129,13 @@ trait Rounding
$normalizedValue = floor($function(($value - $minimum) / $precision) * $precision + $minimum);
/** @var CarbonInterface $result */
$result = $this->$normalizedUnit($normalizedValue);
$result = $this;
foreach ($changes as $unit => $value) {
$result = $result->$unit($value);
}
return $normalizedUnit === 'month' && $precision <= 1 && abs($result->month - $initialMonth) === 2
// Re-run the change in case an overflow occurred
? $result->$normalizedUnit($normalizedValue)
: $result;
return $result->$normalizedUnit($normalizedValue);
}
/**

View File

@@ -120,6 +120,8 @@ trait Serialization
/**
* Returns the list of properties to dump on serialize() called on.
*
* Only used by PHP < 7.4.
*
* @return array
*/
public function __sleep()
@@ -134,22 +136,70 @@ trait Serialization
return $properties;
}
/**
* Returns the values to dump on serialize() called on.
*
* Only used by PHP >= 7.4.
*
* @return array
*/
public function __serialize(): array
{
// @codeCoverageIgnoreStart
if (isset($this->timezone_type, $this->timezone, $this->date)) {
return [
'date' => $this->date ?? null,
'timezone_type' => $this->timezone_type,
'timezone' => $this->timezone ?? null,
];
}
// @codeCoverageIgnoreEnd
$timezone = $this->getTimezone();
$export = [
'date' => $this->format('Y-m-d H:i:s.u'),
'timezone_type' => $timezone->getType(),
'timezone' => $timezone->getName(),
];
// @codeCoverageIgnoreStart
if (\extension_loaded('msgpack') && isset($this->constructedObjectId)) {
$export['dumpDateProperties'] = [
'date' => $this->format('Y-m-d H:i:s.u'),
'timezone' => serialize($this->timezone ?? null),
];
}
// @codeCoverageIgnoreEnd
if ($this->localTranslator ?? null) {
$export['dumpLocale'] = $this->locale ?? null;
}
return $export;
}
/**
* Set locale if specified on unserialize() called.
*
* Only used by PHP < 7.4.
*
* @return void
*/
#[ReturnTypeWillChange]
public function __wakeup()
{
if (get_parent_class() && method_exists(parent::class, '__wakeup')) {
if (parent::class && method_exists(parent::class, '__wakeup')) {
// @codeCoverageIgnoreStart
try {
parent::__wakeup();
} catch (Throwable $exception) {
// FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later.
['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties;
parent::__construct($date, unserialize($timezone));
try {
// FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later.
['date' => $date, 'timezone' => $timezone] = $this->dumpDateProperties;
parent::__construct($date, unserialize($timezone));
} catch (Throwable $ignoredException) {
throw $exception;
}
}
// @codeCoverageIgnoreEnd
}
@@ -164,6 +214,38 @@ trait Serialization
$this->cleanupDumpProperties();
}
/**
* Set locale if specified on unserialize() called.
*
* Only used by PHP >= 7.4.
*
* @return void
*/
public function __unserialize(array $data): void
{
// @codeCoverageIgnoreStart
try {
$this->__construct($data['date'] ?? null, $data['timezone'] ?? null);
} catch (Throwable $exception) {
if (!isset($data['dumpDateProperties']['date'], $data['dumpDateProperties']['timezone'])) {
throw $exception;
}
try {
// FatalError occurs when calling msgpack_unpack() in PHP 7.4 or later.
['date' => $date, 'timezone' => $timezone] = $data['dumpDateProperties'];
$this->__construct($date, unserialize($timezone));
} catch (Throwable $ignoredException) {
throw $exception;
}
}
// @codeCoverageIgnoreEnd
if (isset($data['dumpLocale'])) {
$this->locale($data['dumpLocale']);
}
}
/**
* Prepare the object for JSON serialization.
*
@@ -207,11 +289,15 @@ trait Serialization
*/
public function cleanupDumpProperties()
{
foreach ($this->dumpProperties as $property) {
if (isset($this->$property)) {
unset($this->$property);
// @codeCoverageIgnoreStart
if (PHP_VERSION < 8.2) {
foreach ($this->dumpProperties as $property) {
if (isset($this->$property)) {
unset($this->$property);
}
}
}
// @codeCoverageIgnoreEnd
return $this;
}

View File

@@ -28,7 +28,7 @@ trait Test
/**
* A test Carbon instance to be returned when now instances are created.
*
* @var static
* @var Closure|static|null
*/
protected static $testNow;
@@ -59,15 +59,13 @@ trait Test
*
* /!\ Use this method for unit tests only.
*
* @param Closure|static|string|false|null $testNow real or mock Carbon instance
* @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance
*/
public static function setTestNow($testNow = null)
{
if ($testNow === false) {
$testNow = null;
}
static::$testNow = \is_string($testNow) ? static::parse($testNow) : $testNow;
static::$testNow = $testNow instanceof self || $testNow instanceof Closure
? $testNow
: static::make($testNow);
}
/**
@@ -87,7 +85,7 @@ trait Test
*
* /!\ Use this method for unit tests only.
*
* @param Closure|static|string|false|null $testNow real or mock Carbon instance
* @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance
*/
public static function setTestNowAndTimezone($testNow = null, $tz = null)
{
@@ -121,16 +119,22 @@ trait Test
*
* /!\ Use this method for unit tests only.
*
* @param Closure|static|string|false|null $testNow real or mock Carbon instance
* @param Closure|null $callback
* @template T
*
* @return mixed
* @param DateTimeInterface|Closure|static|string|false|null $testNow real or mock Carbon instance
* @param Closure(): T $callback
*
* @return T
*/
public static function withTestNow($testNow = null, $callback = null)
public static function withTestNow($testNow, $callback)
{
static::setTestNow($testNow);
$result = $callback();
static::setTestNow();
try {
$result = $callback();
} finally {
static::setTestNow();
}
return $result;
}

View File

@@ -63,7 +63,7 @@ trait Timestamp
public static function createFromTimestampMsUTC($timestamp)
{
[$milliseconds, $microseconds] = self::getIntegerAndDecimalParts($timestamp, 3);
$sign = $milliseconds < 0 || $milliseconds === 0.0 && $microseconds < 0 ? -1 : 1;
$sign = $milliseconds < 0 || ($milliseconds === 0.0 && $microseconds < 0) ? -1 : 1;
$milliseconds = abs($milliseconds);
$microseconds = $sign * abs($microseconds) + static::MICROSECONDS_PER_MILLISECOND * ($milliseconds % static::MILLISECONDS_PER_SECOND);
$seconds = $sign * floor($milliseconds / static::MILLISECONDS_PER_SECOND);
@@ -125,7 +125,7 @@ trait Timestamp
*/
public function getPreciseTimestamp($precision = 6)
{
return round($this->rawFormat('Uu') / pow(10, 6 - $precision));
return round(((float) $this->rawFormat('Uu')) / pow(10, 6 - $precision));
}
/**
@@ -182,7 +182,7 @@ trait Timestamp
$integer = 0;
$decimal = 0;
foreach (preg_split('`[^0-9.]+`', $numbers) as $chunk) {
foreach (preg_split('`[^\d.]+`', $numbers) as $chunk) {
[$integerPart, $decimalPart] = explode('.', "$chunk.");
$integer += (int) $integerPart;

View File

@@ -17,6 +17,7 @@ use Carbon\CarbonInterval;
use Carbon\Exceptions\UnitException;
use Closure;
use DateInterval;
use DateMalformedStringException;
use ReturnTypeWillChange;
/**
@@ -60,8 +61,6 @@ trait Units
case 'millisecond':
return $this->addRealUnit('microsecond', $value * static::MICROSECONDS_PER_MILLISECOND);
break;
// @call addRealUnit
case 'second':
break;
@@ -167,7 +166,7 @@ trait Units
'weekday',
];
return \in_array($unit, $modifiableUnits) || \in_array($unit, static::$units);
return \in_array($unit, $modifiableUnits, true) || \in_array($unit, static::$units, true);
}
/**
@@ -199,7 +198,7 @@ trait Units
public function add($unit, $value = 1, $overflow = null)
{
if (\is_string($unit) && \func_num_args() === 1) {
$unit = CarbonInterval::make($unit);
$unit = CarbonInterval::make($unit, [], true);
}
if ($unit instanceof CarbonConverterInterface) {
@@ -232,6 +231,8 @@ trait Units
*/
public function addUnit($unit, $value = 1, $overflow = null)
{
$originalArgs = \func_get_args();
$date = $this;
if (!is_numeric($value) || !(float) $value) {
@@ -264,7 +265,7 @@ trait Units
/** @var static $date */
$date = $date->addDays($sign);
while (\in_array($date->dayOfWeek, $weekendDays)) {
while (\in_array($date->dayOfWeek, $weekendDays, true)) {
$date = $date->addDays($sign);
}
}
@@ -274,14 +275,14 @@ trait Units
}
$timeString = $date->toTimeString();
} elseif ($canOverflow = \in_array($unit, [
} elseif ($canOverflow = (\in_array($unit, [
'month',
'year',
]) && ($overflow === false || (
$overflow === null &&
($ucUnit = ucfirst($unit).'s') &&
!($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}())
))) {
)))) {
$day = $date->day;
}
@@ -304,16 +305,21 @@ trait Units
$unit = 'second';
$value = $second;
}
$date = $date->modify("$value $unit");
if (isset($timeString)) {
$date = $date->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) {
$date = $date->modify('last day of previous month');
try {
$date = $date->modify("$value $unit");
if (isset($timeString)) {
$date = $date->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) {
$date = $date->modify('last day of previous month');
}
} catch (DateMalformedStringException $ignoredException) { // @codeCoverageIgnore
$date = null; // @codeCoverageIgnore
}
if (!$date) {
throw new UnitException('Unable to add unit '.var_export(\func_get_args(), true));
throw new UnitException('Unable to add unit '.var_export($originalArgs, true));
}
return $date;
@@ -362,7 +368,7 @@ trait Units
public function sub($unit, $value = 1, $overflow = null)
{
if (\is_string($unit) && \func_num_args() === 1) {
$unit = CarbonInterval::make($unit);
$unit = CarbonInterval::make($unit, [], true);
}
if ($unit instanceof CarbonConverterInterface) {
@@ -398,7 +404,7 @@ trait Units
public function subtract($unit, $value = 1, $overflow = null)
{
if (\is_string($unit) && \func_num_args() === 1) {
$unit = CarbonInterval::make($unit);
$unit = CarbonInterval::make($unit, [], true);
}
return $this->sub($unit, $value, $overflow);