8000 feature #22444 [Serializer] DateTimeNormalizer: allow to provide time… · symfony/symfony@1ed41b5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1ed41b5

Browse files
committed
feature #22444 [Serializer] DateTimeNormalizer: allow to provide timezone (ogizanagi)
This PR was merged into the 3.4 branch. Discussion ---------- [Serializer] DateTimeNormalizer: allow to provide timezone | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | N/A | License | MIT | Doc PR | N/A My own use-case was for denormalization of a csv file provided by a third-party. The datetime format inside does not contain any timezone information, and won't change, but it's established to be UTC (or at least consistent). So by providing the new `datetime_timezone` option, the returned instance of `\DateTime(Interface)` will properly be set with the expected timezone. (In case the format already supports the time offset, the provided timezone is ignored in favor of the one parsed by the `\DateTime` object) Regarding normalization, the expected behavior of this feature is to consistently return the same time offset. Commits ------- c10a780 [Serializer] DateTimeNormalizer: allow to provide timezone
2 parents a03e194 + c10a780 commit 1ed41b5

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@
2323
class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface
2424
{
2525
const FORMAT_KEY = 'datetime_format';
26+
const TIMEZONE_KEY = 'datetime_timezone';
2627

2728
/**
2829
* @var string
2930
*/
3031
private $format;
32+
private $timezone;
3133

3234
/**
33-
* @param string $format
35+
* @param string $format
36+
* @param \DateTimeZone|null $timezone
3437
*/
35-
public function __construct($format = \DateTime::RFC3339)
38+
public function __construct($format = \DateTime::RFC3339, \DateTimeZone $timezone = null)
3639
{
3740
$this->format = $format;
41+
$this->timezone = $timezone;
3842
}
3943

4044
/**
@@ -49,6 +53,11 @@ public function normalize($object, $format = null, array $context = array())
4953
}
5054

5155
$format = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
56+
$timezone = $this->getTimezone($context);
57+
58+
if (null !== $timezone) {
59+
$object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
60+
}
5261

5362
return $object->format($format);
5463
}
@@ -69,9 +78,15 @@ public function supportsNormalization($data, $format = null)
6978
public function denormalize($data, $class, $format = null, array $context = array())
7079
{
7180
$dateTimeFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : null;
81+
$timezone = $this->getTimezone($context);
7282

7383
if (null !== $dateTimeFormat) {
74-
$object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data);
84+
if (null === $timezone && PHP_VERSION_ID < 50600) {
85+
// https://bugs.php.net/bug.php?id=68669
86+
$object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data);
87+
} else {
88+
$object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone);
89+
}
7590

7691
if (false !== $object) {
7792
return $object;
@@ -89,7 +104,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
89104
}
90105

91106
try {
92-
return \DateTime::class === $class ? new \DateTime($data) : new \DateTimeImmutable($data);
107+
return \DateTime::class === $class ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone);
93108
} catch (\Exception $e) {
94109
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
95110
}
@@ -126,4 +141,15 @@ private function formatDateTimeErrors(array $errors)
126141

127142
return $formattedErrors;
128143
}
144+
145+
private function getTimezone(array $context)
146+
{
147+
$dateTimeZone = array_key_exists(self::TIMEZONE_KEY, $context) ? $context[self::TIMEZONE_KEY] : $this->timezone;
148+
149+
if (null === $dateTimeZone) {
150+
return null;
151+
}
152+
153+
return $dateTimeZone instanceof \DateTimeZone ? $dateTimeZone : new \DateTimeZone($dateTimeZone);
154+
}
129155
}

src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,32 @@ public function testNormalizeUsingFormatPassedInConstructor()
5252
$this->assertEquals('16', (new DateTimeNormalizer('y'))->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC'))));
5353
}
5454

55+
public function testNormalizeUsingTimeZonePassedInConstructor()
56+
{
57+
$normalizer = new DateTimeNormalizer(\DateTime::RFC3339, new \DateTimeZone('Japan'));
58+
59+
$this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('Japan'))));
60+
$this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC'))));
61+
}
62+
63+
/**
64+
* @dataProvider normalizeUsingTimeZonePassedInContextProvider
65+
*/
66+
public function testNormalizeUsingTimeZonePassedInContext($expected, $input, $timezone)
67+
{
68+
$this->assertSame($expected, $this->normalizer->normalize($input, null, array(
69+
DateTimeNormalizer::TIMEZONE_KEY => $timezone,
70+
)));
71+
}
72+
73+
public function normalizeUsingTimeZonePassedInContextProvider()
74+
{
75+
yield array('2016-12-01T00:00:00+00:00', new \DateTime('2016/12/01', new \DateTimeZone('UTC')), null);
76+
yield array('2016-12-01T00:00:00+09:00', new \DateTime('2016/12/01', new \DateTimeZone('Japan')), new \DateTimeZone('Japan'));
77+
yield array('2016-12-01T09:00:00+09:00', new \DateTime('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan'));
78+
yield array('2016-12-01T09:00:00+09:00', new \DateTimeImmutable('2016/12/01', new \DateTimeZone('UTC')), new \DateTimeZone('Japan'));
79+
}
80+
5581
/**
5682
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
5783
* @expectedExceptionMessage The object must implement the "\DateTimeInterface".
@@ -76,13 +102,63 @@ public function testDenormalize()
76102
$this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTime::class));
77103
}
78104

105+
public function testDenormalizeUsingTimezonePassedInConstructor()
106+
{
107+
$timezone = new \DateTimeZone('Japan');
108+
$expected = new \DateTime('2016/12/01 17:35:00', $timezone);
109+
$normalizer = new DateTimeNormalizer(null, $timezone);
110+
111+
$this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, array(
112+
DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s',
113+
)));
114+
}
115+
79116
public function testDenormalizeUsingFormatPassedInContext()
80117
{
81118
$this->assertEquals(new \DateTimeImmutable('2016/01/01'), $this->normalizer->denormalize('2016.01.01', \DateTimeInterface::class, null, array(DateTimeNormalizer::FORMAT_KEY => 'Y.m.d|')));
82119
$this->assertEquals(new \DateTimeImmutable('2016/01/01'), $this->normalizer->denormalize('2016.01.01', \DateTimeImmutable::class, null, array(DateTimeNormalizer::FORMAT_KEY => 'Y.m.d|')));
83120
$this->assertEquals(new \DateTime('2016/01/01'), $this->normalizer->denormalize('2016.01.01', \DateTime::class, null, array(DateTimeNormalizer::FORMAT_KEY => 'Y.m.d|')));
84121
}
85122

123+
/**
124+
* @dataProvider denormalizeUsingTimezonePassedInContextProvider
125+
*/
126+
public function testDenormalizeUsingTimezonePassedInContext($input, $expected, $timezone, $format = null)
127+
{
128+
$actual = $this->normalizer->denormalize($input, \DateTimeInterface::class, null, array(
129+
DateTimeNormalizer::TIMEZONE_KEY => $timezone,
130+
DateTimeNormalizer::FORMAT_KEY => $format,
131+
));
132+
133+
$this->assertEquals($expected, $actual);
134+
}
135+
136+
public function denormalizeUsingTimezonePassedInContextProvider()
137+
{
138+
yield 'with timezone' => array(
139+
'2016/12/01 17:35:00',
140+
new \DateTimeImmutable('2016/12/01 17:35:00', new \DateTimeZone('Japan')),
141+
new \DateTimeZone('Japan'),
142+
);
143+
yield 'with timezone as string' => array(
144+
'2016/12/01 17:35:00',
145+
new \DateTimeImmutable('2016/12/01 17:35:00', new \DateTimeZone('Japan')),
146+
'Japan',
147+
);
148+
yield 'with format without timezone information' => array(
149+
'2016.12.01 17:35:00',
150+
new \DateTimeImmutable('2016/12/01 17:35:00', new \DateTimeZone('Japan')),
151+
new \DateTimeZone('Japan'),
152+
'Y.m.d H:i:s',
153+
);
154+
yield 'ignored with format with timezone information' => array(
155+
'2016-12-01T17:35:00Z',
156+
new \DateTimeImmutable('2016/12/01 17:35:00', new \DateTimeZone('UTC')),
157+
'Europe/Paris',
158+
\DateTime::RFC3339,
159+
);
160+
}
161+
86162
/**
87163
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
88164
*/

0 commit comments

Comments
 (0)
0