Skip to content

[Intl][Validator] Handle alias locales/timezones #31354

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/Symfony/Component/Intl/Locales.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static function exists(string $locale): bool

return true;
} catch (MissingResourceException $e) {
return false;
return \in_array($locale, self::getAliases(), true);
}
}

Expand All @@ -53,7 +53,15 @@ public static function exists(string $locale): bool
*/
public static function getName(string $locale, string $displayLocale = null): string
{
return self::readEntry(['Names', $locale], $displayLocale);
try {
return self::readEntry(['Names', $locale], $displayLocale);
} catch (MissingResourceException $e) {
if (false === $aliased = array_search($locale, self::getAliases(), true)) {
throw $e;
}

return self::readEntry(['Names', $aliased], $displayLocale);
}
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/Symfony/Component/Intl/Tests/LocalesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@ public function testGetNameWithInvalidLocale()
Locales::getName('foo');
}

public function testGetNameWithAliasLocale()
{
$this->assertSame(Locales::getName('tl_PH'), Locales::getName('fil_PH'));
}

public function testExists()
{
$this->assertTrue(Locales::exists('nl_NL'));
$this->assertTrue(Locales::exists('tl_PH'));
$this->assertTrue(Locales::exists('fil_PH')); // alias for "tl_PH"
$this->assertFalse(Locales::exists('zxx_ZZ'));
}
}
14 changes: 13 additions & 1 deletion src/Symfony/Component/Intl/Tests/TimezonesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,23 @@ public function testGetNameDefaultLocale()
/**
* @expectedException \Symfony\Component\Intl\Exception\MissingResourceException
*/
public function testGetNameWithInvalidTimezoneId()
public function testGetNameWithInvalidTimezone()
{
Timezones::getName('foo');
}

/**
* @expectedException \Symfony\Component\Intl\Exception\MissingResourceException
*/
public function testGetNameWithAliasTimezone()
{
Timezones::getName('US/Pacific'); // alias in icu (not compiled), name unavailable in php
}

public function testExists()
{
$this->assertTrue(Timezones::exists('Europe/Amsterdam'));
$this->assertTrue(Timezones::exists('US/Pacific')); // alias in icu (not compiled), identifier available in php
$this->assertFalse(Timezones::exists('Etc/Unknown'));
}

Expand All @@ -547,6 +556,9 @@ public function testGetRawOffset()
$this->assertSame(0, Timezones::getRawOffset('Etc/UTC'));
$this->assertSame(-10800, Timezones::getRawOffset('America/Buenos_Aires'));
$this->assertSame(20700, Timezones::getRawOffset('Asia/Katmandu'));

// ensure we support identifiers available in php (not compiled from icu)
Timezones::getRawOffset('US/Pacific');
}

/**
Expand Down
14 changes: 10 additions & 4 deletions src/Symfony/Component/Intl/Timezones.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ public static function exists(string $timezone): bool

return true;
} catch (MissingResourceException $e) {
return false;
try {
new \DateTimeZone($timezone);

return true;
} catch (\Exception $e) {
return false;
}
}
}

/**
* @throws MissingResourceException if the timezone identifier does not exists
* @throws MissingResourceException if the timezone identifier does not exist or is an alias
*/
public static function getName(string $timezone, string $displayLocale = null): string
{
Expand All @@ -57,7 +63,7 @@ public static function getNames(string $displayLocale = null): array
}

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

/**
* @throws MissingResourceException if the country code does not exists
* @throws MissingResourceException if the country code does not exist
*/
public static function forCountryCode(string $country): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\Intl\Intl;
use Symfony\Component\Intl\Locales;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;

Expand Down Expand Up @@ -42,13 +42,17 @@ public function validate($value, Constraint $constraint)
throw new UnexpectedValueException($value, 'string');
}

if (!class_exists(Locales::class)) {
throw new LogicException('The "symfony/intl" component is required to use the Locale constraint.');
}

$inputValue = (string) $value;
$value = $inputValue;
if ($constraint->canonicalize) {
$value = \Locale::canonicalize($value);
}

if (!Locales::exists($value) && !\in_array($value, Locales::getAliases(), true)) {
if (!Locales::exists($value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($inputValue))
->setCode(Locale::NO_SUCH_LOCALE_ERROR)
Expand Down
40 changes: 26 additions & 14 deletions src/Symfony/Component/Validator/Constraints/TimezoneValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,10 @@ public function validate($value, Constraint $constraint)
return;
}

if ($constraint->countryCode) {
$phpTimezoneIds = @\DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode) ?: [];
try {
$intlTimezoneIds = Timezones::forCountryCode($constraint->countryCode);
} catch (MissingResourceException $e) {
$intlTimezoneIds = [];
}
} else {
$phpTimezoneIds = \DateTimeZone::listIdentifiers($constraint->zone);
$intlTimezoneIds = self::getIntlTimezones($constraint->zone);
}

if (\in_array($value, $phpTimezoneIds, true) || \in_array($value, $intlTimezoneIds, true)) {
if (
\in_array($value, self::getPhpTimezones($constraint->zone, $constraint->countryCode), true) ||
\in_array($value, self::getIntlTimezones($constraint->zone, $constraint->countryCode), true)
) {
return;
}

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

private static function getIntlTimezones(int $zone): array
private static function getPhpTimezones(int $zone, string $countryCode = null): array
{
if (null !== $countryCode) {
return @\DateTimeZone::listIdentifiers($zone, $countryCode) ?: [];
}

return \DateTimeZone::listIdentifiers($zone);
}

private static function getIntlTimezones(int $zone, string $countryCode = null): array
{
if (!class_exists(Timezones::class)) {
return [];
}

if (null !== $countryCode) {
try {
return Timezones::forCountryCode($countryCode);
} catch (MissingResourceException $e) {
return [];
}
}

$timezones = Timezones::getIds();

if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ public function getValidLocales()
['pt', ['canonicalize' => true]],
['pt_PT', ['canonicalize' => true]],
['zh_Hans', ['canonicalize' => true]],
['fil_PH', ['canonicalize' => true]],
['tl_PH', ['canonicalize' => true]],
['fil_PH', ['canonicalize' => true]], // alias for "tl_PH"
];
}

Expand Down