Skip to content

Commit

Permalink
bug #34904 [Validator][ConstraintValidator] Safe fail on invalid time…
Browse files Browse the repository at this point in the history
…zones (fancyweb)

This PR was merged into the 3.4 branch.

Discussion
----------

[Validator][ConstraintValidator] Safe fail on invalid timezones

Co-authored-by: Scott Dawson <scott@loyaltycorp.com.au>

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #33901
| License       | MIT
| Doc PR        |

Alternative to #33902.

I will explain why I think it is better this way:

1. We set the timezone with the setter because it's 100% safe, it never fails. It fall backs to the default timezone if the provided timezone is not supported (as if we passed null, so the same behavior that always existed). We are therefore compatible with all edge cases.
2. We don't validate the timezone with `\DateTimeZone::listIdentifiers()`. It only returns full identifiers like "Europe/Paris" but it doesn't take into account "numeric" identifiers such as "+08:00" which are perfectly valid. I added a test case to ensure we stay valid with this case. + some invalid identifiers for the native `\IntlDateFormatter` are valid with the polyfill that uses `\DateTimeZone` (eg : `X`). I don't think we can validate anything safely that will work reliably on both implementations.

Commits
-------

3b1b994 [Validator][ConstraintValidator] Safe fail on invalid timezones
  • Loading branch information
nicolas-grekas committed Dec 13, 2019
2 parents 4ec093a + 3b1b994 commit e043316
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 11 deletions.
14 changes: 5 additions & 9 deletions src/Symfony/Component/Validator/ConstraintValidator.php
Expand Up @@ -87,16 +87,12 @@ protected function formatValue($value, $format = 0)
{
if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) {
if (class_exists('IntlDateFormatter')) {
$locale = \Locale::getDefault();
$formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, $value->getTimezone());
$formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC');

// neither the native nor the stub IntlDateFormatter support
// DateTimeImmutable as of yet
if (!$value instanceof \DateTime) {
$value = new \DateTime($value->format('Y-m-d H:i:s.u e'));
}

return $formatter->format($value);
return $formatter->format(new \DateTime(
$value->format('Y-m-d H:i:s.u'),
new \DateTimeZone('UTC')
));
}

return $value->format('Y-m-d H:i:s');
Expand Down
12 changes: 10 additions & 2 deletions src/Symfony/Component/Validator/Tests/ConstraintValidatorTest.php
Expand Up @@ -27,6 +27,9 @@ public function testFormatValue($expected, $value, $format = 0)

public function formatValueProvider()
{
$defaultTimezone = date_default_timezone_get();
date_default_timezone_set('Europe/Moscow'); // GMT+3

$data = [
['true', true],
['false', false],
Expand All @@ -36,10 +39,15 @@ public function formatValueProvider()
['array', []],
['object', $toString = new TestToStringObject()],
['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],
['object', $dateTime = new \DateTimeImmutable('1971-02-02T08:00:00UTC')],
[class_exists(\IntlDateFormatter::class) ? 'Oct 4, 2019, 11:02 AM' : '2019-10-04 11:02:03', new \DateTimeImmutable('2019-10-04T11:02:03+09:00'), ConstraintValidator::PRETTY_DATE],
[class_exists(\IntlDateFormatter::class) ? 'Feb 2, 1971, 8:00 AM' : '1971-02-02 08:00:00', $dateTime, ConstraintValidator::PRETTY_DATE],
[class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 6:00 AM' : '1970-01-01 06:00:00', new \DateTimeImmutable('1970-01-01T06:00:00Z'), ConstraintValidator::PRETTY_DATE],
[class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 3:00 PM' : '1970-01-01 15:00:00', (new \DateTimeImmutable('1970-01-01T23:00:00'))->setTimezone(new \DateTimeZone('America/New_York')), ConstraintValidator::PRETTY_DATE],
];

date_default_timezone_set($defaultTimezone);

return $data;
}
}
Expand Down

0 comments on commit e043316

Please sign in to comment.