TimezoneValidator.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Validator\Constraints;
  11. use Symfony\Component\Intl\Exception\MissingResourceException;
  12. use Symfony\Component\Intl\Timezones;
  13. use Symfony\Component\Validator\Constraint;
  14. use Symfony\Component\Validator\ConstraintValidator;
  15. use Symfony\Component\Validator\Exception\UnexpectedTypeException;
  16. use Symfony\Component\Validator\Exception\UnexpectedValueException;
  17. /**
  18. * Validates whether a value is a valid timezone identifier.
  19. *
  20. * @author Javier Spagnoletti <phansys@gmail.com>
  21. * @author Hugo Hamon <hugohamon@neuf.fr>
  22. */
  23. class TimezoneValidator extends ConstraintValidator
  24. {
  25. /**
  26. * {@inheritdoc}
  27. */
  28. public function validate($value, Constraint $constraint)
  29. {
  30. if (!$constraint instanceof Timezone) {
  31. throw new UnexpectedTypeException($constraint, Timezone::class);
  32. }
  33. if (null === $value || '' === $value) {
  34. return;
  35. }
  36. if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
  37. throw new UnexpectedValueException($value, 'string');
  38. }
  39. $value = (string) $value;
  40. if ($constraint->intlCompatible && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($value)->getID()) {
  41. $this->context->buildViolation($constraint->message)
  42. ->setParameter('{{ value }}', $this->formatValue($value))
  43. ->setCode(Timezone::TIMEZONE_IDENTIFIER_INTL_ERROR)
  44. ->addViolation();
  45. return;
  46. }
  47. if (
  48. \in_array($value, self::getPhpTimezones($constraint->zone, $constraint->countryCode), true) ||
  49. \in_array($value, self::getIntlTimezones($constraint->zone, $constraint->countryCode), true)
  50. ) {
  51. return;
  52. }
  53. if ($constraint->countryCode) {
  54. $code = Timezone::TIMEZONE_IDENTIFIER_IN_COUNTRY_ERROR;
  55. } elseif (\DateTimeZone::ALL !== $constraint->zone) {
  56. $code = Timezone::TIMEZONE_IDENTIFIER_IN_ZONE_ERROR;
  57. } else {
  58. $code = Timezone::TIMEZONE_IDENTIFIER_ERROR;
  59. }
  60. $this->context->buildViolation($constraint->message)
  61. ->setParameter('{{ value }}', $this->formatValue($value))
  62. ->setCode($code)
  63. ->addViolation();
  64. }
  65. private static function getPhpTimezones(int $zone, string $countryCode = null): array
  66. {
  67. if (null !== $countryCode) {
  68. try {
  69. return @\DateTimeZone::listIdentifiers($zone, $countryCode) ?: [];
  70. } catch (\ValueError $e) {
  71. return [];
  72. }
  73. }
  74. return \DateTimeZone::listIdentifiers($zone);
  75. }
  76. private static function getIntlTimezones(int $zone, string $countryCode = null): array
  77. {
  78. if (!class_exists(Timezones::class)) {
  79. return [];
  80. }
  81. if (null !== $countryCode) {
  82. try {
  83. return Timezones::forCountryCode($countryCode);
  84. } catch (MissingResourceException $e) {
  85. return [];
  86. }
  87. }
  88. $timezones = Timezones::getIds();
  89. if (\DateTimeZone::ALL === (\DateTimeZone::ALL & $zone)) {
  90. return $timezones;
  91. }
  92. $filtered = [];
  93. foreach ((new \ReflectionClass(\DateTimeZone::class))->getConstants() as $const => $flag) {
  94. if ($flag !== ($flag & $zone)) {
  95. continue;
  96. }
  97. $filtered[] = array_filter($timezones, static function ($id) use ($const) {
  98. return 0 === stripos($id, $const.'/');
  99. });
  100. }
  101. return $filtered ? array_merge(...$filtered) : [];
  102. }
  103. }