diff --git a/src/Symfony/Component/Validator/Constraints/Timezone.php b/src/Symfony/Component/Validator/Constraints/Timezone.php new file mode 100644 index 0000000000000..485e14b602a63 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/Timezone.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @Annotation + * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) + * + * @author Javier Spagnoletti + */ +class Timezone extends Constraint +{ + const NO_SUCH_TIMEZONE_ERROR = '45de6628-3479-46d6-a210-00ad584f530a'; + const NO_SUCH_TIMEZONE_IN_ZONE_ERROR = 'b57767b1-36c0-40ac-a3d7-629420c775b8'; + const NO_SUCH_TIMEZONE_IN_COUNTRY_ERROR = 'c4a22222-dc92-4fc0-abb0-d95b268c7d0b'; + public $zone = \DateTimeZone::ALL; + public $countryCode; + public $message = 'This value is not a valid timezone.'; + + protected static $errorNames = array( + self::NO_SUCH_TIMEZONE_ERROR => 'NO_SUCH_TIMEZONE_ERROR', + self::NO_SUCH_TIMEZONE_IN_ZONE_ERROR => 'NO_SUCH_TIMEZONE_IN_ZONE_ERROR', + self::NO_SUCH_TIMEZONE_IN_COUNTRY_ERROR => 'NO_SUCH_TIMEZONE_IN_COUNTRY_ERROR', + ); + + /** + * {@inheritdoc} + */ + public function __construct(array $options = null) + { + parent::__construct($options); + + if ($this->countryCode && \DateTimeZone::PER_COUNTRY !== $this->zone) { + throw new ConstraintDefinitionException('The option "countryCode" can only be used when "zone" option has `\DateTimeZone::PER_COUNTRY` as value'); + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php new file mode 100644 index 0000000000000..88030595d5e93 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/TimezoneValidator.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Validates whether a value is a valid timezone identifier. + * + * @author Javier Spagnoletti + */ +class TimezoneValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Timezone) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Timezone'); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + + // @see: https://bugs.php.net/bug.php?id=75928 + if ($constraint->countryCode) { + $timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone, $constraint->countryCode); + } else { + $timezoneIds = \DateTimeZone::listIdentifiers($constraint->zone); + } + + if ($timezoneIds && !\in_array($value, $timezoneIds, true)) { + if ($constraint->countryCode) { + $code = Timezone::NO_SUCH_TIMEZONE_IN_COUNTRY_ERROR; + } elseif (\DateTimeZone::ALL !== $constraint->zone) { + $code = Timezone::NO_SUCH_TIMEZONE_IN_ZONE_ERROR; + } else { + $code = Timezone::NO_SUCH_TIMEZONE_ERROR; + } + + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $this->formatValue($value)) + ->setCode($code) + ->addViolation(); + } + } + + /** + * {@inheritdoc} + */ + public function getDefaultOption() + { + return 'zone'; + } + + /** + * {@inheritdoc} + */ + protected function formatValue($value, $format = 0) + { + $value = parent::formatValue($value, $format); + if ($value) { + if (\DateTimeZone::PER_COUNTRY !== $value) { + $r = new \ReflectionClass(\DateTimeZone::class); + $consts = $r->getConstants(); + if ($zoneFound = array_search($value, $consts, true)) { + return $zoneFound; + } + + return $value; + } + } + + return $value; + } +} diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 4bb2760b418e3..ce058b2a3587a 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -326,6 +326,10 @@ This value should be a multiple of {{ compared_value }}. This value should be a multiple of {{ compared_value }}. + + This value is not a valid timezone. + This value is not a valid timezone. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index 18eb8f4ca2da3..989f14592e280 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -326,6 +326,10 @@ This value should be a multiple of {{ compared_value }}. Este valor debería ser un múltiplo de {{ compared_value }}. + + This value is not a valid timezone. + Este valor no es una zona horaria válida. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php new file mode 100644 index 0000000000000..cd9e556a8885e --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraints\Timezone; + +/** + * @author Javier Spagnoletti + */ +class TimezoneTest extends TestCase +{ + public function testValidTimezoneConstraints() + { + $constraint = new Timezone(); + + $constraint = new Timezone(array( + 'message' => 'myMessage', + 'zone' => \DateTimeZone::PER_COUNTRY, + 'countryCode' => 'AR', + )); + + $constraint = new Timezone(array( + 'message' => 'myMessage', + 'zone' => \DateTimeZone::ALL, + )); + + // Make an assertion in order to avoid this test to be marked as risky + $this->assertInstanceOf(Timezone::class, $constraint); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testExceptionForGroupedTimezonesByCountryWithWrongTimezone() + { + $constraint = new Timezone(array( + 'message' => 'myMessage', + 'zone' => \DateTimeZone::ALL, + 'countryCode' => 'AR', + )); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testExceptionForGroupedTimezonesByCountryWithoutTimezone() + { + $constraint = new Timezone(array( + 'message' => 'myMessage', + 'countryCode' => 'AR', + )); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php new file mode 100644 index 0000000000000..41422e31d92db --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/TimezoneValidatorTest.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use Symfony\Component\Validator\Constraints\Timezone; +use Symfony\Component\Validator\Constraints\TimezoneValidator; +use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; + +/** + * @author Javier Spagnoletti + */ +class TimezoneValidatorTest extends ConstraintValidatorTestCase +{ + protected function createValidator(): TimezoneValidator + { + return new TimezoneValidator(); + } + + public function testNullIsValid() + { + $this->validator->validate(null, new Timezone()); + + $this->assertNoViolation(); + } + + public function testEmptyStringIsValid() + { + $this->validator->validate('', new Timezone()); + + $this->assertNoViolation(); + } + + /** + * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException + */ + public function testExpectsStringCompatibleType() + { + $this->validator->validate(new \stdClass(), new Timezone()); + } + + /** + * @dataProvider getValidTimezones + */ + public function testValidTimezones(string $timezone) + { + $this->validator->validate($timezone, new Timezone()); + + $this->assertNoViolation(); + } + + public function getValidTimezones(): iterable + { + return array( + array('America/Argentina/Buenos_Aires'), + array('America/Barbados'), + array('Antarctica/Syowa'), + array('Africa/Douala'), + array('Atlantic/Canary'), + array('Asia/Gaza'), + array('Europe/Copenhagen'), + ); + } + + /** + * @dataProvider getValidGroupedTimezones + */ + public function testValidGroupedTimezones(string $timezone, int $what) + { + $constraint = new Timezone(array( + 'zone' => $what, + )); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + public function getValidGroupedTimezones(): iterable + { + return array( + array('America/Argentina/Cordoba', \DateTimeZone::AMERICA), + array('America/Barbados', \DateTimeZone::AMERICA), + array('Africa/Cairo', \DateTimeZone::AFRICA), + array('Atlantic/Cape_Verde', \DateTimeZone::ATLANTIC), + array('Europe/Bratislava', \DateTimeZone::EUROPE), + array('Indian/Christmas', \DateTimeZone::INDIAN), + array('Pacific/Kiritimati', \DateTimeZone::ALL), + array('Pacific/Kiritimati', \DateTimeZone::ALL_WITH_BC), + array('Pacific/Kiritimati', \DateTimeZone::PACIFIC), + array('Arctic/Longyearbyen', \DateTimeZone::ARCTIC), + array('Asia/Beirut', \DateTimeZone::ASIA), + array('Atlantic/Bermuda', \DateTimeZone::ASIA | \DateTimeZone::ATLANTIC), + array('Atlantic/Azores', \DateTimeZone::ATLANTIC | \DateTimeZone::ASIA), + ); + } + + /** + * @dataProvider getInvalidTimezones + */ + public function testInvalidTimezonesWithoutZone(string $timezone) + { + $constraint = new Timezone(array( + 'message' => 'myMessage', + )); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$timezone.'"') + ->setCode(Timezone::NO_SUCH_TIMEZONE_ERROR) + ->assertRaised(); + } + + public function getInvalidTimezones(): iterable + { + return array( + array('Buenos_Aires/Argentina/America'), + array('Mayotte/Indian'), + array('foobar'), + ); + } + + /** + * @dataProvider getInvalidGroupedTimezones + */ + public function testInvalidGroupedTimezones(string $timezone, int $what) + { + $constraint = new Timezone(array( + 'zone' => $what, + 'message' => 'myMessage', + )); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$timezone.'"') + ->setCode(Timezone::NO_SUCH_TIMEZONE_IN_ZONE_ERROR) + ->assertRaised(); + } + + public function getInvalidGroupedTimezones(): iterable + { + return array( + array('Antarctica/McMurdo', \DateTimeZone::AMERICA), + array('America/Barbados', \DateTimeZone::ANTARCTICA), + array('Europe/Kiev', \DateTimeZone::ARCTIC), + array('Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN), + array('Asia/Ho_Chi_Minh', \DateTimeZone::INDIAN | \DateTimeZone::ANTARCTICA), + ); + } + + /** + * @dataProvider getValidGroupedTimezonesByCountry + */ + public function testValidGroupedTimezonesByCountry(string $timezone, int $what, string $country) + { + $constraint = new Timezone(array( + 'zone' => $what, + 'countryCode' => $country, + )); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + public function getValidGroupedTimezonesByCountry(): iterable + { + return array( + array('America/Argentina/Cordoba', \DateTimeZone::PER_COUNTRY, 'AR'), + array('America/Barbados', \DateTimeZone::PER_COUNTRY, 'BB'), + array('Africa/Cairo', \DateTimeZone::PER_COUNTRY, 'EG'), + array('Atlantic/Cape_Verde', \DateTimeZone::PER_COUNTRY, 'CV'), + array('Europe/Bratislava', \DateTimeZone::PER_COUNTRY, 'SK'), + array('Indian/Christmas', \DateTimeZone::PER_COUNTRY, 'CX'), + array('Pacific/Kiritimati', \DateTimeZone::PER_COUNTRY, 'KI'), + array('Pacific/Kiritimati', \DateTimeZone::PER_COUNTRY, 'KI'), + array('Pacific/Kiritimati', \DateTimeZone::PER_COUNTRY, 'KI'), + array('Arctic/Longyearbyen', \DateTimeZone::PER_COUNTRY, 'SJ'), + array('Asia/Beirut', \DateTimeZone::PER_COUNTRY, 'LB'), + array('Atlantic/Bermuda', \DateTimeZone::PER_COUNTRY, 'BM'), + array('Atlantic/Azores', \DateTimeZone::PER_COUNTRY, 'PT'), + ); + } + + /** + * @dataProvider getInvalidGroupedTimezonesByCountry + */ + public function testInvalidGroupedTimezonesByCountry(string $timezone, int $what, string $country) + { + $constraint = new Timezone(array( + 'message' => 'myMessage', + 'zone' => $what, + 'countryCode' => $country, + )); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$timezone.'"') + ->setCode(Timezone::NO_SUCH_TIMEZONE_IN_COUNTRY_ERROR) + ->assertRaised(); + } + + public function getInvalidGroupedTimezonesByCountry(): iterable + { + return array( + array('America/Argentina/Cordoba', \DateTimeZone::PER_COUNTRY, 'FR'), + array('America/Barbados', \DateTimeZone::PER_COUNTRY, 'PT'), + ); + } + + /** + * @dataProvider getDeprecatedTimezones + */ + public function testDeprecatedTimezonesAreVaildWithBC(string $timezone) + { + $constraint = new Timezone(array( + 'zone' => \DateTimeZone::ALL_WITH_BC, + )); + + $this->validator->validate($timezone, $constraint); + + $this->assertNoViolation(); + } + + /** + * @dataProvider getDeprecatedTimezones + */ + public function testDeprecatedTimezonesAreInvaildWithoutBC(string $timezone) + { + $constraint = new Timezone(array( + 'message' => 'myMessage', + )); + + $this->validator->validate($timezone, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$timezone.'"') + ->setCode(Timezone::NO_SUCH_TIMEZONE_ERROR) + ->assertRaised(); + } + + public function getDeprecatedTimezones(): iterable + { + return array( + array('America/Buenos_Aires'), + array('Etc/GMT'), + array('US/Pacific'), + ); + } +} 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