diff --git a/src/Symfony/Component/Validator/Constraints/Timezone.php b/src/Symfony/Component/Validator/Constraints/Timezone.php index bf6fece9e91ea..0f4b57b0875a8 100644 --- a/src/Symfony/Component/Validator/Constraints/Timezone.php +++ b/src/Symfony/Component/Validator/Constraints/Timezone.php @@ -26,15 +26,18 @@ class Timezone extends Constraint public const TIMEZONE_IDENTIFIER_ERROR = '5ce113e6-5e64-4ea2-90fe-d2233956db13'; public const TIMEZONE_IDENTIFIER_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8'; public const TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b'; + public const TIMEZONE_IDENTIFIER_INTL_ERROR = '45863c26-88dc-41ba-bf53-c73bd1f7e90d'; public $zone = \DateTimeZone::ALL; public $countryCode; + public $intlCompatible = false; public $message = 'This value is not a valid timezone.'; protected static $errorNames = [ self::TIMEZONE_IDENTIFIER_ERROR => 'TIMEZONE_IDENTIFIER_ERROR', self::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR => 'TIMEZONE_IDENTIFIER_IN_ZONE_ERROR', self::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR => 'TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR', + self::TIMEZONE_IDENTIFIER_INTL_ERROR => 'TIMEZONE_IDENTIFIER_INTL_ERROR', ]; /** @@ -51,5 +54,8 @@ public function __construct(array $options = null) } elseif (\DateTimeZone::PER_COUNTRY !== (\DateTimeZone::PER_COUNTRY & $this->zone)) { throw new ConstraintDefinitionException('The option "countryCode" can only be used when the "zone" option is configured with "\DateTimeZone::PER_COUNTRY".'); } + if ($this->intlCompatible && !class_exists(\IntlTimeZone::class)) { + throw new ConstraintDefinitionException('The option "intlCompatible" can only be used when the PHP intl extension is available.'); + } } } diff --git a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php index a9941ad987db5..ac0d751a45327 100644 --- a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php +++ b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Constraints; +use Symfony\Component\Intl\Exception\MissingResourceException; +use Symfony\Component\Intl\Timezones; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -43,14 +45,28 @@ public function validate($value, Constraint $constraint) $value = (string) $value; - // @see: https://bugs.php.net/bug.php?id=75928 + if ($constraint->intlCompatible && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($value)->getID()) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR) + ->addViolation(); + + return; + } + if ($constraint->countryCode) { - $timezoneIds = @\DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode) ?: []; + $phpTimezoneIds = @\DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode) ?: []; + try { + $intlTimezoneIds = Timezones::forCountryCode($constraint->countryCode); + } catch (MissingResourceException $e) { + $intlTimezoneIds = []; + } } else { - $timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone); + $phpTimezoneIds = \DateTimeZone::listIdentifiers($constraint->zone); + $intlTimezoneIds = self::getIntlTimezones($constraint->zone); } - if (\in_array($value, $timezoneIds, true)) { + if (\in_array($value, $phpTimezoneIds, true) || \in_array($value, $intlTimezoneIds, true)) { return; } @@ -63,9 +79,9 @@ public function validate($value, Constraint $constraint) } $this->context->buildViolation($constraint->message) - ->setParameter('{{ value }}', $this->formatValue($value)) - ->setCode($code) - ->addViolation(); + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); } /** @@ -89,4 +105,26 @@ protected function formatValue($value, $format = 0) return array_search($value, (new \ReflectionClass(\DateTimeZone::class))->getConstants(), true) ?: $value; } + + private static function getIntlTimezones(int $zone): array + { + $timezones = Timezones::getIds(); + + if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) { + return $timezones; + } + + $filtered = []; + foreach ((new \ReflectionClass(\DateTimeZone::class))->getConstants() as $const => $flag) { + if ($flag !== ($flag & $zone)) { + continue; + } + + $filtered[] = array_filter($timezones, static function ($id) use ($const) { + return 0 === stripos($id, $const.'/'); + }); + } + + return $filtered ? array_merge(...$filtered) : []; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php index a81e642cdfaaf..c5dc5c40d458e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php @@ -60,7 +60,26 @@ public function testValidTimezones(string $timezone) public function getValidTimezones(): iterable { + // ICU standard (alias/BC in PHP) + yield ['Etc/UTC']; + yield ['Etc/GMT']; + yield ['America/Buenos_Aires']; + + // PHP standard (alias in ICU) + yield ['UTC']; yield ['America/Argentina/Buenos_Aires']; + + // not deprecated in ICU + yield ['CST6CDT']; + yield ['EST5EDT']; + yield ['MST7MDT']; + yield ['PST8PDT']; + yield ['America/Montreal']; + + // expired in ICU + yield ['Europe/Saratov']; + + // standard yield ['America/Barbados']; yield ['America/Toronto']; yield ['Antarctica/Syowa']; @@ -71,7 +90,6 @@ public function getValidTimezones(): iterable yield ['Europe/Copenhagen']; yield ['Europe/Paris']; yield ['Pacific/Noumea']; - yield ['UTC']; } /** @@ -90,6 +108,8 @@ public function testValidGroupedTimezones(string $timezone, int $zone) public function getValidGroupedTimezones(): iterable { + yield ['America/Buenos_Aires', \DateTimeZone::AMERICA | \DateTimeZone::AUSTRALIA]; // icu + yield ['America/Argentina/Buenos_Aires', \DateTimeZone::AMERICA]; // php yield ['America/Argentina/Cordoba', \DateTimeZone::AMERICA]; yield ['America/Barbados', \DateTimeZone::AMERICA]; yield ['Africa/Cairo', \DateTimeZone::AFRICA]; @@ -124,6 +144,7 @@ public function testInvalidTimezoneWithoutZone(string $timezone) public function getInvalidTimezones(): iterable { + yield ['Buenos_Aires/America']; yield ['Buenos_Aires/Argentina/America']; yield ['Mayotte/Indian']; yield ['foobar']; @@ -149,11 +170,15 @@ public function testInvalidGroupedTimezones(string $timezone, int $zone) public function getInvalidGroupedTimezones(): iterable { + yield ['America/Buenos_Aires', \DateTimeZone::ASIA | \DateTimeZone::AUSTRALIA]; // icu + yield ['America/Argentina/Buenos_Aires', \DateTimeZone::EUROPE]; // php yield ['Antarctica/McMurdo', \DateTimeZone::AMERICA]; yield ['America/Barbados', \DateTimeZone::ANTARCTICA]; yield ['Europe/Kiev', \DateTimeZone::ARCTIC]; yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN]; yield ['Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN | \DateTimeZone::ANTARCTICA]; + yield ['UTC', \DateTimeZone::EUROPE]; + yield ['Etc/UTC', \DateTimeZone::EUROPE]; } /** @@ -173,6 +198,8 @@ public function testValidGroupedTimezonesByCountry(string $timezone, string $cou public function getValidGroupedTimezonesByCountry(): iterable { + yield ['America/Buenos_Aires', 'AR']; // icu + yield ['America/Argentina/Buenos_Aires', 'AR']; // php yield ['America/Argentina/Cordoba', 'AR']; yield ['America/Barbados', 'BB']; yield ['Africa/Cairo', 'EG']; @@ -215,6 +242,7 @@ public function getInvalidGroupedTimezonesByCountry(): iterable yield ['America/Argentina/Cordoba', 'FR']; yield ['America/Barbados', 'PT']; yield ['Europe/Bern', 'FR']; + yield ['Etc/UTC', 'NL']; yield ['Europe/Amsterdam', 'AC']; // "AC" has no timezones, but is a valid country code } @@ -267,8 +295,6 @@ public function testDeprecatedTimezonesAreInvalidWithoutBC(string $timezone) public function getDeprecatedTimezones(): iterable { - yield ['America/Buenos_Aires']; - yield ['America/Montreal']; yield ['Australia/ACT']; yield ['Australia/LHI']; yield ['Australia/Queensland']; @@ -277,13 +303,29 @@ public function getDeprecatedTimezones(): iterable yield ['Canada/Mountain']; yield ['Canada/Pacific']; yield ['CET']; - yield ['CST6CDT']; - yield ['Etc/GMT']; + yield ['GMT']; yield ['Etc/Greenwich']; yield ['Etc/UCT']; yield ['Etc/Universal']; - yield ['Etc/UTC']; yield ['Etc/Zulu']; yield ['US/Pacific']; } + + /** + * @requires extension intl + */ + public function testIntlCompatibility() + { + $constraint = new Timezone([ + 'message' => 'myMessage', + 'intlCompatible' => true, + ]); + + $this->validator->validate('Europe/Saratov', $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"Europe/Saratov"') + ->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR) + ->assertRaised(); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy