Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7294b59

Browse filesBrowse files
committed
[Validator] Allow intl timezones
1 parent 1c110fa commit 7294b59
Copy full SHA for 7294b59

File tree

3 files changed

+99
-13
lines changed
Filter options

3 files changed

+99
-13
lines changed

‎src/Symfony/Component/Validator/Constraints/Timezone.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/Timezone.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ class Timezone extends Constraint
2626
public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13';
2727
public const TIMEZONE_IDENTIFIER_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8';
2828
public const TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b';
29+
public const TIMEZONE_IDENTIFIER_INTL_ERROR = '45863c26-88dc-41ba-bf53-c73bd1f7e90d';
2930

3031
public $zone = \DateTimeZone::ALL;
3132
public $countryCode;
33+
public $intlCompatible = false;
3234
public $message = 'This value is not a valid timezone.';
3335

3436
protected static $errorNames = [
3537
self::TIMEZONE_IDENTIFIER_ERROR => 'TIMEZONE_IDENTIFIER_ERROR',
3638
self::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR => 'TIMEZONE_IDENTIFIER_IN_ZONE_ERROR',
3739
self::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR => 'TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR',
40+
self::TIMEZONE_IDENTIFIER_INTL_ERROR => 'TIMEZONE_IDENTIFIER_INTL_ERROR',
3841
];
3942

