From 873e6a3b49373909119ff45611acafabdd73b37d Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Wed, 27 Mar 2024 21:16:26 +0100 Subject: [PATCH] Make start and end period properties correct at CarbonPeriod creation --- src/Carbon/CarbonPeriod.php | 81 ++++++++++++++++++++++++------- tests/CarbonPeriod/CreateTest.php | 21 ++++++++ 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/Carbon/CarbonPeriod.php b/src/Carbon/CarbonPeriod.php index 1b75d271ef..541bef3832 100644 --- a/src/Carbon/CarbonPeriod.php +++ b/src/Carbon/CarbonPeriod.php @@ -595,7 +595,7 @@ public static function __callStatic(string $method, array $parameters): mixed */ public function __construct(...$arguments) { - $raw = ['R1/2000-01-01T00:00:00Z/P1D']; + $raw = null; if (isset($arguments['raw'])) { $raw = $arguments['raw']; @@ -608,13 +608,6 @@ public function __construct(...$arguments) $arguments = $raw; } - // Dummy construct, as properties are completely overridden - parent::__construct(...$raw); - - if (is_a($this->dateClass, DateTimeImmutable::class, true)) { - $this->options = static::IMMUTABLE; - } - // Parse and assign arguments one by one. First argument may be an ISO 8601 spec, // which will be first parsed into parts and then processed the same way. @@ -642,13 +635,18 @@ 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); + $sortedArguments['timezone'] = $argument; } elseif ($this->dateInterval === null && ( (\is_string($argument) && preg_match( @@ -661,25 +659,72 @@ 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['timezone'])) { + $this->setTimezone($sortedArguments['timezone']); + } + + if (isset($sortedArguments['options'])) { + $this->setOptions($sortedArguments['options']); + } + if ($this->startDate === null) { $dateClass = $this->dateClass; $this->setStartDate($dateClass::now()); diff --git a/tests/CarbonPeriod/CreateTest.php b/tests/CarbonPeriod/CreateTest.php index 742b4c6580..a2f6086bba 100644 --- a/tests/CarbonPeriod/CreateTest.php +++ b/tests/CarbonPeriod/CreateTest.php @@ -866,5 +866,26 @@ public function testStartAndEndFallback() 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'), + ]); } }