Skip to content

Commit

Permalink
Merge pull request #2984 from briannesbitt/feature/period-start-and-end
Browse files Browse the repository at this point in the history
Make start and end period properties correct when created from Carbon object
  • Loading branch information
kylekatarnls committed Mar 27, 2024
2 parents 6bfea14 + aa0ed85 commit b4272c2
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 24 deletions.
82 changes: 68 additions & 14 deletions src/Carbon/CarbonPeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -595,11 +595,17 @@ public static function __callStatic(string $method, array $parameters): mixed
*/
public function __construct(...$arguments)
{
// Dummy construct, as properties are completely overridden
parent::__construct('R1/2000-01-01T00:00:00Z/P1D');
$raw = null;

if (is_a($this->dateClass, DateTimeImmutable::class, true)) {
$this->options = static::IMMUTABLE;
if (isset($arguments['raw'])) {
$raw = $arguments['raw'];
$this->isDefaultInterval = $arguments['isDefaultInterval'] ?? false;

if (isset($arguments['dateClass'])) {
$this->dateClass = $arguments['dateClass'];
}

$arguments = $raw;
}

// Parse and assign arguments one by one. First argument may be an ISO 8601 spec,
Expand Down Expand Up @@ -629,14 +635,19 @@ public function __construct(...$arguments)
}
}

if (is_a($this->dateClass, DateTimeImmutable::class, true)) {
$this->options = static::IMMUTABLE;
}

$optionsSet = false;
$sortedArguments = [];

foreach ($arguments as $argument) {
$parsedDate = null;

if ($argument instanceof DateTimeZone) {
$this->setTimezone($argument);
} elseif ($this->dateInterval === null &&
} elseif (!isset($sortedArguments['interval']) &&
(
(\is_string($argument) && preg_match(
'/^(-?\d(\d(?![\/-])|[^\d\/-]([\/-])?)*|P[T\d].*|(?:\h*\d+(?:\.\d+)?\h*[a-z]+)+)$/i',
Expand All @@ -648,25 +659,68 @@ public function __construct(...$arguments)
) &&
$parsedInterval = self::makeInterval($argument)
) {
$this->setDateInterval($parsedInterval);
} elseif ($this->startDate === null && $parsedDate = $this->makeDateTime($argument)) {
$this->setStartDate($parsedDate);
} elseif ($this->endDate === null && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) {
$this->setEndDate($parsedDate);
} elseif ($this->carbonRecurrences === null &&
$this->endDate === null &&
$sortedArguments['interval'] = $parsedInterval;
} elseif (!isset($sortedArguments['start']) && $parsedDate = $this->makeDateTime($argument)) {
$sortedArguments['start'] = $parsedDate;
} elseif (!isset($sortedArguments['end']) && ($parsedDate = $parsedDate ?? $this->makeDateTime($argument))) {
$sortedArguments['end'] = $parsedDate;
} elseif (!isset($sortedArguments['recurrences']) &&
!isset($sortedArguments['end']) &&
(\is_int($argument) || \is_float($argument))
&& $argument >= 0
) {
$this->setRecurrences($argument);
$sortedArguments['recurrences'] = $argument;
} elseif (!$optionsSet && (\is_int($argument) || $argument === null)) {
$optionsSet = true;
$this->setOptions(((int) $this->options) | ((int) $argument));
$sortedArguments['options'] = (((int) $this->options) | ((int) $argument));
} else {
throw new InvalidPeriodParameterException('Invalid constructor parameters.');
}
}

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,
];
}

if ($raw === null && \is_string($arguments[0] ?? null) && substr_count($arguments[0], '/') >= 1) {
$raw = [$arguments[0]];
}

$raw ??= ['R1/2000-01-01T00:00:00Z/P1D'];

// Dummy construct, as properties are completely overridden
parent::__construct(...$raw);

if (isset($sortedArguments['start'])) {
$this->setStartDate($sortedArguments['start']);
}

if (isset($sortedArguments['end'])) {
$this->setEndDate($sortedArguments['end']);
}

if (isset($sortedArguments['recurrences'])) {
$this->setRecurrences($sortedArguments['recurrences']);
}

if (isset($sortedArguments['interval'])) {
$this->setDateInterval($sortedArguments['interval']);
}

if (isset($sortedArguments['options'])) {
$this->setOptions($sortedArguments['options']);
}

if ($this->startDate === null) {
$dateClass = $this->dateClass;
$this->setStartDate($dateClass::now());
Expand Down
24 changes: 14 additions & 10 deletions src/Carbon/Traits/Converter.php
Original file line number Diff line number Diff line change
Expand Up @@ -519,21 +519,25 @@ public function toPeriod($end = null, $interval = null, $unit = null): CarbonPer
$interval = CarbonInterval::make("$interval ".static::pluralUnit($unit));
}

$period = ($this->isMutable() ? new CarbonPeriod() : new CarbonPeriodImmutable())
->setDateClass(static::class)
->setStartDate($this);
$isDefaultInterval = !$interval;
$interval ??= CarbonInterval::day();
$class = $this->isMutable() ? CarbonPeriod::class : CarbonPeriodImmutable::class;

if ($interval) {
$period = $period->setDateInterval($interval);
if (\is_int($end) || (\is_string($end) && ctype_digit($end))) {
$end = (int) $end;
}

if (\is_int($end) || (\is_string($end) && ctype_digit($end))) {
$period = $period->setRecurrences($end);
} elseif ($end) {
$period = $period->setEndDate($end);
$end ??= 1;

if (!\is_int($end)) {
$end = $this->resolveCarbon($end);
}

return $period;
return new $class(
raw: [$this, $interval, $end],
dateClass: static::class,
isDefaultInterval: $isDefaultInterval,
);
}

/**
Expand Down
32 changes: 32 additions & 0 deletions tests/CarbonPeriod/CreateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -856,4 +856,36 @@ public function testEnums()

$this->assertSame(2, $period->count());
}

public function testStartAndEndFallback()
{
$this->assertSame([
'2024-09-01',
'2024-09-30',
], [
Carbon::parse('Sep 1')->toPeriod('Sep 30')->start->format('Y-m-d'),
Carbon::parse('Sep 1')->toPeriod('Sep 30')->end->format('Y-m-d'),
]);

$periodClass = static::$periodClass;
$period = new $periodClass('Sep 1', 'Sep 30');

$this->assertSame([
'2024-09-01',
'2024-09-30',
], [
$period->start->format('Y-m-d'),
$period->end->format('Y-m-d'),
]);

$period = new $periodClass('Sep 1');

$this->assertSame([
'2024-09-01',
null,
], [
$period->start->format('Y-m-d'),
$period->end?->format('Y-m-d'),
]);
}
}

0 comments on commit b4272c2

Please sign in to comment.