4043
/**
@@ -51,5 +54,8 @@ public function __construct(array $options = null)
5154
} elseif (\DateTimeZone::PER_COUNTRY !== (\DateTimeZone::PER_COUNTRY & $this->zone)) {
5255
throw new ConstraintDefinitionException('The option "countryCode" can only be used when the "zone" option is configured with "\DateTimeZone::PER_COUNTRY".');
5356
}
57+
if ($this->intlCompatible && !class_exists(\IntlTimeZone::class)) {
58+
throw new ConstraintDefinitionException('The option "intlCompatible" can only be used when the PHP intl extension is available.');
59+
}
5460
}
5561
}

‎src/Symfony/Component/Validator/Constraints/TimezoneValidator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/TimezoneValidator.php
+45-7Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14+
use Symfony\Component\Intl\Exception\MissingResourceException;
15+
use Symfony\Component\Intl\Timezones;
1416
use Symfony\Component\Validator\Constraint;
1517
use Symfony\Component\Validator\ConstraintValidator;
1618
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -43,14 +45,28 @@ public function validate($value, Constraint $constraint)
4345

4446
$value = (string) $value;
4547

46-
// @see: https://bugs.php.net/bug.php?id=75928
48+
if ($constraint->intlCompatible && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($value)->getID()) {
49+
$this->context->buildViolation($constraint->message)
50+
->setParameter('{{ value }}', $this->formatValue($value))
51+
->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR)
52+
->addViolation();
53+
54+
return;
55+
}
56+
4757
if ($constraint->countryCode) {
48-
$timezoneIds = @\DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode) ?: [];
58+
$phpTimezoneIds = @\DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode) ?: [];
59+
try {
60+
$intlTimezoneIds = Timezones::forCountryCode($constraint->countryCode);
61+
} catch (MissingResourceException $e) {
62+
$intlTimezoneIds = [];
63+
}
4964
} else {
50-
$timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone);
65+
$phpTimezoneIds = \DateTimeZone::listIdentifiers($constraint->zone);
66+
$intlTimezoneIds = self::getIntlTimezones($constraint->zone);
5167
}
5268

53-
if (\in_array($value, $timezoneIds, true)) {
69+
if (\in_array($value, $phpTimezoneIds, true) || \in_array($value, $intlTimezoneIds, true)) {
5470
return;
5571
}
5672

@@ -63,9 +79,9 @@ public function validate($value, Constraint $constraint)
6379
}
6480

6581
$this->context->buildViolation($constraint->message)
66-
->setParameter('{{ value }}', $this->formatValue($value))
67-
->setCode($code)
68-
->addViolation();
82+
->setParameter('{{ value }}', $this->formatValue($value))
83+
->setCode($code)
84+
->addViolation();
6985
}
7086

7187
/**
@@ -89,4 +105,26 @@ protected function formatValue($value, $format = 0)
89105

90106
return array_search($value, (new \ReflectionClass(\DateTimeZone::class))->getConstants(), true) ?: $value;
91107
}
108+
109+
private static function getIntlTimezones(int $zone): array
110+
{
111+
$timezones = Timezones::getIds();
112+
113+
if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) {
114+
return $timezones;
115+
}
116+
117+
$filtered = [];
118+
foreach ((new \ReflectionClass(\DateTimeZone::class))->getConstants() as $const => $flag) {
119+
if ($flag !== ($flag & $zone)) {
120+
continue;
121+
}
122+
123+
$filtered[] = array_filter($timezones, static function ($id) use ($const) {
124+
return 0 === stripos($id, $const.'/');
125+
});
126+
}
127+
128+
return $filtered ? array_merge(...$filtered) : [];
129+
}
92130
}

‎src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php
+48-6Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,26 @@ public function testValidTimezones(string $timezone)
6060

6161
public function getValidTimezones(): iterable
6262
{
63+
// ICU standard (alias/BC in PHP)
64+
yield ['Etc/UTC'];
65+
yield ['Etc/GMT'];
66+
yield ['America/Buenos_Aires'];
67+
68+
// PHP standard (alias in ICU)
69+
yield ['UTC'];
6370
yield ['America/Argentina/Buenos_Aires'];
71+
72+
// not deprecated in ICU
73+
yield ['CST6CDT'];
74+
yield ['EST5EDT'];
75+
yield ['MST7MDT'];
76+
yield ['PST8PDT'];
77+
yield ['America/Montreal'];
78+
79+
// expired in ICU
80+
yield ['Europe/Saratov'];
81+
82+
// standard
6483
yield ['America/Barbados'];
6584
yield ['America/Toronto'];
6685
yield ['Antarctica/Syowa'];
@@ -71,7 +90,6 @@ public function getValidTimezones(): iterable
7190
yield ['Europe/Copenhagen'];
7291
yield ['Europe/Paris'];
7392
yield ['Pacific/Noumea'];
74-
yield ['UTC'];
7593
}
7694

7795
/**
@@ -90,6 +108,8 @@ public function testValidGroupedTimezones(string $timezone, int $zone)
90108

91109
public function getValidGroupedTimezones(): iterable
92110
{
111+
yield ['America/Buenos_Aires', \DateTimeZone::AMERICA | \DateTimeZone::AUSTRALIA]; // icu
112+
yield ['America/Argentina/Buenos_Aires', \DateTimeZone::AMERICA]; // php
93113
yield ['America/Argentina/Cordoba', \DateTimeZone::AMERICA];
94114
yield ['America/Barbados', \DateTimeZone::AMERICA];
95115
yield ['Africa/Cairo', \DateTimeZone::AFRICA];
@@ -124,6 +144,7 @@ public function testInvalidTimezoneWithoutZone(string $timezone)
124144

125145
public function getInvalidTimezones(): iterable
126146
{
147+
yield ['Buenos_Aires/America'];
127148
yield ['Buenos_Aires/Argentina/America'];
128149
yield ['Mayotte/Indian'];
129150
yield ['foobar'];
@@ -149,11 +170,15 @@ public function testInvalidGroupedTimezones(string $timezone, int $zone)
149170

150171
public function getInvalidGroupedTimezones(): iterable
151172
{
173+
yield ['America/Buenos_Aires', \DateTimeZone::ASIA | \DateTimeZone::AUSTRALIA]; // icu
174+
yield ['America/Argentina/Buenos_Aires', \DateTimeZone::EUROPE]; // php
152175
yield ['Antarctica/McMurdo', \DateTimeZone::AMERICA];
153176
yield ['America/Barbados', \DateTimeZone::ANTARCTICA];
154177
yield ['Europe/Kiev', \DateTimeZone::ARCTIC];
155178
yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN];
156179
yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN | \DateTimeZone::ANTARCTICA];
180+
yield ['UTC', \DateTimeZone::EUROPE];
181+
yield ['Etc/UTC', \DateTimeZone::EUROPE];
157182
}
158183

159184
/**
@@ -173,6 +198,8 @@ public function testValidGroupedTimezonesByCountry(string $timezone, string $cou
173198

174199
public function getValidGroupedTimezonesByCountry(): iterable
175200
{
201+
yield ['America/Buenos_Aires', 'AR']; // icu
202+
yield ['America/Argentina/Buenos_Aires', 'AR']; // php
176203
yield ['America/Argentina/Cordoba', 'AR'];
177204
yield ['America/Barbados', 'BB'];
178205
yield ['Africa/Cairo', 'EG'];
@@ -215,6 +242,7 @@ public function getInvalidGroupedTimezonesByCountry(): iterable
215242
yield ['America/Argentina/Cordoba', 'FR'];
216243
yield ['America/Barbados', 'PT'];
217244
yield ['Europe/Bern', 'FR'];
245+
yield ['Etc/UTC', 'NL'];
218246
yield ['Europe/Amsterdam', 'AC']; // "AC" has no timezones, but is a valid country code
219247
}
220248

@@ -267,8 +295,6 @@ public function testDeprecatedTimezonesAreInvalidWithoutBC(string $timezone)
267295

268296
public function getDeprecatedTimezones(): iterable
269297
{
270-
yield ['America/Buenos_Aires'];
271-
yield ['America/Montreal'];
272298
yield ['Australia/ACT'];
273299
yield ['Australia/LHI'];
274300
yield ['Australia/Queensland'];
@@ -277,13 +303,29 @@ public function getDeprecatedTimezones(): iterable
277303
yield ['Canada/Mountain'];
278304
yield ['Canada/Pacific'];
279305
yield ['CET'];
280-
yield ['CST6CDT'];
281-
yield ['Etc/GMT'];
306+
yield ['GMT'];
282307
yield ['Etc/Greenwich'];
283308
yield ['Etc/UCT'];
284309
yield ['Etc/Universal'];
285-
yield ['Etc/UTC'];
286310
yield ['Etc/Zulu'];
287311
yield ['US/Pacific'];
288312
}
313+
314+
/**
315+
* @requires extension intl
316+
*/
317+
public function testIntlCompatibility()
318+
{
319+
$constraint = new Timezone([
320+
'message' => 'myMessage',
321+
'intlCompatible' => true,
322+
]);
323+
324+
$this->validator->validate('Europe/Saratov', $constraint);
325+
326+
$this->buildViolation('myMessage')
327+
->setParameter('{{ value }}', '"Europe/Saratov"')
328+
->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR)
329+
->assertRaised();
330+
}
289331
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.