From 1868de8b1064b1fab4fd7c4a4b72a778ed3ca004 Mon Sep 17 00:00:00 2001 From: Scott Dawson Date: Mon, 18 Nov 2019 13:12:17 +1100 Subject: [PATCH] [Validator] Validate timezone before passing to IntlDateFormatter --- .../Component/Validator/ConstraintValidator.php | 8 +++++++- .../Validator/Tests/ConstraintValidatorTest.php | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/ConstraintValidator.php b/src/Symfony/Component/Validator/ConstraintValidator.php index 93cca2ea74a5e..08811c9798b71 100644 --- a/src/Symfony/Component/Validator/ConstraintValidator.php +++ b/src/Symfony/Component/Validator/ConstraintValidator.php @@ -87,8 +87,14 @@ protected function formatValue($value, $format = 0) { if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) { if (class_exists('IntlDateFormatter')) { + $timezone = $value->getTimezone(); + // Timezone 'Z' is a valid ISO8601 indicator for UTC + $timezone = 'Z' === strtoupper($timezone->getName()) ? new \DateTimeZone('UTC') : $timezone; + // Only use timezone if it's valid, otherwise fall back to system default + $timezone = \in_array($timezone->getName(), \DateTimeZone::listIdentifiers(), true) ? $timezone : null; + $locale = \Locale::getDefault(); - $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, $value->getTimezone()); + $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, $timezone); // neither the native nor the stub IntlDateFormatter support // DateTimeImmutable as of yet diff --git a/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php b/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php index 96af6f13eb4e7..8584e0dc91511 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php @@ -25,6 +25,22 @@ public function testFormatValue($expected, $value, $format = 0) $this->assertSame($expected, (new TestFormatValueConstraintValidator())->formatValueProxy($value, $format)); } + public function testInvalidTimezoneFallback() + { + if (!class_exists(\IntlDateFormatter::class)) { + $this->markTestSkipped('Test is only valid if IntlDateFormatter is present.'); + } + + $dateTime = new \DateTimeImmutable('1970-01-01T00:00:00X'); + + $locale = \Locale::getDefault(); + $formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT); + $expected = $formatter->format($dateTime); + + $this->assertSame('X', $dateTime->getTimezone()->getName()); + $this->assertSame($expected, (new TestFormatValueConstraintValidator())->formatValueProxy($dateTime, ConstraintValidator::PRETTY_DATE)); + } + public function formatValueProvider() { $data = [ @@ -38,6 +54,7 @@ public function formatValueProvider() ['ccc', $toString, ConstraintValidator::OBJECT_TO_STRING], ['object', $dateTime = (new \DateTimeImmutable('@0'))->setTimezone(new \DateTimeZone('UTC'))], [class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 12:00 AM' : '1970-01-01 00:00:00', $dateTime, ConstraintValidator::PRETTY_DATE], + [class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 12:00 AM' : '1970-01-01 00:00:00', (new \DateTimeImmutable('@0'))->setTimezone(new \DateTimeZone('Z')), ConstraintValidator::PRETTY_DATE], ]; return $data;