Repository: briannesbitt/Carbon (PHP)

Issue:
Period `start` and `end` always wrong with Carbon 3
In Carbon 3 the `start` and `end` properties of a CarbonPeriod or CarbonPeriodImmutable are always `2000-01-01` and `null`.

[Sandbox example.](https://play.phpsandbox.io/nesbot/carbon/mN7g4E1kZgKWn281)

Code to reproduce the issue:

```php
[
    Carbon::parse('Sep 1')->toPeriod('Sep 30')->start,
    Carbon::parse('Sep 1')->toPeriod('Sep 30')->end,
];
```

Carbon version: **3.1.1**

PHP version: **8.2.15**

Expected (and what Carbon 2 returns):

```php
[
    '2024-09-01 00:00:00', // Carbon\Carbon instance
    '2024-09-30 00:00:00', // Carbon\Carbon instance
];
```

Received:

```php
[
    '2000-01-01 00:00:00', // DateTime instance
    null,
];
```



Discussion: It's because now `CarbonPeriod` extends `DatePeriod` but readonly cannot be controlled from sub-class.

I recommend you use the getters:
```php
[
    Carbon::parse('Sep 1')->toPeriod('Sep 30')->getStartDate(),
    Carbon::parse('Sep 1')->toPeriod('Sep 30')->getEndDate(),
];
```
We can make it correct at creation and then `CarbonPeriodImmutable` would be compatible including start and end properties, the issue will be that for `CarbonPeriod` start and end can be changed after creation but the properties would sill contain the old values.
Thanks! 🙏🏻 



Fix (patch):
diff --git a/src/Carbon/CarbonPeriod.php b/src/Carbon/CarbonPeriod.php
index 326ef3383f..3579c1619c 100644
--- a/src/Carbon/CarbonPeriod.php
+++ b/src/Carbon/CarbonPeriod.php
@@ -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,
@@ -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',
@@ -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());
diff --git a/src/Carbon/Traits/Converter.php b/src/Carbon/Traits/Converter.php
index 5fdbca4cae..8a55b08b30 100644
--- a/src/Carbon/Traits/Converter.php
+++ b/src/Carbon/Traits/Converter.php
@@ -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,
+        );
     }
 
     /**