Repository: briannesbitt/Carbon (php)

Issue:
Invalid CarbonInterval->format() results for Carbon 3 (unknown).
<!--
    🛑 DON'T REMOVE ME.
    This issue template applies to all
      - bug reports,
      - feature proposals,
      - and documentation requests

    Having all those information will allow us to know exactly
    what you expect and answer you faster and precisely (answer
    that matches your Carbon version, PHP version and usage).
    
    Note: Comments between <!- - and - -> won't appear in the final
    issue (See [Preview] tab).
-->
Hello,

I encountered an issue with the following code:
<!--
    ⚠️ Your code below (not the code from inside Carbon library)
    Be sure it's dependency-free and enough to reproduce the issue
    when we run it on:
    https://play.phpsandbox.io/embed/nesbot/carbon
-->
```php
$past = new \DateTime('-3 Days');
$today = new \DateTime('today');
$interval = $today->diff($past);

var_dump($interval->format('%a')); // string(1) "2"

$past = new CarbonImmutable('-3 Days');
$today = new CarbonImmutable('today');
$interval = $today->diff($past);

var_dump($interval->format('%a')); // string(9) "(unknown)"
```

Carbon version: 3.0.0 to 3.3.0 (Carbon v2 returned correct values)
<!--
    ⚠️ Run the command `composer show nesbot/carbon` to get "versions"
-->

PHP version: 8.2

<!--
    Use `echo phpversion();`
    to get PHP version.

    Some issues can depends on your context, settings,
    macros, timezone, language. So to be sure the code
    you give is enough to reproduce your bug, try it
    first in:
    https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Aecho%20Carbon%3A%3Aparse('2018-06-01')-%3EsubDay()-%3Emonth%3B

    You can use the [Options] button to change the version
    then when you get the bug with this editor, you can use
    the [Export] button, copy the link of the opened tab,
    then paste it in the issue. Then we can immediately get
    your issue.
-->


I expected to get:

```
Valid number of days: 2 in previous example.
```
<!--
    Always give your expectations. Each use has their owns.
    You may want daylight saving time to be taken into account,
    someone else want it to be ignored. You may want timezone
    to be used in comparisons, someone else may not, etc.
-->

But I actually get: 

```
(unknown)
```
<!--
    If you did not succeed to get the same result in
    https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Aecho%20Carbon%3A%3Aparse('2018-06-01')%3B then precise the
    result you get.
-->

Thanks!


Pull request description:
Support %a format
Fix #3003

Patch:
diff --git a/src/Carbon/CarbonInterval.php b/src/Carbon/CarbonInterval.php
index dcfcaa72..d6373e24 100644
--- a/src/Carbon/CarbonInterval.php
+++ b/src/Carbon/CarbonInterval.php
@@ -289,6 +289,11 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
      */
     protected ?CarbonInterface $endDate = null;
 
+    /**
+     * End date if interval was created from a difference between 2 dates.
+     */
+    protected ?DateInterval $rawInterval = null;
+
     protected ?array $initialValues = null;
 
     /**
@@ -303,12 +308,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
             $this->startDate = $this->startDate
                 ->avoidMutation()
                 ->setTimezone($timezone);
+            $this->rawInterval = null;
         }
 
         if ($this->endDate) {
             $this->endDate = $this->endDate
                 ->avoidMutation()
                 ->setTimezone($timezone);
+            $this->rawInterval = null;
         }
 
         return $this;
@@ -326,12 +333,14 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
             $this->startDate = $this->startDate
                 ->avoidMutation()
                 ->shiftTimezone($timezone);
+            $this->rawInterval = null;
         }
 
         if ($this->endDate) {
             $this->endDate = $this->endDate
                 ->avoidMutation()
                 ->shiftTimezone($timezone);
+            $this->rawInterval = null;
         }
 
         return $this;
@@ -781,6 +790,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
         $this->originalInput = null;
         $this->startDate = null;
         $this->endDate = null;
+        $this->rawInterval = null;
 
         return $this;
     }
@@ -1091,8 +1101,10 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
     {
         $start = $start instanceof CarbonInterface ? $start : Carbon::make($start);
         $end = $end instanceof CarbonInterface ? $end : Carbon::make($end);
-        $interval = static::instance($start->diffAsDateInterval($end, $absolute), $skip);
+        $rawInterval = $start->diffAsDateInterval($end, $absolute);
+        $interval = static::instance($rawInterval, $skip);
 
+        $interval->rawInterval = $rawInterval;
         $interval->startDate = $start;
         $interval->endDate = $end;
         $interval->initialValues = $interval->getInnerValues();
@@ -2042,6 +2054,19 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
         return $this->translate($transId, array_merge($time, $interpolations, $time), null, $translator);
     }
 
+    public function format(string $format): string
+    {
+        $output = parent::format($format);
+
+        if (!str_contains($format, '%a') || !isset($this->startDate, $this->endDate)) {
+            return $output;
+        }
+
+        $this->rawInterval ??= $this->startDate->diffAsDateInterval($this->endDate);
+
+        return str_replace('(unknown)', $this->rawInterval->format('%a'), $output);
+    }
+
     /**
      * Format the instance as a string using the forHumans() function.
      *
@@ -3269,6 +3294,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
         ) {
             $this->startDate = null;
             $this->endDate = null;
+            $this->rawInterval = null;
         }
     }
 }


Test patch:
diff --git a/tests/Carbon/DiffTest.php b/tests/Carbon/DiffTest.php
index 00d49b76..3a4893fe 100644
--- a/tests/Carbon/DiffTest.php
+++ b/tests/Carbon/DiffTest.php
@@ -2169,4 +2169,13 @@ class DiffTest extends AbstractTestCase
                 ->diffInDays(Carbon::parse('2024-04-01', 'America/New_York')),
         );
     }
+
+    public function testAFormat()
+    {
+        $past = new Carbon('-3 Days');
+        $today = new Carbon('today');
+        $interval = $today->diff($past);
+
+        $this->assertSame('2', $interval->format('%a'));
+    }
 }
diff --git a/tests/CarbonImmutable/DiffTest.php b/tests/CarbonImmutable/DiffTest.php
index f91aeef9..b8749c65 100644
--- a/tests/CarbonImmutable/DiffTest.php
+++ b/tests/CarbonImmutable/DiffTest.php
@@ -1935,4 +1935,13 @@ class DiffTest extends AbstractTestCase
             'minimumUnit' => 'd',
         ]));
     }
+
+    public function testAFormat()
+    {
+        $past = new Carbon('-3 Days');
+        $today = new Carbon('today');
+        $interval = $today->diff($past);
+
+        $this->assertSame('2', $interval->format('%a'));
+    }
 }