Skip to content

Commit fec95e0

Browse files
committed
bug #31354 [Intl][Validator] Handle alias locales/timezones (ro0NL)
This PR was squashed before being merged into the 4.3-dev branch (closes #31354). Discussion ---------- [Intl][Validator] Handle alias locales/timezones | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | no | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no | Tests pass? | yes (including intl-data group) | Fixed tickets | #31022 | License | MIT | Doc PR | symfony/symfony-docs#... <!-- required for new features --> both timezones and locales have aliases (either thru deprecation/migration/etc.) for locales we compile a mapping, for timezones we dont. yet we can benefit partial alias support thru DateTimeZone, which knows about most timezone IDs already. both the timezone + locale validator already support aliases. Connsequently, we should support aliases in `Timezones::exists()` + `Locales::exists()` as well IMHO. so far so good; the catch is; with this PR `Locales::getName()` supports aliases, whereas `Timezones::getName()` doesnt. I think it's reasonable for now, until we compile the timezone mapping so we can widen the timezone ID conversion here. Commits ------- 0a9be0d [Intl][Validator] Handle alias locales/timezones
2 parents 26e1d89 + 0a9be0d commit fec95e0

File tree

7 files changed

+74
-24
lines changed

7 files changed

+74
-24
lines changed

src/Symfony/Component/Intl/Locales.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static function exists(string $locale): bool
4444

4545
return true;
4646
} catch (MissingResourceException $e) {
47-
return false;
47+
return \in_array($locale, self::getAliases(), true);
4848
}
4949
}
5050

@@ -53,7 +53,15 @@ public static function exists(string $locale): bool
5353
*/
5454
public static function getName(string $locale, string $displayLocale = null): string
5555
{
56-
return self::readEntry(['Names', $locale], $displayLocale);
56+
try {
57+
return self::readEntry(['Names', $locale], $displayLocale);
58+
} catch (MissingResourceException $e) {
59+
if (false === $aliased = array_search($locale, self::getAliases(), true)) {
60+
throw $e;
61+
}
62+
63+
return self::readEntry(['Names', $aliased], $displayLocale);
64+
}
5765
}
5866

5967
/**

src/Symfony/Component/Intl/Tests/LocalesTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,16 @@ public function testGetNameWithInvalidLocale()
9292
Locales::getName('foo');
9393
}
9494

95+
public function testGetNameWithAliasLocale()
96+
{
97+
$this->assertSame(Locales::getName('tl_PH'), Locales::getName('fil_PH'));
98+
}
99+
95100
public function testExists()
96101
{
97102
$this->assertTrue(Locales::exists('nl_NL'));
103+
$this->assertTrue(Locales::exists('tl_PH'));
104+
$this->assertTrue(Locales::exists('fil_PH')); // alias for "tl_PH"
98105
$this->assertFalse(Locales::exists('zxx_ZZ'));
99106
}
100107
}

src/Symfony/Component/Intl/Tests/TimezonesTest.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,14 +530,23 @@ public function testGetNameDefaultLocale()
530530
/**
531531
* @expectedException \Symfony\Component\Intl\Exception\MissingResourceException
532532
*/
533-
public function testGetNameWithInvalidTimezoneId()
533+
public function testGetNameWithInvalidTimezone()
534534
{
535535
Timezones::getName('foo');
536536
}
537537

538+
/**
539+
* @expectedException \Symfony\Component\Intl\Exception\MissingResourceException
540+
*/
541+
public function testGetNameWithAliasTimezone()
542+
{
543+
Timezones::getName('US/Pacific'); // alias in icu (not compiled), name unavailable in php
544+
}
545+
538546
public function testExists()
539547
{
540548
$this->assertTrue(Timezones::exists('Europe/Amsterdam'));
549+
$this->assertTrue(Timezones::exists('US/Pacific')); // alias in icu (not compiled), identifier available in php
541550
$this->assertFalse(Timezones::exists('Etc/Unknown'));
542551
}
543552

@@ -547,6 +556,9 @@ public function testGetRawOffset()
547556
$this->assertSame(0, Timezones::getRawOffset('Etc/UTC'));
548557
$this->assertSame(-10800, Timezones::getRawOffset('America/Buenos_Aires'));
549558
$this->assertSame(20700, Timezones::getRawOffset('Asia/Katmandu'));
559+
560+
// ensure we support identifiers available in php (not compiled from icu)
561+
Timezones::getRawOffset('US/Pacific');
550562
}
551563

