Repository: briannesbitt/Carbon (PHP)

Issue:
🐛 Factory shift the time when calling ->now()
<!--
    🛑 DON'T REMOVE ME.
    This issue template apply to all
      - bug reports,
      - feature proposals,
      - and documentation requests

    Having all those informations 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:
```php
$Test = new Factory([
    'locale' => 'fr_FR',
    'timezone' => 'Europe/Amsterdam',
]);
echo '$Test   = ' . $Test->now()->format('H:i');
```

Carbon version: **2.39.0**

PHP version: **7.4.8**

<!--
    Run the command `composer show nesbot/carbon`
    to get "versions"
    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://try-carbon.herokuapp.com/?theme=xcode&export&embed

    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 immediatly get
    your issue.
-->


I expected to get:

```
Current time from timezone (Europe/Amsterdam)
```
<!--
    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:

```
Current UTC time
```
for same result paste undernead code in https://try-carbon.herokuapp.com/

```php
$Test = new Factory([
    'locale' => 'fr_FR',
    'timezone' => 'Europe/Amsterdam',
]);

echo '$Test   = ' . $Test->now()->format('H:i');
echo "\n";

$Test2 = new Carbon();
$Test2->settimezone('Europe/Amsterdam');
echo '$Test 2 = ' . $Test2->format('H:i');
```
<!--
    If you did not succeed to get the same result in
    https://try-carbon.herokuapp.com/ then precise the
    result you get.
-->

Thanks!



Discussion:
Hello,

The factory actually uses `shiftTimezone()` instead of `setTimezone()`. I don't remember the reason for that. I will inspect.
This actually uses `shiftTimezone()` so `$Test->parse('21:54:00')` will actually be 21:54 in the given timezone. So it can't be simply replaced. It has to use shift or set or to automatically fill the timezone parameter before the instance is created.
It will be fixed in 2.40.0 release.



Patch:
diff --git a/src/Carbon/Factory.php b/src/Carbon/Factory.php
index 1029ffeb0c..6b845347b3 100644
--- a/src/Carbon/Factory.php
+++ b/src/Carbon/Factory.php
@@ -11,6 +11,7 @@
 namespace Carbon;
 
 use Closure;
+use ReflectionMethod;
 
 /**
  * A factory to generate Carbon instances with common settings.
@@ -220,6 +221,7 @@ public function __construct(array $settings = [], string $className = null)
         if ($className) {
             $this->className = $className;
         }
+
         $this->settings = $settings;
     }
 
@@ -266,8 +268,24 @@ public function mergeSettings(array $settings)
 
     public function __call($name, $arguments)
     {
+        $method = new ReflectionMethod($this->className, $name);
+        $settings = $this->settings;
+
+        if ($settings && isset($settings['timezone'])) {
+            $tzParameters = array_filter($method->getParameters(), function ($parameter) {
+                return in_array($parameter->getName(), ['tz', 'timezone'], true);
+            });
+
+            if (count($tzParameters)) {
+                array_splice($arguments, key($tzParameters), 0, [$settings['timezone']]);
+                unset($settings['timezone']);
+            }
+        }
+
         $result = $this->className::$name(...$arguments);
 
-        return $result instanceof CarbonInterface ? $result->settings($this->settings) : $result;
+        return $result instanceof CarbonInterface && !empty($settings)
+            ? $result->settings($settings)
+            : $result;
     }
 }


Test patch:
diff --git a/tests/Factory/FactoryTest.php b/tests/Factory/FactoryTest.php
index dd95c922eb..b8c60e5aab 100644
--- a/tests/Factory/FactoryTest.php
+++ b/tests/Factory/FactoryTest.php
@@ -79,4 +79,33 @@ public function testFactoryModification()
             'timezone' => 'Europe/Paris',
         ], $factory->settings());
     }
+
+    public function testFactoryTimezone()
+    {
+        Carbon::setTestNow(Carbon::parse('2020-09-04 03:39:04.123456', 'UTC'));
+
+        $factory = new Factory();
+
+        $date = $factory->now();
+        $this->assertInstanceOf(Carbon::class, $date);
+        $this->assertSame('2020-09-04 03:39:04.123456 UTC', $date->format('Y-m-d H:i:s.u e'));
+
+        $factory = new Factory([
+            'timezone' => 'Europe/Paris',
+        ]);
+
+        $this->assertSame('2020-09-04 05:39:04.123456 Europe/Paris', $factory->now()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-04 00:00:00.000000 Europe/Paris', $factory->today()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-05 00:00:00.000000 Europe/Paris', $factory->tomorrow()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-04 09:39:04.123456 Europe/Paris', $factory->parse('2020-09-04 09:39:04.123456')->format('Y-m-d H:i:s.u e'));
+
+        $factory = new Factory([
+            'timezone' => 'America/Toronto',
+        ]);
+
+        $this->assertSame('2020-09-03 23:39:04.123456 America/Toronto', $factory->now()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-03 00:00:00.000000 America/Toronto', $factory->today()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-04 00:00:00.000000 America/Toronto', $factory->tomorrow()->format('Y-m-d H:i:s.u e'));
+        $this->assertSame('2020-09-04 09:39:04.123456 America/Toronto', $factory->parse('2020-09-04 09:39:04.123456')->format('Y-m-d H:i:s.u e'));
+    }
 }