Mail Parser: Completely remove Webklex IMAP and all dependcies

This commit is contained in:
johnnyq
2026-06-24 13:39:07 -04:00
parent 63ad3256ee
commit 171a0d38f8
779 changed files with 6408 additions and 82971 deletions

View File

@@ -48,6 +48,13 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
protected array $directories = [];
/**
* Cache for language files.
*
* @var array<string, array>
*/
protected array $fileCache = [];
/**
* Set to true while constructing.
*/
@@ -171,7 +178,7 @@ abstract class AbstractTranslator extends SymfonyTranslator
foreach ($this->getDirectories() as $directory) {
$file = \sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale);
$data = @include $file;
$data = ($this->fileCache[$file] ??= self::loadFile($file));
if ($data !== false) {
$this->messages[$locale] = $data;
@@ -305,7 +312,7 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
public function getMessages(?string $locale = null): array
{
return $locale === null ? $this->messages : $this->messages[$locale];
return $locale === null ? $this->messages : ($this->messages[$locale] ?? []);
}
/**
@@ -315,6 +322,12 @@ abstract class AbstractTranslator extends SymfonyTranslator
*/
public function setLocale($locale): void
{
$previousLocale = $this->getLocale();
if ($previousLocale === $locale && isset($this->messages[$locale])) {
return;
}
$locale = preg_replace_callback('/[-_]([a-z]{2,}|\d{2,})/', function ($matches) {
// _2-letters or YUE is a region, _3+-letters is a variant
$upper = strtoupper($matches[1]);
@@ -326,8 +339,6 @@ abstract class AbstractTranslator extends SymfonyTranslator
return '_'.ucfirst($matches[1]);
}, strtolower($locale));
$previousLocale = $this->getLocale();
if ($previousLocale === $locale && isset($this->messages[$locale])) {
return;
}
@@ -407,7 +418,12 @@ abstract class AbstractTranslator extends SymfonyTranslator
$this->initializing = false;
}
private static function compareChunkLists($referenceChunks, $chunks)
private function loadFile(string $file): array|false
{
return file_exists($file) ? (include $file) : false;
}
private static function compareChunkLists(array $referenceChunks, array $chunks): int
{
$score = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -437,6 +437,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
$spec = $years;
$isStringSpec = (\is_string($spec) && !preg_match('/^[\d.]/', $spec));
$inverted = false;
if (!$isStringSpec || (float) $years) {
$spec = static::PERIOD_PREFIX;
@@ -463,8 +464,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
}
if ($isStringSpec && str_starts_with($spec, '-')) {
$inverted = true;
$spec = substr($spec, 1);
}
try {
parent::__construct($spec);
if ($inverted) {
$this->invert = 1;
}
} catch (Throwable $exception) {
try {
parent::__construct('PT0S');
@@ -548,8 +558,17 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
foreach (['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'] as $unit) {
if ($$unit < 0) {
$this->set($unit, $$unit);
$value = $$unit;
if (
(
\is_int($value)
|| \is_float($value)
|| (\is_string($value) && preg_match('/^[\d.-]+$/', $value))
)
&& $value < 0
) {
$this->set($unit, $value);
}
}
}
@@ -763,6 +782,42 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
return $instance;
}
public static function monthWithAnchorDay(int $day): static
{
return new static(function (CarbonInterface $date, bool $negated) use ($day) {
$next = $date->day(1)->addMonths($negated ? -1 : 1);
return $next->day(min($day, $next->daysInMonth));
});
}
public static function monthNoOverflow(): static
{
return new static(static function (CarbonInterface $date, bool $negated) {
return $negated
? $date->subMonthNoOverflow()
: $date->addMonthNoOverflow();
});
}
public static function yearWithAnchorDay(int $day): static
{
return new static(function (CarbonInterface $date, bool $negated) use ($day) {
$next = $date->day(1)->addYears($negated ? -1 : 1);
return $next->day(min($day, $next->daysInMonth));
});
}
public static function yearNoOverflow(): static
{
return new static(static function (CarbonInterface $date, bool $negated) {
return $negated
? $date->subYearNoOverflow()
: $date->addYearNoOverflow();
});
}
/**
* Return the original source used to create the current interval.
*
@@ -1117,8 +1172,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public static function diff($start, $end = null, bool $absolute = false, array $skip = []): static
{
$start = $start instanceof CarbonInterface ? $start : Carbon::make($start);
$end = $end instanceof CarbonInterface ? $end : Carbon::make($end);
$start = self::carbonOrMake($start);
$end = self::carbonOrMake($end);
$rawInterval = $start->diffAsDateInterval($end, $absolute);
$interval = static::instance($rawInterval, $skip);
@@ -2159,6 +2214,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
$class = ($params[0] ?? null) instanceof DateTime ? CarbonPeriod::class : CarbonPeriodImmutable::class;
if ($this->step) {
$dates = array_filter($params, static fn (mixed $param) => $param instanceof DateTimeInterface);
if (\count($dates) >= 2 && $dates[0] > $dates[1]) {
$this->invert();
}
}
return $class::create($this, ...$params);
}
@@ -2225,13 +2288,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
/**
* Add the passed interval to the current instance.
*
* @param string|DateInterval $unit
* @param int|float $value
* @param Unit|string|DateInterval $unit
* @param int|float $value
*
* @return $this
*/
public function add($unit, $value = 1): static
{
$this->checkNoStepIsDefined(__METHOD__);
if (is_numeric($unit)) {
[$value, $unit] = [$unit, $value];
}
@@ -2268,13 +2333,15 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
/**
* Subtract the passed interval to the current instance.
*
* @param string|DateInterval $unit
* @param int|float $value
* @param Unit|string|DateInterval $unit
* @param int|float $value
*
* @return $this
*/
public function sub($unit, $value = 1): static
{
$this->checkNoStepIsDefined(__METHOD__);
if (is_numeric($unit)) {
[$value, $unit] = [$unit, $value];
}
@@ -2326,7 +2393,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
/**
* Add given parameters to the current interval.
* Subtract given parameters to the current interval.
*
* @param int $years
* @param int $months
@@ -2372,6 +2439,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public function times($factor): static
{
$this->checkNoStepIsDefined(__METHOD__);
if ($factor < 0) {
$this->invert = $this->invert ? 0 : 1;
$factor = -$factor;
@@ -2434,6 +2503,8 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*/
public function multiply($factor): static
{
$this->checkNoStepIsDefined(__METHOD__);
if ($factor < 0) {
$this->invert = $this->invert ? 0 : 1;
$factor = -$factor;
@@ -2472,38 +2543,46 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*
* @return string
*/
public static function getDateIntervalSpec(DateInterval $interval, bool $microseconds = false, array $skip = []): string
{
public static function getDateIntervalSpec(
DateInterval $interval,
bool $microseconds = false,
array $skip = [],
bool $withNegatives = false,
): string {
$date = array_filter([
static::PERIOD_YEARS => abs($interval->y),
static::PERIOD_MONTHS => abs($interval->m),
static::PERIOD_DAYS => abs($interval->d),
static::PERIOD_YEARS => $withNegatives ? $interval->y : abs($interval->y),
static::PERIOD_MONTHS => $withNegatives ? $interval->m : abs($interval->m),
static::PERIOD_DAYS => $withNegatives ? $interval->d : abs($interval->d),
]);
$skip = array_map([Unit::class, 'toNameIfUnit'], $skip);
$days = abs((int) $interval->days);
if (
$interval->days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH &&
$days >= CarbonInterface::DAYS_PER_WEEK * CarbonInterface::WEEKS_PER_MONTH &&
(!isset($date[static::PERIOD_YEARS]) || \count(array_intersect(['y', 'year', 'years'], $skip))) &&
(!isset($date[static::PERIOD_MONTHS]) || \count(array_intersect(['m', 'month', 'months'], $skip)))
) {
$date = [
static::PERIOD_DAYS => abs($interval->days),
static::PERIOD_DAYS => $withNegatives ? $interval->days : $days,
];
}
$seconds = abs($interval->s);
if ($microseconds && $interval->f > 0) {
$seconds = \sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000);
$seconds = $withNegatives ? $interval->s : abs($interval->s);
if ($microseconds && $interval->f !== 0.0) {
$seconds = $withNegatives
? number_format($seconds + $interval->f, 6, '.', '')
: \sprintf('%d.%06d', $seconds, abs($interval->f) * 1000000);
}
$time = array_filter([
static::PERIOD_HOURS => abs($interval->h),
static::PERIOD_MINUTES => abs($interval->i),
static::PERIOD_HOURS => $withNegatives ? $interval->h : abs($interval->h),
static::PERIOD_MINUTES => $withNegatives ? $interval->i : abs($interval->i),
static::PERIOD_SECONDS => $seconds,
]);
$specString = static::PERIOD_PREFIX;
$specString = ($withNegatives && $interval->invert ? '-' : '').static::PERIOD_PREFIX;
foreach ($date as $key => $value) {
$specString .= $value.$key;
@@ -2511,6 +2590,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
if (\count($time) > 0) {
$specString .= static::PERIOD_TIME_PREFIX;
foreach ($time as $key => $value) {
$specString .= $value.$key;
}
@@ -2524,9 +2604,9 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
*
* @return string
*/
public function spec(bool $microseconds = false): string
public function spec(bool $microseconds = false, bool $withNegatives = false): string
{
return static::getDateIntervalSpec($this, $microseconds);
return static::getDateIntervalSpec($this, $microseconds, [], $withNegatives);
}
/**
@@ -2981,6 +3061,12 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
)->invert($inverted)->cascade());
}
$initiallyInverted = (bool) $this->invert;
if ($initiallyInverted) {
$this->invert();
}
$base = CarbonImmutable::parse('2000-01-01 00:00:00', 'UTC')
->roundUnit($unit, $precision, $function);
$next = $base->add($this);
@@ -2996,7 +3082,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
->diff($base),
);
return $this->invert($inverted);
return $this->invert($initiallyInverted xor $inverted);
}
/**
@@ -3514,6 +3600,13 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface, U
}
}
private static function carbonOrMake(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: Carbon::make($dateTime);
}
private static function incrementUnit(DateInterval $instance, string $unit, int $value): void
{
if ($value === 0) {

View File

@@ -426,6 +426,96 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
return self::createFromIso($iso, $options);
}
public static function monthly(
DateTimeInterface|string|int|null $start = null,
DateTimeInterface|string|int|null $end = null,
?int $recurrences = null,
?int $anchorDay = null,
OverflowMode $mode = OverflowMode::AnchorDay,
?int $options = null,
): static {
if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name,
);
}
if ($end !== null && $recurrences !== null) {
throw new InvalidArgumentException(
'You must specify $end or $recurrences but not both',
);
}
if (\is_int($start)) {
$start = CarbonImmutable::createFromTimestamp($start);
} elseif (\is_string($start)) {
$start = CarbonImmutable::parse($start);
}
$start ??= CarbonImmutable::now();
if (\is_int($end)) {
$end = CarbonImmutable::createFromTimestamp($end);
}
return (new static(
$start,
match ($mode) {
OverflowMode::AnchorDay => CarbonInterval::monthWithAnchorDay(
$anchorDay ?? $start->day,
),
OverflowMode::NoOverflow => CarbonInterval::monthNoOverflow(),
OverflowMode::Overflow => CarbonInterval::month(),
},
$end ?? $recurrences,
))->setOptions($options ?? self::IMMUTABLE);
}
public static function yearly(
DateTimeInterface|string|int|null $start = null,
DateTimeInterface|string|int|null $end = null,
?int $recurrences = null,
?int $anchorDay = null,
OverflowMode $mode = OverflowMode::AnchorDay,
?int $options = null,
): static {
if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name,
);
}
if ($end !== null && $recurrences !== null) {
throw new InvalidArgumentException(
'You must specify $end or $recurrences but not both',
);
}
if (\is_int($start)) {
$start = CarbonImmutable::createFromTimestamp($start);
} elseif (\is_string($start)) {
$start = CarbonImmutable::parse($start);
}
$start ??= CarbonImmutable::now();
if (\is_int($end)) {
$end = CarbonImmutable::createFromTimestamp($end);
}
return (new static(
$start,
match ($mode) {
OverflowMode::AnchorDay => CarbonInterval::yearWithAnchorDay(
$anchorDay ?? $start->day,
),
OverflowMode::NoOverflow => CarbonInterval::yearNoOverflow(),
OverflowMode::Overflow => CarbonInterval::month(),
},
$end ?? $recurrences,
))->setOptions($options ?? self::IMMUTABLE);
}
/**
* Return whether the given interval contains non-zero value of any time unit.
*/
@@ -695,20 +785,6 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
}
}
if ($raw === null && isset($sortedArguments['start'])) {
$end = $sortedArguments['end'] ?? max(1, $sortedArguments['recurrences'] ?? 1);
if (\is_float($end)) {
$end = $end === INF ? PHP_INT_MAX : (int) round($end);
}
$raw = [
$sortedArguments['start'],
$sortedArguments['interval'] ?? CarbonInterval::day(),
$end,
];
}
$this->setFromAssociativeArray($sortedArguments);
if ($this->startDate === null) {
@@ -1250,13 +1326,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$self = $this->copyIfImmutable();
$self->carbonRecurrences = $recurrences === INF ? INF : (int) $recurrences;
if (!$self->hasFilter(static::RECURRENCES_FILTER)) {
return $self->addFilter(static::RECURRENCES_FILTER);
}
$self->handleChangedParameters();
return $self;
return self::addFilterOrHandleChangedParameters($self, static::RECURRENCES_FILTER);
}
/**
@@ -1282,6 +1352,8 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
}
$self->syncNativePeriod();
return $self;
}
@@ -1301,22 +1373,38 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
throw new InvalidPeriodDateException('Invalid end date.');
}
if (!$date) {
return $this->removeFilter(static::END_DATE_FILTER);
}
// ::make() is responsible for converting strings to DateTimeInterface objects
\assert(!\is_string($date));
$self = $this->copyIfImmutable();
if (!$date) {
$self = $self->removeFilter(static::END_DATE_FILTER);
$self->syncNativePeriod();
return $self;
}
\assert($date instanceof DateTimeInterface);
$self->endDate = $date;
if (
$self->startDate !== null
&& $self->dateInterval !== null
&& !$self->dateInterval->invert
&& $self->startDate > $self->endDate
) {
$self->dateInterval->invert = 1;
}
if ($inclusive !== null) {
$self = $self->toggleOptions(static::EXCLUDE_END_DATE, !$inclusive);
}
if (!$self->hasFilter(static::END_DATE_FILTER)) {
return $self->addFilter(static::END_DATE_FILTER);
}
$self = self::addFilterOrHandleChangedParameters($self, static::END_DATE_FILTER);
$self->handleChangedParameters();
$self->syncNativePeriod();
return $self;
}
@@ -2286,11 +2374,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
'options' => $this->setOptions(...),
'recurrences' => $this->setRecurrences(...),
'current' => function (mixed $current): void {
if (!($current instanceof CarbonInterface)) {
$current = $this->resolveCarbon($current);
}
$this->carbonCurrent = $current;
$this->carbonCurrent = $this->carbonOrResolve($current);
},
'start' => 'startDate',
'interval' => $this->setDateInterval(...),
@@ -2409,7 +2493,7 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
return true;
}
if ($this->dateInterval->invert ? $current > $this->endDate : $current < $this->endDate) {
if ($this->dateInterval->invert ? ($current > $this->endDate) : ($current < $this->endDate)) {
return true;
}
@@ -2440,6 +2524,28 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$this->validationResult = null;
}
/**
* Synchronize the native DatePeriod properties with the current state.
*/
protected function syncNativePeriod(): void
{
if (\PHP_VERSION_ID < 80200) {
return; // @codeCoverageIgnore
}
// Default interval if not set (matches __construct logic)
$interval = $this->dateInterval ?? \Carbon\CarbonInterval::day();
// Reinitialize the parent DatePeriod to update $start, $end, etc.
// This mirrors the logic in __construct and initializeSerialization.
parent::__construct(
$this->startDate,
$interval,
$this->endDate ?? max(1, min(2147483639, $this->recurrences ?? 1)),
$this->options ?? 0,
);
}
/**
* Validate current date and stop iteration when necessary.
*
@@ -2510,7 +2616,10 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
$attempts = 0;
do {
$this->carbonCurrent = $this->carbonCurrent->add($this->dateInterval);
$this->carbonCurrent = $this->carbonCurrent->add(
$this->dateInterval,
$this->dateInterval->getStep() && $this->dateInterval->invert ? -1 : 1,
);
$this->validationResult = null;
@@ -2563,6 +2672,13 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
: static::create($period, ...$arguments);
}
private function carbonOrResolve(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: $this->resolveCarbon($dateTime);
}
private function orderCouple($first, $second): array
{
return $first > $second ? [$second, $first] : [$first, $second];
@@ -2715,4 +2831,17 @@ class CarbonPeriod extends DatePeriodBase implements Countable, JsonSerializable
);
// @codeCoverageIgnoreEnd
}
private static function addFilterOrHandleChangedParameters(
self $period,
array|callable|string $filter,
): self {
if (!$period->hasFilter($filter)) {
return $period->addFilter($filter);
}
$period->handleChangedParameters();
return $period;
}
}

View File

@@ -16,33 +16,40 @@ namespace Carbon\Exceptions;
use RuntimeException as BaseRuntimeException;
use Throwable;
/**
* @final
*/
class ImmutableException extends BaseRuntimeException implements RuntimeException
{
/**
* The value.
*
* @var string
*/
protected $value;
protected string $value;
/**
* Constructor.
*
* @param string $value the immutable type/value
* @param string $value the immutable message
* @param int $code
* @param Throwable|null $previous
*
* @deprecated Use ImmutableException::fromClass() or ImmutableException::fromMethod() to construct an
* ImmutableException instance.
*/
public function __construct($value, $code = 0, ?Throwable $previous = null)
public function __construct($message, int $code = 0, ?Throwable $previous = null, ?string $value = null)
{
$this->value = $value;
parent::__construct("$value is immutable.", $code, $previous);
$this->value ??= $value ?? $message;
parent::__construct($message, $code, $previous);
}
public static function fromClass(string $class, int $code = 0, ?Throwable $previous = null): self
{
return new self("$class class is immutable.", $code, $previous, "$class class");
}
public static function fromMethod(string $class, string $method, int $code = 0, ?Throwable $previous = null): self
{
return new self("$method not allowed on $class.", $code, $previous, "$class::$method method");
}
/**
* Get the value.
*
* @return string
*/
public function getValue(): string
{
return $this->value;

View File

@@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Carbon;
use Closure;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
@@ -70,7 +71,7 @@ use Throwable;
* the types of objects that can be built, for instance:
* @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales.
* (It will ignore custom translator dynamic loading.)
* @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* name, region and variant of the locale.
* @method array getDays() Get the days of the week.
* @method ?string getFallbackLocale() Get the fallback locale.
@@ -553,16 +554,13 @@ class Factory
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
* - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
* - When a string containing the desired time is passed to Carbon::parse().
*
* Note the timezone parameter was left out of the examples above and
* has no affect as the mock value will be returned regardless of its value.
*
* Only the moment is mocked with setTestNow(), the timezone will still be the one passed
* as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()).
*
@@ -576,14 +574,14 @@ class Factory
public function setTestNow(mixed $testNow = null): void
{
$this->useTimezoneFromTestNow = false;
$this->testNow = $testNow instanceof self || $testNow instanceof Closure
$this->testNow = $testNow instanceof Closure
? $testNow
: $this->make($testNow);
}
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
@@ -696,11 +694,21 @@ class Factory
}
if (!($testNow instanceof CarbonInterface)) {
$timezone ??= $this->useTimezoneFromTestNow ? $testNow->getTimezone() : null;
$timezone ??= $this->useTimezoneFromTestNow
? $testNow->getTimezone()
: new CarbonTimeZone(date_default_timezone_get());
$testNow = $this->__call('instance', [$testNow, $timezone]);
}
}
if ($testNow !== null && $timezone === null) {
if ($testNow instanceof DateTime) {
$testNow = clone $testNow;
}
$testNow = $testNow->setTimezone(date_default_timezone_get());
}
return $testNow;
}

View File

@@ -67,7 +67,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
* the types of objects that can be built, for instance:
* @method array getAvailableLocales() Returns the list of internally available locales and already loaded custom locales.
* (It will ignore custom translator dynamic loading.)
* @method Language[] getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* @method array getAvailableLocalesInfo() Returns list of Language object for each available locale. This object allow you to get the ISO name, native
* name, region and variant of the locale.
* @method array getDays() Get the days of the week.
* @method ?string getFallbackLocale() Get the fallback locale.

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Carbon;
enum OverflowMode
{
case AnchorDay;
case NoOverflow;
case Overflow;
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Carbon\Traits;
use BadMethodCallException;
use Carbon\Callback;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
@@ -28,12 +29,12 @@ trait IntervalStep
*
* @var Closure|null
*/
protected $step;
protected ?Closure $step = null;
/**
* Get the dynamic step in use.
*
* @return Closure
* @return Closure|null
*/
public function getStep(): ?Closure
{
@@ -64,8 +65,7 @@ trait IntervalStep
*/
public function convertDate(DateTimeInterface $dateTime, bool $negated = false): CarbonInterface
{
/** @var CarbonInterface $carbonDate */
$carbonDate = $dateTime instanceof CarbonInterface ? $dateTime : $this->resolveCarbon($dateTime);
$carbonDate = $this->carbonOrResolve($dateTime);
if ($this->step) {
$carbonDate = Callback::parameter($this->step, $carbonDate->avoidMutation());
@@ -91,4 +91,23 @@ trait IntervalStep
return Carbon::instance($dateTime);
}
private function carbonOrResolve(mixed $dateTime): CarbonInterface
{
return $dateTime instanceof CarbonInterface
? $dateTime
: $this->resolveCarbon($dateTime);
}
private function checkNoStepIsDefined(string $method): void
{
if ($this->step !== null) {
$chunks = explode('::', $method, 2);
$method = $chunks[1] ?? $method;
throw new BadMethodCallException(
"->$method() cannot be called on an interval with a step",
);
}
}
}

View File

@@ -345,7 +345,7 @@ trait Localization
* @param string|null $locale
* @param string ...$fallbackLocales
*
* @return $this|string
* @return ($locale is null ? string : static)
*/
public function locale(?string $locale = null, string ...$fallbackLocales): static|string
{
@@ -407,7 +407,16 @@ trait Localization
$translator = static::getTranslator();
if (method_exists($translator, 'setFallbackLocales')) {
$translator->setFallbackLocales([$locale]);
$fallbackLocales = [$locale];
if (
method_exists($translator, 'getFallbackLocales')
&& $fallbackLocales === $translator->getFallbackLocales()
) {
return;
}
$translator->setFallbackLocales($fallbackLocales);
if ($translator instanceof Translator) {
$preferredLocale = $translator->getLocale();

View File

@@ -30,16 +30,13 @@ trait Test
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)
* - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now')
* - When a string containing the desired time is passed to Carbon::parse().
*
* Note the timezone parameter was left out of the examples above and
* has no affect as the mock value will be returned regardless of its value.
*
* Only the moment is mocked with setTestNow(), the timezone will still be the one passed
* as parameter of date_default_timezone_get() as a fallback (see setTestNowAndTimezone()).
*
@@ -57,7 +54,7 @@ trait Test
/**
* Set a Carbon instance (real or mock) to be returned when a "now"
* instance is created. The provided instance will be returned
* instance is created. The provided instance will be returned
* specifically under the following conditions:
* - A call to the static now() method, ex. Carbon::now()
* - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null)

View File

@@ -20,10 +20,12 @@ use Carbon\Exceptions\InvalidFormatException;
use Carbon\Exceptions\InvalidIntervalException;
use Carbon\Exceptions\UnitException;
use Carbon\Exceptions\UnsupportedUnitException;
use Carbon\OverflowMode;
use Carbon\Unit;
use Closure;
use DateInterval;
use DateMalformedStringException;
use InvalidArgumentException;
use ReturnTypeWillChange;
/**
@@ -244,12 +246,13 @@ trait Units
*
* @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
#[ReturnTypeWillChange]
public function add($unit, $value = 1, ?bool $overflow = null): static
public function add($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
$unit = Unit::toNameIfUnit($unit);
$value = Unit::toNameIfUnit($value);
@@ -263,7 +266,14 @@ trait Units
}
if ($unit instanceof Closure) {
$result = $this->resolveCarbon($unit($this, false));
$inverted = ($value < 0);
$result = $this;
self::disallowDecimalPart($value);
for ($i = abs($value); $i > 0; $i--) {
$result = $result->resolveCarbon($unit($result, $inverted));
}
if ($this !== $result && $this->isMutable()) {
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
@@ -280,16 +290,22 @@ trait Units
[$value, $unit] = [$unit, $value];
}
return $this->addUnit((string) $unit, $value, $overflow);
return $this->addUnit((string) $unit, $value, $overflow, $anchorDay);
}
/**
* Add given units to the current instance.
*/
public function addUnit(Unit|string $unit, $value = 1, ?bool $overflow = null): static
{
public function addUnit(
Unit|string $unit,
$value = 1,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
$unit = Unit::toName($unit);
$overflow = $this->getOverflowMode($overflow, $anchorDay);
$originalArgs = \func_get_args();
$date = $this;
@@ -311,8 +327,13 @@ trait Units
$value *= $factor;
}
if ($overflow === OverflowMode::AnchorDay) {
$anchorDay ??= $date->day;
$overflow = OverflowMode::NoOverflow;
}
if ($unit === 'weekday') {
$weekendDays = $this->transmitFactory(static fn () => static::getWeekendDays());
$weekendDays = $this->transmitFactory(static::getWeekendDays(...));
if ($weekendDays !== [static::SATURDAY, static::SUNDAY]) {
$absoluteValue = abs($value);
@@ -337,10 +358,9 @@ trait Units
} elseif ($canOverflow = (\in_array($unit, [
'month',
'year',
]) && ($overflow === false || (
]) && ($overflow === OverflowMode::NoOverflow || (
$overflow === null &&
($ucUnit = ucfirst($unit).'s') &&
!($this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}())
!$this->shouldUnitOverflow($unit)
)))) {
$day = $date->day;
}
@@ -355,10 +375,16 @@ trait Units
try {
$date = self::rawAddUnit($date, $unit, $value);
if (isset($timeString)) {
$date = $date?->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date?->day) {
$date = $date?->modify('last day of previous month');
if ($date !== null) {
if (isset($timeString)) {
$date = $date->setTimeFromTimeString($timeString);
} elseif (isset($canOverflow, $day) && $canOverflow && $day !== $date->day) {
$date = $date->modify('last day of previous month');
}
if ($anchorDay !== null) {
$date = $date->setAnchorDay($anchorDay);
}
}
} catch (DateMalformedStringException|InvalidFormatException|UnsupportedUnitException $exception) {
$date = null;
@@ -374,9 +400,13 @@ trait Units
/**
* Subtract given units to the current instance.
*/
public function subUnit(Unit|string $unit, $value = 1, ?bool $overflow = null): static
{
return $this->addUnit($unit, -$value, $overflow);
public function subUnit(
Unit|string $unit,
$value = 1,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->addUnit($unit, -$value, $overflow, $anchorDay);
}
/**
@@ -396,12 +426,13 @@ trait Units
*
* @param Unit|int|string|DateInterval|Closure|CarbonConverterInterface $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
#[ReturnTypeWillChange]
public function sub($unit, $value = 1, ?bool $overflow = null): static
public function sub($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
$unit = Unit::toNameIfUnit($unit);
$value = Unit::toNameIfUnit($value);
@@ -415,7 +446,14 @@ trait Units
}
if ($unit instanceof Closure) {
$result = $this->resolveCarbon($unit($this, true));
$inverted = ($value < 0);
$result = $this;
self::disallowDecimalPart($value);
for ($i = abs($value); $i > 0; $i--) {
$result = $result->resolveCarbon($unit($result, !$inverted));
}
if ($this !== $result && $this->isMutable()) {
return $this->modify($result->rawFormat('Y-m-d H:i:s.u e O'));
@@ -432,7 +470,7 @@ trait Units
[$value, $unit] = [$unit, $value];
}
return $this->addUnit((string) $unit, -(float) $value, $overflow);
return $this->addUnit((string) $unit, -(float) $value, $overflow, $anchorDay);
}
/**
@@ -442,31 +480,163 @@ trait Units
*
* @param Unit|int|string|DateInterval $unit
* @param Unit|int|float|string $value
* @param bool|null $overflow
* @param OverflowMode|bool|null $overflow
* @param int|null $anchorDay
*
* @return static
*/
public function subtract($unit, $value = 1, ?bool $overflow = null): static
public function subtract($unit, $value = 1, OverflowMode|bool|null $overflow = null, ?int $anchorDay = null): static
{
if (\is_string($unit) && \func_num_args() === 1) {
$unit = CarbonInterval::make($unit, [], true);
}
return $this->sub($unit, $value, $overflow);
return $this->sub($unit, $value, $overflow, $anchorDay);
}
/**
* Add given amount of time to the current date.
*
* @SuppressWarnings(ExcessiveParameterList)
*/
private function doPlus(
int $years = 0,
int $months = 0,
int|float $weeks = 0,
int|float $days = 0,
int|float $hours = 0,
int|float $minutes = 0,
int|float $seconds = 0,
int|float $microseconds = 0,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->addUnit(Unit::Year, $years, $overflow, $anchorDay)
->addUnit(Unit::Month, $months, $overflow, $anchorDay)
->add("
$weeks weeks $days days
$hours hours $minutes minutes $seconds seconds $microseconds microseconds
");
}
/**
* Subtract given amount of time to the current date.
*
* @SuppressWarnings(ExcessiveParameterList)
*/
private function doMinus(
int $years = 0,
int $months = 0,
int|float $weeks = 0,
int|float $days = 0,
int|float $hours = 0,
int|float $minutes = 0,
int|float $seconds = 0,
int|float $microseconds = 0,
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): static {
return $this->subUnit(Unit::Year, $years, $overflow, $anchorDay)
->subUnit(Unit::Month, $months, $overflow, $anchorDay)
->sub("
$weeks weeks $days days
$hours hours $minutes minutes $seconds seconds $microseconds microseconds
");
}
private function callPlusOrMinus(string $method, array $parameters): ?static
{
return match ($method) {
'plus' => $this->doPlus(...$parameters),
'minus' => $this->doMinus(...$parameters),
default => null,
};
}
private static function rawAddUnit(self $date, string $unit, int|float $value): ?static
{
try {
$absoluteValue = abs($value);
return $date->rawAdd(
CarbonInterval::fromString(abs($value)." $unit")->invert($value < 0),
CarbonInterval::fromString(self::getNumberAsString($absoluteValue)." $unit")
->invert($value < 0),
);
} catch (InvalidIntervalException $exception) {
try {
return $date->modify("$value $unit");
return $date->modify(self::getNumberAsString($value)." $unit");
} catch (InvalidFormatException) {
throw new UnsupportedUnitException($unit, previous: $exception);
}
}
}
private static function getNumberAsString(int|float $value): string
{
$stringValue = (string) $value;
if ($value < -1 || $value > 1) {
if (str_contains($stringValue, 'E')) {
return number_format($value, 0, '.', '');
}
return $stringValue;
}
if (str_contains($stringValue, 'E')) {
return number_format($value, 14, '.', '');
}
return $stringValue;
}
/**
* Set current day of the instance to the passed value if it exits in the
* current month, else set current day to the last day of the month.
*/
public function setAnchorDay(int $anchorDay): static
{
if ($anchorDay < 1) {
throw new InvalidArgumentException('$anchorDay must be greater than 0');
}
return $this->day(min($anchorDay, $this->daysInMonth));
}
private static function disallowDecimalPart(mixed $value): void
{
if (((float) $value) !== ((float) (int) $value)) {
throw new InvalidArgumentException(
'Interval objects cannot be multiplied by a non-integer value.',
);
}
}
private function getOverflowMode(
OverflowMode|bool|null $overflow = null,
?int $anchorDay = null,
): ?OverflowMode {
if ($anchorDay !== null) {
$overflow ??= OverflowMode::AnchorDay;
if ($overflow !== OverflowMode::AnchorDay) {
throw new InvalidArgumentException(
'$anchorDay can be set only $overflow = OverflowMode::AnchorDay',
);
}
}
return match ($overflow) {
true => OverflowMode::Overflow,
false => OverflowMode::NoOverflow,
default => $overflow,
};
}
private function shouldUnitOverflow(string $unit): bool
{
$ucUnit = ucfirst($unit).'s';
return $this->{'local'.$ucUnit.'Overflow'} ?? static::{'shouldOverflow'.$ucUnit}();
}
}

View File

@@ -94,7 +94,7 @@ class TranslatorImmutable extends Translator
private function disallowMutation($method)
{
if ($this->constructed) {
throw new ImmutableException($method.' not allowed on '.static::class);
throw ImmutableException::fromMethod(static::class, $method);
}
}
}