552564
/**

src/Symfony/Component/Intl/Timezones.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,18 @@ public static function exists(string $timezone): bool
3636

3737
return true;
3838
} catch (MissingResourceException $e) {
39-
return false;
39+
try {
40+
new \DateTimeZone($timezone);
41+
42+
return true;
43+
} catch (\Exception $e) {
44+
return false;
45+
}
4046
}
4147
}
4248

4349
/**
44-
* @throws MissingResourceException if the timezone identifier does not exists
50+
* @throws MissingResourceException if the timezone identifier does not exist or is an alias
4551
*/
4652
public static function getName(string $timezone, string $displayLocale = null): string
4753
{
@@ -57,7 +63,7 @@ public static function getNames(string $displayLocale = null): array
5763
}
5864

5965
/**
60-
* @throws \Exception if the timezone identifier does not exists
66+
* @throws \Exception if the timezone identifier does not exist
6167
* @throws RuntimeException if there's no timezone DST transition information available
6268
*/
6369
public static function getRawOffset(string $timezone, int $timestamp = null): int
@@ -92,7 +98,7 @@ public static function getCountryCode(string $timezone): string
9298
}
9399

94100
/**
95-
* @throws MissingResourceException if the country code does not exists
101+
* @throws MissingResourceException if the country code does not exist
96102
*/
97103
public static function forCountryCode(string $country): array
98104
{

src/Symfony/Component/Validator/Constraints/LocaleValidator.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14-
use Symfony\Component\Intl\Intl;
1514
use Symfony\Component\Intl\Locales;
1615
use Symfony\Component\Validator\Constraint;
1716
use Symfony\Component\Validator\ConstraintValidator;
17+
use Symfony\Component\Validator\Exception\LogicException;
1818
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
1919
use Symfony\Component\Validator\Exception\UnexpectedValueException;
2020

@@ -42,13 +42,17 @@ public function validate($value, Constraint $constraint)
4242
throw new UnexpectedValueException($value, 'string');
4343
}
4444

45+
if (!class_exists(Locales::class)) {
46+
throw new LogicException('The "symfony/intl" component is required to use the Locale constraint.');
47+
}
48+
4549
$inputValue = (string) $value;
4650
$value = $inputValue;
4751
if ($constraint->canonicalize) {
4852
$value = \Locale::canonicalize($value);
4953
}
5054

51-
if (!Locales::exists($value) && !\in_array($value, Locales::getAliases(), true)) {
55+
if (!Locales::exists($value)) {
5256
$this->context->buildViolation($constraint->message)
5357
->setParameter('{{ value }}', $this->formatValue($inputValue))
5458
->setCode(Locale::NO_SUCH_LOCALE_ERROR)

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

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,10 @@ public function validate($value, Constraint $constraint)
5454
return;
5555
}
5656

57-
if ($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-
}
64-
} else {
65-
$phpTimezoneIds = \DateTimeZone::listIdentifiers($constraint->zone);
66-
$intlTimezoneIds = self::getIntlTimezones($constraint->zone);
67-
}
68-
69-
if (\in_array($value, $phpTimezoneIds, true) || \in_array($value, $intlTimezoneIds, true)) {
57+
if (
58+
\in_array($value, self::getPhpTimezones($constraint->zone, $constraint->countryCode), true) ||
59+
\in_array($value, self::getIntlTimezones($constraint->zone, $constraint->countryCode), true)
60+
) {
7061
return;
7162
}
7263

@@ -106,8 +97,29 @@ protected function formatValue($value, $format = 0)
10697
return array_search($value, (new \ReflectionClass(\DateTimeZone::class))->getConstants(), true) ?: $value;
10798
}
10899

109-
private static function getIntlTimezones(int $zone): array
100+
private static function getPhpTimezones(int $zone, string $countryCode = null): array
110101
{
102+
if (null !== $countryCode) {
103+
return @\DateTimeZone::listIdentifiers($zone, $countryCode) ?: [];
104+
}
105+
106+
return \DateTimeZone::listIdentifiers($zone);
107+
}
108+
109+
private static function getIntlTimezones(int $zone, string $countryCode = null): array
110+
{
111+
if (!class_exists(Timezones::class)) {
112+
return [];
113+
}
114+
115+
if (null !== $countryCode) {
116+
try {
117+
return Timezones::forCountryCode($countryCode);
118+
} catch (MissingResourceException $e) {
119+
return [];
120+
}
121+
}
122+
111123
$timezones = Timezones::getIds();
112124

113125
if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) {

src/Symfony/Component/Validator/Tests/Constraints/LocaleValidatorTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ public function getValidLocales()
111111
['pt', ['canonicalize' => true]],
112112
['pt_PT', ['canonicalize' => true]],
113113
['zh_Hans', ['canonicalize' => true]],
114-
['fil_PH', ['canonicalize' => true]],
114+
['tl_PH', ['canonicalize' => true]],
115+
['fil_PH', ['canonicalize' => true]], // alias for "tl_PH"
115116
];
116117
}
117118

0 commit comments

Comments
 (0)