diff --git a/src/Action/SetObjectFieldValueAction.php b/src/Action/SetObjectFieldValueAction.php index dcc805a26f..e1b5ec4d45 100644 --- a/src/Action/SetObjectFieldValueAction.php +++ b/src/Action/SetObjectFieldValueAction.php @@ -121,7 +121,15 @@ public function __invoke(Request $request): JsonResponse // Handle date and datetime types have setter expecting a DateTime object if ('' !== $value && \in_array($fieldDescription->getType(), ['date', 'datetime'], true)) { - $value = new \DateTime($value); + $inputTimezone = new \DateTimeZone(date_default_timezone_get()); + $outputTimezone = $fieldDescription->getOption('timezone'); + + if ($outputTimezone && !$outputTimezone instanceof \DateTimeZone) { + $outputTimezone = new \DateTimeZone($outputTimezone); + } + + $value = new \DateTime($value, $outputTimezone ?: $inputTimezone); + $value->setTimezone($inputTimezone); } // Handle boolean type transforming the value into a boolean diff --git a/src/Resources/views/CRUD/base_list_field.html.twig b/src/Resources/views/CRUD/base_list_field.html.twig index 5f9eef3c9c..812b64bc41 100644 --- a/src/Resources/views/CRUD/base_list_field.html.twig +++ b/src/Resources/views/CRUD/base_list_field.html.twig @@ -55,7 +55,7 @@ file that was distributed with this source code. ) %} {% if field_description.type == constant('Sonata\\AdminBundle\\Templating\\TemplateRegistry::TYPE_DATE') and value is not empty %} - {% set data_value = value.format('Y-m-d') %} + {% set data_value = value|date('Y-m-d', options.timezone|default(null)) %} {% elseif field_description.type == constant('Sonata\\AdminBundle\\Templating\\TemplateRegistry::TYPE_BOOLEAN') and value is empty %} {% set data_value = 0 %} {% elseif value is iterable %} diff --git a/tests/Action/Bafoo.php b/tests/Action/Bafoo.php index f5df9a767d..91c6f36a73 100644 --- a/tests/Action/Bafoo.php +++ b/tests/Action/Bafoo.php @@ -31,7 +31,7 @@ public function setDateProp(\DateTime $dateProp): self return $this; } - public function getDatetimeProp(): \DateTime + public function getDatetimeProp(): ?\DateTime { return $this->datetimeProp; } diff --git a/tests/Action/SetObjectFieldValueActionTest.php b/tests/Action/SetObjectFieldValueActionTest.php index 75fe058dec..8ac2386799 100644 --- a/tests/Action/SetObjectFieldValueActionTest.php +++ b/tests/Action/SetObjectFieldValueActionTest.php @@ -126,7 +126,25 @@ public function testSetObjectFieldValueAction(): void $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); } - public function testSetObjectFieldValueActionWithDate(): void + public function getTimeZones(): iterable + { + $default = new \DateTimeZone(date_default_timezone_get()); + $custom = new \DateTimeZone('Europe/Rome'); + + return [ + 'empty timezone' => [null, $default], + 'disabled timezone' => [false, $default], + 'default timezone by name' => [$default->getName(), $default], + 'default timezone by object' => [$default, $default], + 'custom timezone by name' => [$custom->getName(), $custom], + 'custom timezone by object' => [$custom, $custom], + ]; + } + + /** + * @dataProvider getTimeZones + */ + public function testSetObjectFieldValueActionWithDate($timezone, \DateTimeZone $expectedTimezone): void { $object = new Bafoo(); $request = new Request([ @@ -161,6 +179,7 @@ public function testSetObjectFieldValueActionWithDate(): void $container->reveal() )); $fieldDescription->getOption('editable')->willReturn(true); + $fieldDescription->getOption('timezone')->willReturn($timezone); $fieldDescription->getAdmin()->willReturn($this->admin->reveal()); $fieldDescription->getType()->willReturn('date'); $fieldDescription->getTemplate()->willReturn('field_template'); @@ -171,9 +190,20 @@ public function testSetObjectFieldValueActionWithDate(): void $response = ($this->action)($request); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); + + $defaultTimezone = new \DateTimeZone(date_default_timezone_get()); + $expectedDate = new \DateTime($request->query->get('value'), $expectedTimezone); + $expectedDate->setTimezone($defaultTimezone); + + $this->assertInstanceOf(\DateTime::class, $object->getDateProp()); + $this->assertSame($expectedDate->format('Y-m-d'), $object->getDateProp()->format('Y-m-d')); + $this->assertSame($defaultTimezone->getName(), $object->getDateProp()->getTimezone()->getName()); } - public function testSetObjectFieldValueActionWithDateTime(): void + /** + * @dataProvider getTimeZones + */ + public function testSetObjectFieldValueActionWithDateTime($timezone, \DateTimeZone $expectedTimezone): void { $object = new Bafoo(); $request = new Request([ @@ -208,6 +238,7 @@ public function testSetObjectFieldValueActionWithDateTime(): void $container->reveal() )); $fieldDescription->getOption('editable')->willReturn(true); + $fieldDescription->getOption('timezone')->willReturn($timezone); $fieldDescription->getAdmin()->willReturn($this->admin->reveal()); $fieldDescription->getType()->willReturn('datetime'); $fieldDescription->getTemplate()->willReturn('field_template'); @@ -218,6 +249,14 @@ public function testSetObjectFieldValueActionWithDateTime(): void $response = ($this->action)($request); $this->assertSame(Response::HTTP_OK, $response->getStatusCode()); + + $defaultTimezone = new \DateTimeZone(date_default_timezone_get()); + $expectedDate = new \DateTime($request->query->get('value'), $expectedTimezone); + $expectedDate->setTimezone($defaultTimezone); + + $this->assertInstanceOf(\DateTime::class, $object->getDatetimeProp()); + $this->assertSame($expectedDate->format('Y-m-d H:i:s'), $object->getDatetimeProp()->format('Y-m-d H:i:s')); + $this->assertSame($defaultTimezone->getName(), $object->getDatetimeProp()->getTimezone()->getName()); } public function testSetObjectFieldValueActionOnARelationField(): void