You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
6.1KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\validators;
  8. use IntlDateFormatter;
  9. use Yii;
  10. use DateTime;
  11. use yii\helpers\FormatConverter;
  12. /**
  13. * DateValidator verifies if the attribute represents a date, time or datetime in a proper format.
  14. *
  15. * @author Qiang Xue <qiang.xue@gmail.com>
  16. * @author Carsten Brandt <mail@cebe.cc>
  17. * @since 2.0
  18. */
  19. class DateValidator extends Validator
  20. {
  21. /**
  22. * @var string the date format that the value being validated should follow.
  23. * This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
  24. *
  25. * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP Datetime class.
  26. * Please refer to <http://php.net/manual/en/datetime.createfromformat.php> on supported formats.
  27. *
  28. * If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details.
  29. *
  30. * Here are some example values:
  31. *
  32. * ```php
  33. * 'MM/dd/yyyy' // date in ICU format
  34. * 'php:m/d/Y' // the same date in PHP format
  35. * ```
  36. */
  37. public $format;
  38. /**
  39. * @var string the locale ID that is used to localize the date parsing.
  40. * This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
  41. * If not set, the locale of the [[\yii\base\Application::formatter|formatter]] will be used.
  42. * See also [[\yii\i18n\Formatter::locale]].
  43. */
  44. public $locale;
  45. /**
  46. * @var string the timezone to use for parsing date and time values.
  47. * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
  48. * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
  49. * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
  50. * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
  51. */
  52. public $timeZone;
  53. /**
  54. * @var string the name of the attribute to receive the parsing result.
  55. * When this property is not null and the validation is successful, the named attribute will
  56. * receive the parsing result.
  57. */
  58. public $timestampAttribute;
  59. /**
  60. * @var array map of short format names to IntlDateFormatter constant values.
  61. */
  62. private $_dateFormats = [
  63. 'short' => 3, // IntlDateFormatter::SHORT,
  64. 'medium' => 2, // IntlDateFormatter::MEDIUM,
  65. 'long' => 1, // IntlDateFormatter::LONG,
  66. 'full' => 0, // IntlDateFormatter::FULL,
  67. ];
  68. /**
  69. * @inheritdoc
  70. */
  71. public function init()
  72. {
  73. parent::init();
  74. if ($this->message === null) {
  75. $this->message = Yii::t('yii', 'The format of {attribute} is invalid.');
  76. }
  77. if ($this->format === null) {
  78. $this->format = Yii::$app->formatter->dateFormat;
  79. }
  80. if ($this->locale === null) {
  81. $this->locale = Yii::$app->language;
  82. }
  83. if ($this->timeZone === null) {
  84. $this->timeZone = Yii::$app->timeZone;
  85. }
  86. }
  87. /**
  88. * @inheritdoc
  89. */
  90. public function validateAttribute($model, $attribute)
  91. {
  92. $value = $model->$attribute;
  93. $timestamp = $this->parseDateValue($value);
  94. if ($timestamp === false) {
  95. $this->addError($model, $attribute, $this->message, []);
  96. } elseif ($this->timestampAttribute !== null) {
  97. $model->{$this->timestampAttribute} = $timestamp;
  98. }
  99. }
  100. /**
  101. * @inheritdoc
  102. */
  103. protected function validateValue($value)
  104. {
  105. return $this->parseDateValue($value) === false ? [$this->message, []] : null;
  106. }
  107. /**
  108. * Parses date string into UNIX timestamp
  109. *
  110. * @param string $value string representing date
  111. * @return boolean|integer UNIX timestamp or false on failure
  112. */
  113. protected function parseDateValue($value)
  114. {
  115. if (is_array($value)) {
  116. return false;
  117. }
  118. $format = $this->format;
  119. if (strncmp($this->format, 'php:', 4) === 0) {
  120. $format = substr($format, 4);
  121. } else {
  122. if (extension_loaded('intl')) {
  123. if (isset($this->_dateFormats[$format])) {
  124. $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone);
  125. } else {
  126. $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format);
  127. }
  128. // enable strict parsing to avoid getting invalid date values
  129. $formatter->setLenient(false);
  130. // There should not be a warning thrown by parse() but this seems to be the case on windows so we suppress it here
  131. // See https://github.com/yiisoft/yii2/issues/5962 and https://bugs.php.net/bug.php?id=68528
  132. $parsePos = 0;
  133. $parsedDate = @$formatter->parse($value, $parsePos);
  134. if ($parsedDate !== false && $parsePos === mb_strlen($value, Yii::$app ? Yii::$app->charset : 'UTF-8')) {
  135. return $parsedDate;
  136. }
  137. return false;
  138. } else {
  139. // fallback to PHP if intl is not installed
  140. $format = FormatConverter::convertDateIcuToPhp($format, 'date');
  141. }
  142. }
  143. $date = DateTime::createFromFormat($format, $value, new \DateTimeZone($this->timeZone));
  144. $errors = DateTime::getLastErrors();
  145. if ($date === false || $errors['error_count'] || $errors['warning_count']) {
  146. return false;
  147. } else {
  148. // if no time was provided in the format string set time to 0 to get a simple date timestamp
  149. if (strpbrk($format, 'HhGgis') === false) {
  150. $date->setTime(0, 0, 0);
  151. }
  152. return $date->getTimestamp();
  153. }
  154. }
  155. }