Repository: briannesbitt/Carbon (PHP)

Issue:
Can no longer use `createFromFormat` with an non-string value as time input
<!--
    🛑 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 after upgrading to Carbon v3 as part of the Laravel 11 upgrade:
<!--
    ⚠️ 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
CarbonImmutable::createFromFormat('U', 1709589519);
```

Carbon version: 3.1.1
<!--
    ⚠️ Run the command `composer show nesbot/carbon` to get "versions"
-->

PHP version: 8.3

<!--
    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:

A CarbonImmutable object is successfully created.

<!--
    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:

An exception thrown with the following message:

```
Carbon\CarbonImmutable::rawCreateFromFormat(): Argument #2 ($time) must be of type string, int given, called in /home/vagrant/atlas/vendor/nesbot/carbon/src/Carbon/Traits/Creator.php on line 647

```
<!--
    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.
-->

Unfortunately most of this code happens out of my control inside a library I'm using (spatie/laravel-data), which this previously used to work fine with in Carbon v2. I am aware this should probably be handled better by `::createFromTimestamp` or via just casting to string.

I'm assuming this is due to added types on the `::rawCreateFromFormat` function or the addition of strict types on these files.





Discussion:
Hello,

This is because of the (expected) switch to strict types in Carbon 3, this method is from PHP DateTime itself
https://www.php.net/manual/fr/datetime.createfromformat.php

And `int` is not accepted. (`string` has been enforced since PHP 8)

It's converted to `string` before given to `DateTimeImmutable::createFromFormat` anyway so I'd recommend to pass only string there:

`CarbonImmutable::createFromFormat('U', (string) 1709589519);`

But if you format is not dynamic, it's even better to use the method:
```php
CarbonImmutable::createFromTimestampUTC(1709589519);
```
That is super explicit about what it takes and give unlike the `U` that needs some doc or by-heart knowledge.
That was my assumption relating to switching over to strict-typing in the library.

In this case the library I'm using, spatie/laravel-data, takes in a list of format strings and attempts to create a CarbonImmutable instance using createFromFormat on the given value, and in certain cases it is an integer timestamp stored in our database, so I don't have a ton of control over that flow.

In this case I have found a work around and created my own custom cast for these dates and instead use the parse method, which seems to work fine. I will probably bring this issue to them as well.

However, couple thing I will note though:
- Because the `createFromFromat` function in Carbon accepts a mixed typing for the time input, the `rawCreateFromFormat` method is what throws this error, as its what actually explicitly requires a string for the time input, so the issue is not immediately obvious in that regard.
- `createFromTimestamp` and the related functions allow passing through float, int and string
- In my own project code without strict typing enabled, using `DateTimeImmutable::createFromFormat('U', 1709589519);` doesn't throw any warnings or errors so the user-experience of the Carbon seems to different form native in that regard.
> In this case the library I'm using, spatie/laravel-data, takes in a list of format strings and attempts to create a CarbonImmutable instance using createFromFormat on the given value, and in certain cases it is an integer timestamp stored in our database, so I don't have a ton of control over that flow.

I see the point, spatie's good friend :) I think we can arrange that on spatie/laravel-data side.

I agree the fact that Carbon::createFromFromat type is not updated is a mistake and prevent the script-type to propagate all way long.

> createFromTimestamp and the related functions allow passing through float, int and string

Is it an issue?
> 
> > createFromTimestamp and the related functions allow passing through float, int and string
> 
> Is it an issue?

Sorry I should have expanded on this point more, however I don't think it matters a ton now, as I've realized that typing the `createFromFormat` function would actually fix this issue by causing non-strict typed code to automatically cast the integer input into a string before passing it through to the function.

I've also opened an issue on the spatie/laravel-data repo about this as well.

It also throws an exception in Filament's DatePicker component. 



Patch:
diff --git a/src/Carbon/Traits/Creator.php b/src/Carbon/Traits/Creator.php
index 29a118fc4e..292fe10b35 100644
--- a/src/Carbon/Traits/Creator.php
+++ b/src/Carbon/Traits/Creator.php
@@ -643,6 +643,19 @@ public static function createFromFormat($format, $time, $timezone = null): ?self
     {
         $function = static::$createFromFormatFunction;
 
+        // format is a single numeric unit
+        if (\is_int($time) && \in_array(ltrim($format, '!'), ['U', 'Y', 'y', 'X', 'x', 'm', 'n', 'd', 'j', 'w', 'W', 'H', 'h', 'G', 'g', 'i', 's', 'u', 'z', 'v'], true)) {
+            $time = (string) $time;
+        }
+
+        if (!\is_string($time)) {
+            @trigger_error(
+                'createFromFormat() will only accept string or integer for 1-letter format representing a numeric unit int next version',
+                \E_USER_DEPRECATED,
+            );
+            $time = (string) $time;
+        }
+
         if (!$function) {
             return static::rawCreateFromFormat($format, $time, $timezone);
         }


Test patch:
diff --git a/tests/Jenssegers/DateTest.php b/tests/Jenssegers/DateTest.php
index bcf154fea8..bac4cb193c 100644
--- a/tests/Jenssegers/DateTest.php
+++ b/tests/Jenssegers/DateTest.php
@@ -47,9 +47,20 @@ public function testMake()
 
     public function testCreateFromCarbon()
     {
+        // Preferred way
         $date = Carbon::make(Carbon::createFromFormat('U', '1367186296'));
         $this->assertInstanceOf(Carbon::class, $date);
         $this->assertSame(1367186296, $date->getTimestamp());
+
+        // Accepted for backward-compatibility with some libraries
+        $date = Carbon::make(Carbon::createFromFormat('!U', 1367186296));
+        $this->assertInstanceOf(Carbon::class, $date);
+        $this->assertSame(1367186296, $date->getTimestamp());
+
+        // Deprecated usage
+        $date = Carbon::make(Carbon::createFromFormat('!md', 1225));
+        $this->assertInstanceOf(Carbon::class, $date);
+        $this->assertSame(30931200, $date->getTimestamp());
     }
 
     public function testManipulation()