Commit ef2f6a08 by Carsten Brandt

Merge pull request #5027 from yiisoft/date-format-convert

[WIP] extracted date format converting to a helper class
parents 01600bf6 3ff548a4
...@@ -32,7 +32,6 @@ $this->title = 'Yii Debugger'; ...@@ -32,7 +32,6 @@ $this->title = 'Yii Debugger';
if (isset($this->context->module->panels['db']) && isset($this->context->module->panels['request'])) { if (isset($this->context->module->panels['db']) && isset($this->context->module->panels['request'])) {
echo " <h1>Available Debug Data</h1>"; echo " <h1>Available Debug Data</h1>";
$timeFormatter = extension_loaded('intl') ? Yii::createObject(['class' => 'yii\i18n\Formatter']) : Yii::$app->formatter;
$codes = []; $codes = [];
foreach ($manifest as $tag => $vals) { foreach ($manifest as $tag => $vals) {
...@@ -66,8 +65,8 @@ if (isset($this->context->module->panels['db']) && isset($this->context->module- ...@@ -66,8 +65,8 @@ if (isset($this->context->module->panels['db']) && isset($this->context->module-
], ],
[ [
'attribute' => 'time', 'attribute' => 'time',
'value' => function ($data) use ($timeFormatter) { 'value' => function ($data) {
return '<span class="nowrap">' . $timeFormatter->asDateTime($data['time'], 'short') . '</span>'; return '<span class="nowrap">' . Yii::$app->formatter->asDateTime($data['time'], 'short') . '</span>';
}, },
'format' => 'html', 'format' => 'html',
], ],
......
...@@ -230,6 +230,7 @@ Yii Framework 2 Change Log ...@@ -230,6 +230,7 @@ Yii Framework 2 Change Log
- Chg #2359: Refactored formatter class. One class with or without intl extension and PHP format pattern as standard (Erik_r, cebe) - Chg #2359: Refactored formatter class. One class with or without intl extension and PHP format pattern as standard (Erik_r, cebe)
- `yii\base\Formatter` functionality has been merged into `yii\i18n\Formatter` - `yii\base\Formatter` functionality has been merged into `yii\i18n\Formatter`
- removed the `yii\base\Formatter` class - removed the `yii\base\Formatter` class
- Chg #1551: Refactored DateValidator to support ICU date format and uses the format defined in Formatter by default (cebe)
- Chg #2380: `yii\widgets\ActiveForm` will register validation js even if there are not fields inside (qiangxue) - Chg #2380: `yii\widgets\ActiveForm` will register validation js even if there are not fields inside (qiangxue)
- Chg #2898: `yii\console\controllers\AssetController` is now using hashes instead of timestamps (samdark) - Chg #2898: `yii\console\controllers\AssetController` is now using hashes instead of timestamps (samdark)
- Chg #2913: RBAC `DbManager` is now initialized via migration (samdark) - Chg #2913: RBAC `DbManager` is now initialized via migration (samdark)
......
...@@ -262,6 +262,14 @@ new ones save the following code as `convert.php` that should be placed in the s ...@@ -262,6 +262,14 @@ new ones save the following code as `convert.php` that should be placed in the s
The specification of the date and time formats is now using the ICU pattern format even if PHP intl extension is not installed. The specification of the date and time formats is now using the ICU pattern format even if PHP intl extension is not installed.
You can prefix a date format with `php:` to use the old format of the PHP `date()`-function. You can prefix a date format with `php:` to use the old format of the PHP `date()`-function.
* The DateValidator has been refactored to use the same format as the Formatter class now (see previous change).
When you use the DateValidator and did not specify a format it will now be what is configured in the formatter class instead of 'Y-m-d'.
To get the old behavior of the DateValidator you have to set the format explicitly in your validation rule:
```php
['attributeName', 'date', 'format' => 'php:Y-m-d'],
```
* `beforeValidate()`, `beforeValidateAll()`, `afterValidate()`, `afterValidateAll()`, `ajaxBeforeSend()` and `ajaxComplete()` * `beforeValidate()`, `beforeValidateAll()`, `afterValidate()`, `afterValidateAll()`, `ajaxBeforeSend()` and `ajaxComplete()`
are removed from `ActiveForm`. The same functionality is now achieved via JavaScript event mechanism like the following: are removed from `ActiveForm`. The same functionality is now achieved via JavaScript event mechanism like the following:
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\helpers;
/**
* FormatConverter provides functionality to convert between different formatting pattern formats.
*
* It provides functions to convert date format patterns between different conventions.
*
* @author Carsten Brandt <mail@cebe.cc>
* @author Enrica Ruedin <e.ruedin@guggach.com>
* @since 2.0
*/
class FormatConverter extends BaseFormatConverter
{
}
...@@ -7,7 +7,7 @@ return [ ...@@ -7,7 +7,7 @@ return [
'messagePath' => __DIR__, 'messagePath' => __DIR__,
// array, required, list of language codes that the extracted messages // array, required, list of language codes that the extracted messages
// should be translated to. For example, ['zh-CN', 'de']. // should be translated to. For example, ['zh-CN', 'de'].
'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'th', 'uk', 'vi', 'zh-CN','zh-TW'], 'languages' => ['ar', 'bg', 'ca', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'th', 'uk', 'vi', 'zh-CN','zh-TW'],
// string, the name of the function for translating messages. // string, the name of the function for translating messages.
// Defaults to 'Yii::t'. This is used as a mark to find the messages to be // Defaults to 'Yii::t'. This is used as a mark to find the messages to be
// translated. You may use a string for single function name or an array for // translated. You may use a string for single function name or an array for
......
...@@ -17,41 +17,7 @@ ...@@ -17,41 +17,7 @@
* NOTE: this file must be saved in UTF-8 encoding. * NOTE: this file must be saved in UTF-8 encoding.
*/ */
return [ return [
'The requested view "{name}" was not found.' => 'Die View-Datei "{name}" konnte nicht gefunden werden.', 'just now' => 'gerade jetzt',
'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{einem Tag} other{# Tagen}}',
'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{einer Minute} other{# Minuten}}',
'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{einem Monat} other{# Monaten}}',
'in {delta, plural, =1{a second} other{# seconds}}' => 'in {delta, plural, =1{einer Sekunde} other{# Sekunden}}',
'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{einem Jahr} other{# Jahren}}',
'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{einer Stunde} other{# Stunden}}',
'{delta, plural, =1{a day} other{# days}} ago' => 'vor {delta, plural, =1{einem Tag} other{# Tagen}}',
'{delta, plural, =1{a minute} other{# minutes}} ago' => 'vor {delta, plural, =1{einer Minute} other{# Minuten}}',
'{delta, plural, =1{a month} other{# months}} ago' => 'vor {delta, plural, =1{einem Monat} other{# Monaten}}',
'{delta, plural, =1{a second} other{# seconds}} ago' => 'vor {delta, plural, =1{einer Sekunde} other{# Sekunden}}',
'{delta, plural, =1{a year} other{# years}} ago' => 'vor {delta, plural, =1{einem Jahr} other{# Jahren}}',
'{delta, plural, =1{an hour} other{# hours}} ago' => 'vor {delta, plural, =1{einer Stunde} other{# Stunden}}',
'{nFormatted} B' => '{nFormatted} B',
'{nFormatted} GB' => '{nFormatted} GB',
'{nFormatted} GiB' => '{nFormatted} GiB',
'{nFormatted} KB' => '{nFormatted} KB',
'{nFormatted} KiB' => '{nFormatted} KiB',
'{nFormatted} MB' => '{nFormatted} MB',
'{nFormatted} MiB' => '{nFormatted} MiB',
'{nFormatted} PB' => '{nFormatted} PB',
'{nFormatted} PiB' => '{nFormatted} PiB',
'{nFormatted} TB' => '{nFormatted} TB',
'{nFormatted} TiB' => '{nFormatted} TiB',
'{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} Byte',
'{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} GibiByte',
'{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} Gigabyte',
'{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} KibiByte',
'{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} Kilobyte',
'{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} MebiByte',
'{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} Megabyte',
'{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} PebiByte',
'{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} Petabyte',
'{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} TebiByte',
'{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} Terabyte',
'(not set)' => '(nicht gesetzt)', '(not set)' => '(nicht gesetzt)',
'An internal server error occurred.' => 'Es ist ein interner Serverfehler aufgetreten.', 'An internal server error occurred.' => 'Es ist ein interner Serverfehler aufgetreten.',
'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?', 'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?',
...@@ -81,6 +47,7 @@ return [ ...@@ -81,6 +47,7 @@ return [
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel breit sein.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel breit sein.',
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel hoch sein.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel hoch sein.',
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel breit sein.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel breit sein.',
'The requested view "{name}" was not found.' => 'Die View-Datei "{name}" konnte nicht gefunden werden.',
'The verification code is incorrect.' => 'Der Prüfcode ist falsch.', 'The verification code is incorrect.' => 'Der Prüfcode ist falsch.',
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Insgesamt <b>{count, number}</b> {count, plural, one{Eintrag} other{Einträge}}.', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'Insgesamt <b>{count, number}</b> {count, plural, one{Eintrag} other{Einträge}}.',
'Unable to verify your data submission.' => 'Es ist nicht möglich, Ihre Dateneingabe zu prüfen.', 'Unable to verify your data submission.' => 'Es ist nicht möglich, Ihre Dateneingabe zu prüfen.',
...@@ -91,6 +58,12 @@ return [ ...@@ -91,6 +58,12 @@ return [
'Yes' => 'Ja', 'Yes' => 'Ja',
'You are not allowed to perform this action.' => 'Sie dürfen diese Aktion nicht durchführen.', 'You are not allowed to perform this action.' => 'Sie dürfen diese Aktion nicht durchführen.',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sie können maximal {limit, number} {limit, plural, one{eine Datei} other{# Dateien}} hochladen.', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sie können maximal {limit, number} {limit, plural, one{eine Datei} other{# Dateien}} hochladen.',
'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{einem Tag} other{# Tagen}}',
'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{einer Minute} other{# Minuten}}',
'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{einem Monat} other{# Monaten}}',
'in {delta, plural, =1{a second} other{# seconds}}' => 'in {delta, plural, =1{einer Sekunde} other{# Sekunden}}',
'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{einem Jahr} other{# Jahren}}',
'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{einer Stunde} other{# Stunden}}',
'the input value' => 'der eingegebene Wert', 'the input value' => 'der eingegebene Wert',
'{attribute} "{value}" has already been taken.' => '{attribute} "{value}" wird bereits verwendet.', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" wird bereits verwendet.',
'{attribute} cannot be blank.' => '{attribute} darf nicht leer sein.', '{attribute} cannot be blank.' => '{attribute} darf nicht leer sein.',
...@@ -113,4 +86,32 @@ return [ ...@@ -113,4 +86,32 @@ return [
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} muss mindestens {min, number} Zeichen enthalten.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} muss mindestens {min, number} Zeichen enthalten.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} darf maximal {max, number} Zeichen enthalten.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} darf maximal {max, number} Zeichen enthalten.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} muss aus genau {length, number} Zeichen bestehen.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} muss aus genau {length, number} Zeichen bestehen.',
'{delta, plural, =1{a day} other{# days}} ago' => 'vor {delta, plural, =1{einem Tag} other{# Tagen}}',
'{delta, plural, =1{a minute} other{# minutes}} ago' => 'vor {delta, plural, =1{einer Minute} other{# Minuten}}',
'{delta, plural, =1{a month} other{# months}} ago' => 'vor {delta, plural, =1{einem Monat} other{# Monaten}}',
'{delta, plural, =1{a second} other{# seconds}} ago' => 'vor {delta, plural, =1{einer Sekunde} other{# Sekunden}}',
'{delta, plural, =1{a year} other{# years}} ago' => 'vor {delta, plural, =1{einem Jahr} other{# Jahren}}',
'{delta, plural, =1{an hour} other{# hours}} ago' => 'vor {delta, plural, =1{einer Stunde} other{# Stunden}}',
'{nFormatted} B' => '{nFormatted} B',
'{nFormatted} GB' => '{nFormatted} GB',
'{nFormatted} GiB' => '{nFormatted} GiB',
'{nFormatted} KB' => '{nFormatted} KB',
'{nFormatted} KiB' => '{nFormatted} KiB',
'{nFormatted} MB' => '{nFormatted} MB',
'{nFormatted} MiB' => '{nFormatted} MiB',
'{nFormatted} PB' => '{nFormatted} PB',
'{nFormatted} PiB' => '{nFormatted} PiB',
'{nFormatted} TB' => '{nFormatted} TB',
'{nFormatted} TiB' => '{nFormatted} TiB',
'{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} Byte',
'{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} GibiByte',
'{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} Gigabyte',
'{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} KibiByte',
'{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} Kilobyte',
'{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} MebiByte',
'{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} Megabyte',
'{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} PebiByte',
'{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} Petabyte',
'{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} TebiByte',
'{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} Terabyte',
]; ];
...@@ -7,23 +7,52 @@ ...@@ -7,23 +7,52 @@
namespace yii\validators; namespace yii\validators;
use IntlDateFormatter;
use Yii; use Yii;
use DateTime; use DateTime;
use yii\helpers\FormatConverter;
/** /**
* DateValidator verifies if the attribute represents a date, time or datetime in a proper format. * DateValidator verifies if the attribute represents a date, time or datetime in a proper format.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class DateValidator extends Validator class DateValidator extends Validator
{ {
/** /**
* @var string the date format that the value being validated should follow. * @var string the date format that the value being validated should follow.
* Please refer to <http://www.php.net/manual/en/datetime.createfromformat.php> on * 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).
* supported formats. *
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP Datetime class.
* Please refer to <http://php.net/manual/en/datetime.createfromformat.php> on supported formats.
*
* If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details.
*
* Here are some example values:
*
* ```php
* 'MM/dd/yyyy' // date in ICU format
* 'php:m/d/Y' // the same date in PHP format
* ```
*/ */
public $format = 'Y-m-d'; public $format;
/**
* @var string the locale ID that is used to localize the date parsing.
* This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
* If not set, the locale of the [[\yii\base\Application::formatter|formatter]] will be used.
* See also [[\yii\i18n\Formatter::locale]].
*/
public $locale;
/**
* @var string the timezone to use for parsing date and time values.
* 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)
* e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
* Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
* If this property is not set, [[\yii\base\Application::timeZone]] will be used.
*/
public $timeZone;
/** /**
* @var string the name of the attribute to receive the parsing result. * @var string the name of the attribute to receive the parsing result.
* When this property is not null and the validation is successful, the named attribute will * When this property is not null and the validation is successful, the named attribute will
...@@ -31,6 +60,16 @@ class DateValidator extends Validator ...@@ -31,6 +60,16 @@ class DateValidator extends Validator
*/ */
public $timestampAttribute; public $timestampAttribute;
/**
* @var array map of short format names to IntlDateFormatter constant values.
*/
private $_dateFormats = [
'short' => 3, // IntlDateFormatter::SHORT,
'medium' => 2, // IntlDateFormatter::MEDIUM,
'long' => 1, // IntlDateFormatter::LONG,
'full' => 0, // IntlDateFormatter::FULL,
];
/** /**
* @inheritdoc * @inheritdoc
...@@ -41,6 +80,15 @@ class DateValidator extends Validator ...@@ -41,6 +80,15 @@ class DateValidator extends Validator
if ($this->message === null) { if ($this->message === null) {
$this->message = Yii::t('yii', 'The format of {attribute} is invalid.'); $this->message = Yii::t('yii', 'The format of {attribute} is invalid.');
} }
if ($this->format === null) {
$this->format = Yii::$app->formatter->dateFormat;
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
if ($this->timeZone === null) {
$this->timeZone = Yii::$app->timeZone;
}
} }
/** /**
...@@ -49,12 +97,11 @@ class DateValidator extends Validator ...@@ -49,12 +97,11 @@ class DateValidator extends Validator
public function validateAttribute($object, $attribute) public function validateAttribute($object, $attribute)
{ {
$value = $object->$attribute; $value = $object->$attribute;
$result = $this->validateValue($value); $timestamp = $this->parseDateValue($value);
if (!empty($result)) { if ($timestamp === false) {
$this->addError($object, $attribute, $result[0], $result[1]); $this->addError($object, $attribute, $this->message, []);
} elseif ($this->timestampAttribute !== null) { } elseif ($this->timestampAttribute !== null) {
$date = DateTime::createFromFormat($this->format, $value); $object->{$this->timestampAttribute} = $timestamp;
$object->{$this->timestampAttribute} = $date->getTimestamp();
} }
} }
...@@ -63,13 +110,42 @@ class DateValidator extends Validator ...@@ -63,13 +110,42 @@ class DateValidator extends Validator
*/ */
protected function validateValue($value) protected function validateValue($value)
{ {
return $this->parseDateValue($value) === false ? [$this->message, []] : null;
}
protected function parseDateValue($value)
{
if (is_array($value)) { if (is_array($value)) {
return [$this->message, []]; return false;
}
$format = $this->format;
if (strncmp($this->format, 'php:', 4) === 0) {
$format = substr($format, 4);
} else {
if (extension_loaded('intl')) {
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format);
}
// enable strict parsing to avoid getting invalid date values
$formatter->setLenient(false);
return $formatter->parse($value);
} else {
// fallback to PHP if intl is not installed
$format = FormatConverter::convertDateIcuToPhp($format, 'date');
}
} }
$date = DateTime::createFromFormat($this->format, $value); $date = DateTime::createFromFormat($format, $value, new \DateTimeZone($this->timeZone));
$errors = DateTime::getLastErrors(); $errors = DateTime::getLastErrors();
$invalid = $date === false || $errors['error_count'] || $errors['warning_count']; if ($date === false || $errors['error_count'] || $errors['warning_count']) {
return false;
return $invalid ? [$this->message, []] : null; } else {
// if no time was provided in the format string set time to 0 to get a simple date timestamp
if (strpbrk($format, 'HhGgis') === false) {
$date->setTime(0, 0, 0);
}
return $date->getTimestamp();
}
} }
} }
<?php
namespace yiiunit\framework\helpers;
use Yii;
use yii\helpers\FormatConverter;
use yii\i18n\Formatter;
use yiiunit\framework\i18n\IntlTestHelper;
use yiiunit\TestCase;
/**
* @group helpers
*/
class FormatConverterTest extends TestCase
{
protected function setUp()
{
parent::setUp();
IntlTestHelper::setIntlStatus($this);
$this->mockApplication([
'timeZone' => 'UTC',
'language' => 'ru-RU',
]);
}
protected function tearDown()
{
parent::tearDown();
IntlTestHelper::resetIntlStatus();
}
public function testIntlIcuToPhpShortForm()
{
$this->assertEquals('n/j/y', FormatConverter::convertDateIcuToPhp('short', 'date', 'en-US'));
$this->assertEquals('d.m.y', FormatConverter::convertDateIcuToPhp('short', 'date', 'de-DE'));
}
public function testIntlOneDigitIcu()
{
$formatter = new Formatter(['locale' => 'en-US']);
$this->assertEquals('24.8.2014', $formatter->asDate('2014-8-24', 'php:d.n.Y'));
$this->assertEquals('24.8.2014', $formatter->asDate('2014-8-24', 'd.M.yyyy'));
}
public function testOneDigitIcu()
{
$formatter = new Formatter(['locale' => 'en-US']);
$this->assertEquals('24.8.2014', $formatter->asDate('2014-8-24', 'php:d.n.Y'));
$this->assertEquals('24.8.2014', $formatter->asDate('2014-8-24', 'd.M.yyyy'));
}
}
<?php <?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
// override information about intl
use yiiunit\framework\i18n\FormatterTest;
function extension_loaded($name)
{
if ($name === 'intl' && FormatterTest::$enableIntl !== null) {
return FormatterTest::$enableIntl;
}
return \extension_loaded($name);
}
namespace yiiunit\framework\i18n; namespace yiiunit\framework\i18n;
use yii\base\InvalidParamException;
use yii\i18n\Formatter; use yii\i18n\Formatter;
use yiiunit\TestCase; use yiiunit\TestCase;
use DateTime; use DateTime;
...@@ -31,8 +12,6 @@ use DateInterval; ...@@ -31,8 +12,6 @@ use DateInterval;
*/ */
class FormatterTest extends TestCase class FormatterTest extends TestCase
{ {
public static $enableIntl;
/** /**
* @var Formatter * @var Formatter
*/ */
...@@ -42,17 +21,7 @@ class FormatterTest extends TestCase ...@@ -42,17 +21,7 @@ class FormatterTest extends TestCase
{ {
parent::setUp(); parent::setUp();
// emulate disabled intl extension IntlTestHelper::setIntlStatus($this);
// enable it only for tests prefixed with testIntl
static::$enableIntl = null;
if (strncmp($this->getName(false), 'testIntl', 8) === 0) {
if (!extension_loaded('intl')) {
$this->markTestSkipped('intl extension is not installed.');
}
static::$enableIntl = true;
} else {
static::$enableIntl = false;
}
$this->mockApplication([ $this->mockApplication([
'timeZone' => 'UTC', 'timeZone' => 'UTC',
...@@ -64,6 +33,7 @@ class FormatterTest extends TestCase ...@@ -64,6 +33,7 @@ class FormatterTest extends TestCase
protected function tearDown() protected function tearDown()
{ {
parent::tearDown(); parent::tearDown();
IntlTestHelper::resetIntlStatus();
$this->formatter = null; $this->formatter = null;
} }
...@@ -202,14 +172,11 @@ class FormatterTest extends TestCase ...@@ -202,14 +172,11 @@ class FormatterTest extends TestCase
public function testAsBoolean() public function testAsBoolean()
{ {
$value = true; $this->assertSame('Yes', $this->formatter->asBoolean(true));
$this->assertSame('Yes', $this->formatter->asBoolean($value)); $this->assertSame('No', $this->formatter->asBoolean(false));
$value = false; $this->assertSame('Yes', $this->formatter->asBoolean("111"));
$this->assertSame('No', $this->formatter->asBoolean($value)); $this->assertSame('No', $this->formatter->asBoolean(""));
$value = "111"; $this->assertSame('No', $this->formatter->asBoolean(0));
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = "";
$this->assertSame('No', $this->formatter->asBoolean($value));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null));
...@@ -233,6 +200,10 @@ class FormatterTest extends TestCase ...@@ -233,6 +200,10 @@ class FormatterTest extends TestCase
$this->assertSame(date('n/j/y', $value), $this->formatter->asDate($value, 'short')); $this->assertSame(date('n/j/y', $value), $this->formatter->asDate($value, 'short'));
$this->assertSame(date('F j, Y', $value), $this->formatter->asDate($value, 'long')); $this->assertSame(date('F j, Y', $value), $this->formatter->asDate($value, 'long'));
// empty input
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(''));
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(0));
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(false));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
} }
...@@ -240,6 +211,12 @@ class FormatterTest extends TestCase ...@@ -240,6 +211,12 @@ class FormatterTest extends TestCase
public function testIntlAsTime() public function testIntlAsTime()
{ {
$this->testAsTime(); $this->testAsTime();
// empty input
$this->formatter->locale = 'de-DE';
$this->assertSame('00:00:00', $this->formatter->asTime(''));
$this->assertSame('00:00:00', $this->formatter->asTime(0));
$this->assertSame('00:00:00', $this->formatter->asTime(false));
} }
public function testAsTime() public function testAsTime()
...@@ -248,6 +225,10 @@ class FormatterTest extends TestCase ...@@ -248,6 +225,10 @@ class FormatterTest extends TestCase
$this->assertSame(date('g:i:s A', $value), $this->formatter->asTime($value)); $this->assertSame(date('g:i:s A', $value), $this->formatter->asTime($value));
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value, 'php:h:i:s A')); $this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value, 'php:h:i:s A'));
// empty input
$this->assertSame('12:00:00 AM', $this->formatter->asTime(''));
$this->assertSame('12:00:00 AM', $this->formatter->asTime(0));
$this->assertSame('12:00:00 AM', $this->formatter->asTime(false));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
} }
...@@ -255,6 +236,12 @@ class FormatterTest extends TestCase ...@@ -255,6 +236,12 @@ class FormatterTest extends TestCase
public function testIntlAsDatetime() public function testIntlAsDatetime()
{ {
$this->testAsDatetime(); $this->testAsDatetime();
// empty input
$this->formatter->locale = 'de-DE';
$this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime(''));
$this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime(0));
$this->assertSame('01.01.1970 00:00:00', $this->formatter->asDatetime(false));
} }
public function testAsDatetime() public function testAsDatetime()
...@@ -263,6 +250,10 @@ class FormatterTest extends TestCase ...@@ -263,6 +250,10 @@ class FormatterTest extends TestCase
$this->assertSame(date('M j, Y g:i:s A', $value), $this->formatter->asDatetime($value)); $this->assertSame(date('M j, Y g:i:s A', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A')); $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'php:Y/m/d h:i:s A'));
// empty input
$this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime(''));
$this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime(0));
$this->assertSame('Jan 1, 1970 12:00:00 AM', $this->formatter->asDatetime(false));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
} }
...@@ -275,13 +266,15 @@ class FormatterTest extends TestCase ...@@ -275,13 +266,15 @@ class FormatterTest extends TestCase
$this->assertSame("$value", $this->formatter->asTimestamp(date('Y-m-d H:i:s', $value))); $this->assertSame("$value", $this->formatter->asTimestamp(date('Y-m-d H:i:s', $value)));
// empty input
$this->assertSame("0", $this->formatter->asTimestamp(0));
$this->assertSame("0", $this->formatter->asTimestamp(false));
$this->assertSame("0", $this->formatter->asTimestamp(""));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTimestamp(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asTimestamp(null));
} }
// TODO test format conversion ICU/PHP
/** /**
* Test for dates before 1970 * Test for dates before 1970
* https://github.com/yiisoft/yii2/issues/3126 * https://github.com/yiisoft/yii2/issues/3126
...@@ -425,6 +418,14 @@ class FormatterTest extends TestCase ...@@ -425,6 +418,14 @@ class FormatterTest extends TestCase
$this->assertSame('in a month', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-03', [$interval_1_month]), $dateNow)); $this->assertSame('in a month', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-03', [$interval_1_month]), $dateNow));
$this->assertSame('in 28 days', $this->formatter->asRelativeTime($dateThen, $dateNow)); $this->assertSame('in 28 days', $this->formatter->asRelativeTime($dateThen, $dateNow));
// just now
$this->assertSame("just now", $this->formatter->asRelativeTime($t = time(), $t));
$this->assertSame("just now", $this->formatter->asRelativeTime(0, 0));
// empty input
$this->assertSame("just now", $this->formatter->asRelativeTime(false, 0));
$this->assertSame("just now", $this->formatter->asRelativeTime("", 0));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time())); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time()));
...@@ -460,7 +461,7 @@ class FormatterTest extends TestCase ...@@ -460,7 +461,7 @@ class FormatterTest extends TestCase
*/ */
public function testAsIntegerException() public function testAsIntegerException()
{ {
$this->assertSame("0", $this->formatter->asInteger('a')); $this->formatter->asInteger('a');
} }
/** /**
...@@ -468,7 +469,23 @@ class FormatterTest extends TestCase ...@@ -468,7 +469,23 @@ class FormatterTest extends TestCase
*/ */
public function testAsIntegerException2() public function testAsIntegerException2()
{ {
$this->assertSame("0", $this->formatter->asInteger('-123abc')); $this->formatter->asInteger('-123abc');
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testAsIntegerException3()
{
$this->formatter->asInteger('');
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testAsIntegerException4()
{
$this->formatter->asInteger(false);
} }
public function testIntlAsDecimal() public function testIntlAsDecimal()
...@@ -696,6 +713,9 @@ class FormatterTest extends TestCase ...@@ -696,6 +713,9 @@ class FormatterTest extends TestCase
$this->formatter->numberFormatterOptions = []; $this->formatter->numberFormatterOptions = [];
$this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3)); $this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3));
// empty values
$this->assertSame('0 B', $this->formatter->asShortSize(0));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
} }
...@@ -723,6 +743,9 @@ class FormatterTest extends TestCase ...@@ -723,6 +743,9 @@ class FormatterTest extends TestCase
$this->formatter->decimalSeparator = ','; $this->formatter->decimalSeparator = ',';
$this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3)); $this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3));
// empty values
$this->assertSame('0 bytes', $this->formatter->asSize(0));
// null display // null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null)); $this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
} }
......
<?php
// override information about intl
namespace yiiunit\framework\i18n {
use yiiunit\TestCase;
class IntlTestHelper {
public static $enableIntl;
/**
* emulate disabled intl extension
*
* enable it only for tests prefixed with testIntl
* @param Testcase $test
*/
public static function setIntlStatus($test)
{
static::$enableIntl = null;
if (strncmp($test->getName(false), 'testIntl', 8) === 0) {
if (!extension_loaded('intl')) {
$test->markTestSkipped('intl extension is not installed.');
}
static::$enableIntl = true;
} else {
static::$enableIntl = false;
}
}
public static function resetIntlStatus()
{
static::$enableIntl = null;
}
}
}
namespace yii\i18n {
use yiiunit\framework\i18n\IntlTestHelper;
if (!function_exists('yii\i18n\extension_loaded')) {
function extension_loaded($name)
{
if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) {
return IntlTestHelper::$enableIntl;
}
return \extension_loaded($name);
}
}
}
namespace yii\helpers {
use yiiunit\framework\i18n\IntlTestHelper;
if (!function_exists('yii\helpers\extension_loaded')) {
function extension_loaded($name)
{
if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) {
return IntlTestHelper::$enableIntl;
}
return \extension_loaded($name);
}
}
}
namespace yii\validators {
use yiiunit\framework\i18n\IntlTestHelper;
if (!function_exists('yii\validators\extension_loaded')) {
function extension_loaded($name)
{
if ($name === 'intl' && IntlTestHelper::$enableIntl !== null) {
return IntlTestHelper::$enableIntl;
}
return \extension_loaded($name);
}
}
}
...@@ -5,6 +5,7 @@ namespace yiiunit\framework\validators; ...@@ -5,6 +5,7 @@ namespace yiiunit\framework\validators;
use DateTime; use DateTime;
use yii\validators\DateValidator; use yii\validators\DateValidator;
use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\data\validators\models\FakedValidationModel;
use yiiunit\framework\i18n\IntlTestHelper;
use yiiunit\TestCase; use yiiunit\TestCase;
/** /**
...@@ -15,7 +16,19 @@ class DateValidatorTest extends TestCase ...@@ -15,7 +16,19 @@ class DateValidatorTest extends TestCase
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication();
IntlTestHelper::setIntlStatus($this);
$this->mockApplication([
'timeZone' => 'UTC',
'language' => 'ru-RU',
]);
}
protected function tearDown()
{
parent::tearDown();
IntlTestHelper::resetIntlStatus();
} }
public function testEnsureMessageIsSet() public function testEnsureMessageIsSet()
...@@ -24,26 +37,79 @@ class DateValidatorTest extends TestCase ...@@ -24,26 +37,79 @@ class DateValidatorTest extends TestCase
$this->assertTrue($val->message !== null && strlen($val->message) > 1); $this->assertTrue($val->message !== null && strlen($val->message) > 1);
} }
public function testIntlValidateValue()
{
$this->testValidateValue();
$this->mockApplication([
'language' => 'en-GB',
'components' => [
'formatter' => [
'dateFormat' => 'short',
]
]
]);
$val = new DateValidator();
$this->assertTrue($val->validate('31/5/2017'));
$this->assertFalse($val->validate('5/31/2017'));
$val = new DateValidator(['format' => 'short', 'locale' => 'en-GB']);
$this->assertTrue($val->validate('31/5/2017'));
$this->assertFalse($val->validate('5/31/2017'));
$this->mockApplication([
'language' => 'de-DE',
'components' => [
'formatter' => [
'dateFormat' => 'short',
]
]
]);
$val = new DateValidator();
$this->assertTrue($val->validate('31.5.2017'));
$this->assertFalse($val->validate('5.31.2017'));
$val = new DateValidator(['format' => 'short', 'locale' => 'de-DE']);
$this->assertTrue($val->validate('31.5.2017'));
$this->assertFalse($val->validate('5.31.2017'));
}
public function testValidateValue() public function testValidateValue()
{ {
$val = new DateValidator; // test PHP format
$val = new DateValidator(['format' => 'php:Y-m-d']);
$this->assertFalse($val->validate('3232-32-32')); $this->assertFalse($val->validate('3232-32-32'));
$this->assertTrue($val->validate('2013-09-13')); $this->assertTrue($val->validate('2013-09-13'));
$this->assertFalse($val->validate('31.7.2013')); $this->assertFalse($val->validate('31.7.2013'));
$this->assertFalse($val->validate('31-7-2013')); $this->assertFalse($val->validate('31-7-2013'));
$this->assertFalse($val->validate(time())); $this->assertFalse($val->validate(time()));
$val->format = 'U'; $val->format = 'php:U';
$this->assertTrue($val->validate(time())); $this->assertTrue($val->validate(time()));
$val->format = 'd.m.Y'; $val->format = 'php:d.m.Y';
$this->assertTrue($val->validate('31.7.2013'));
$val->format = 'php:Y-m-!d H:i:s';
$this->assertTrue($val->validate('2009-02-15 15:16:17'));
// test ICU format
$val = new DateValidator(['format' => 'yyyy-MM-dd']);
$this->assertFalse($val->validate('3232-32-32'));
$this->assertTrue($val->validate('2013-09-13'));
$this->assertFalse($val->validate('31.7.2013'));
$this->assertFalse($val->validate('31-7-2013'));
$this->assertFalse($val->validate(time()));
$val->format = 'dd.MM.yyyy';
$this->assertTrue($val->validate('31.7.2013')); $this->assertTrue($val->validate('31.7.2013'));
$val->format = 'Y-m-!d H:i:s'; $val->format = 'yyyy-MM-dd HH:mm:ss';
$this->assertTrue($val->validate('2009-02-15 15:16:17')); $this->assertTrue($val->validate('2009-02-15 15:16:17'));
} }
public function testValidateAttribute() public function testIntlValidateAttributePHP()
{
$this->testValidateAttributePHPFormat();
}
public function testValidateAttributePHPFormat()
{ {
// error-array-add // error-array-add
$val = new DateValidator; $val = new DateValidator(['format' => 'php:Y-m-d']);
$model = new FakedValidationModel; $model = new FakedValidationModel;
$model->attr_date = '2013-09-13'; $model->attr_date = '2013-09-13';
$val->validateAttribute($model, 'attr_date'); $val->validateAttribute($model, 'attr_date');
...@@ -53,7 +119,7 @@ class DateValidatorTest extends TestCase ...@@ -53,7 +119,7 @@ class DateValidatorTest extends TestCase
$val->validateAttribute($model, 'attr_date'); $val->validateAttribute($model, 'attr_date');
$this->assertTrue($model->hasErrors('attr_date')); $this->assertTrue($model->hasErrors('attr_date'));
//// timestamp attribute //// timestamp attribute
$val = new DateValidator(['timestampAttribute' => 'attr_timestamp']); $val = new DateValidator(['format' => 'php:Y-m-d', 'timestampAttribute' => 'attr_timestamp']);
$model = new FakedValidationModel; $model = new FakedValidationModel;
$model->attr_date = '2013-09-13'; $model->attr_date = '2013-09-13';
$model->attr_timestamp = true; $model->attr_timestamp = true;
...@@ -61,10 +127,48 @@ class DateValidatorTest extends TestCase ...@@ -61,10 +127,48 @@ class DateValidatorTest extends TestCase
$this->assertFalse($model->hasErrors('attr_date')); $this->assertFalse($model->hasErrors('attr_date'));
$this->assertFalse($model->hasErrors('attr_timestamp')); $this->assertFalse($model->hasErrors('attr_timestamp'));
$this->assertEquals( $this->assertEquals(
DateTime::createFromFormat($val->format, '2013-09-13')->getTimestamp(), mktime(0, 0, 0, 9, 13, 2013), // 2013-09-13
// DateTime::createFromFormat('Y-m-d', '2013-09-13')->getTimestamp(),
$model->attr_timestamp $model->attr_timestamp
); );
$val = new DateValidator(); $val = new DateValidator(['format' => 'php:Y-m-d']);
$model = FakedValidationModel::createWithAttributes(['attr_date' => []]);
$val->validateAttribute($model, 'attr_date');
$this->assertTrue($model->hasErrors('attr_date'));
}
public function testIntlValidateAttributeICU()
{
$this->testValidateAttributeICUFormat();
}
public function testValidateAttributeICUFormat()
{
// error-array-add
$val = new DateValidator(['format' => 'yyyy-MM-dd']);
$model = new FakedValidationModel;
$model->attr_date = '2013-09-13';
$val->validateAttribute($model, 'attr_date');
$this->assertFalse($model->hasErrors('attr_date'));
$model = new FakedValidationModel;
$model->attr_date = '1375293913';
$val->validateAttribute($model, 'attr_date');
$this->assertTrue($model->hasErrors('attr_date'));
//// timestamp attribute
$val = new DateValidator(['format' => 'yyyy-MM-dd', 'timestampAttribute' => 'attr_timestamp']);
$model = new FakedValidationModel;
$model->attr_date = '2013-09-13';
$model->attr_timestamp = true;
$val->validateAttribute($model, 'attr_date');
$this->assertFalse($model->hasErrors('attr_date'));
$this->assertFalse($model->hasErrors('attr_timestamp'));
$this->assertEquals(
mktime(0, 0, 0, 9, 13, 2013), // 2013-09-13
// DateTime::createFromFormat('Y-m-d', '2013-09-13')->getTimestamp(),
$model->attr_timestamp
);
$val = new DateValidator(['format' => 'yyyy-MM-dd']);
$model = FakedValidationModel::createWithAttributes(['attr_date' => []]); $model = FakedValidationModel::createWithAttributes(['attr_date' => []]);
$val->validateAttribute($model, 'attr_date'); $val->validateAttribute($model, 'attr_date');
$this->assertTrue($model->hasErrors('attr_date')); $this->assertTrue($model->hasErrors('attr_date'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment