Commit 0e4da438 by Qiang Xue

Merge pull request #3906 from cebe/Erik-r-2359-formatter-refactored

[WIP] formatter refactored
parents 38476309 2b38d6ab
......@@ -141,7 +141,7 @@ Data column is for displaying and sorting data. It is default column type so spe
using it.
The main setting of the data column is its format. It could be specified via `format` attribute. Its values are
corresponding to methods in `format` application component that is [[\yii\base\Formatter|Formatter]] by default:
corresponding to methods in `format` application component that is [[\yii\i18n\Formatter|Formatter]] by default:
```php
<?= GridView::widget([
......@@ -158,34 +158,12 @@ corresponding to methods in `format` application component that is [[\yii\base\F
]); ?>
```
In the above `text` corresponds to [[\yii\base\Formatter::asText()]]. The value of the column is passed as the first
argument. In the second column definition `date` corresponds to [[\yii\base\Formatter::asDate()]]. The value of the
In the above `text` corresponds to [[\yii\i18n\Formatter::asText()]]. The value of the column is passed as the first
argument. In the second column definition `date` corresponds to [[\yii\i18n\Formatter::asDate()]]. The value of the
column is, again, passed as the first argument while 'Y-m-d' is used as the second argument value.
Here's the bundled formatters list:
- [[\yii\base\Formatter::asRaw()|raw]] - the value is outputted as is.
- [[\yii\base\Formatter::asText()|text]] - the value is HTML-encoded. This format is used by default.
- [[\yii\base\Formatter::asNtext()|ntext]] - the value is formatted as an HTML-encoded plain text with newlines converted
into line breaks.
- [[\yii\base\Formatter::asParagraphs()|paragraphs]] - the value is formatted as HTML-encoded text paragraphs wrapped
into `<p>` tags.
- [[\yii\base\Formatter::asHtml()|html]] - the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can
pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`.
- [[\yii\base\Formatter::asEmail()|email]] - the value is formatted as a mailto link.
- [[\yii\base\Formatter::asImage()|image]] - the value is formatted as an image tag.
- [[\yii\base\Formatter::asUrl()|url]] - the value is formatted as a hyperlink.
- [[\yii\base\Formatter::asBoolean()|boolean]] - the value is formatted as a boolean. You can set what's rendered for
true and false values by calling `Yii::$app->formatter->booleanFormat = ['No', 'Yes'];` before outputting GridView.
- [[\yii\base\Formatter::asDate()|date]] - the value is formatted as date.
- [[\yii\base\Formatter::asTime()|time]] - the value is formatted as time.
- [[\yii\base\Formatter::asDatetime()|datetime]] - the value is formatted as datetime.
- [[\yii\base\Formatter::asInteger()|integer]] - the value is formatted as an integer.
- [[\yii\base\Formatter::asDouble()|double]] - the value is formatted as a double number.
- [[\yii\base\Formatter::asNumber()|number]] - the value is formatted as a number with decimal and thousand separators.
- [[\yii\base\Formatter::asSize()|size]] - the value that is a number of bytes is formatted as a human readable size.
- [[\yii\base\Formatter::asRelativeTime()|relativeTime]] - the value is formatted as the time interval between a date
and now in human readable form.
For a list of available formatters see the [section about Data Formatting](output-formatter.md).
#### Action column
......
Data Formatter
==============
For formatting of outputs Yii provides a formatter class to make date more readable for users.
[[yii\i18n\Formatter]] is a helper class that is registered as an [application component](concept-components.md) name `formatter` by default.
It provides a set of methods for data formatting purpose such as date/time values, numbers and other commonly used formats in a localized way.
The formatter can be used in two different ways.
1. Using the formatting methods(all formatter methods prefixed with `as`) directly:
```php
echo Yii::$app->formatter->asDate('2014-01-01', 'long'); // output: January 1, 2014
echo Yii::$app->formatter->asPercent(0.125, 2); // output: 12.50%
echo Yii::$app->formatter->asEmail('cebe@example.com'); // output: <a href="mailto:cebe@example.com">cebe@example.com</a>
echo Yii::$app->formatter->asBoolean(true); // output: Yes
// it also handles display of null values:
echo Yii::$app->formatter->asDate(null); // output: (Not set)
```
2. Using the [[yii\i18n\Formatter::format()|format()]] method using the format name.
This method is used by classes like GridView and DetailView where you can specify the data format of a column in the
widget config.
```php
echo Yii::$app->formatter->format('2014-01-01', 'date'); // output: January 1, 2014
// you can also use an array to specify parameters for the format method:
// `2` is the value for the $decimals parameter of the asPercent()-method.
echo Yii::$app->formatter->format(0.125, ['percent', 2]); // output: 12.50%
```
All output of the formatter is localized when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
You can configure the [[yii\i18n\Formatter::locale|locale]] property of the formatter for this. If not configured, the
application [[yii\base\Application::language|language]] is used as the locale. See the [Section on internationaization](tutorial-i18n.md) for more details.
The Formatter will then choose the correct format for dates and number according to the locale including names of month and
week days translated to the current language. Date formats are also affected by the [[yii\i18n\Formatter::timeZone|timeZone]]
which will also be taken [[yii\base\Application::timeZone|from the application]] by default.
For example the date format call will output different results for different locales:
```php
Yii::$app->formatter->locale = 'en-US';
echo Yii::$app->formatter->asDate('2014-01-01'); // output: January 1, 2014
Yii::$app->formatter->locale = 'de-DE';
echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1. Januar 2014
Yii::$app->formatter->locale = 'ru-RU';
echo Yii::$app->formatter->asDate('2014-01-01'); // output: 1 января 2014 г.
```
> Note that formatting may differ between different versions of the ICU library compiled with PHP and also based on the fact whether the
> [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed or not. So to ensure your website works with the same output
> in all environments it is recommended to install the PHP intl extension in all environments and verify that the version of the ICU library
> is the same.
Configuring the format
----------------------
The default format of the Formatter class can be adjusted using the properties of the formatter class.
You can adjust these values application wide by configuring the `formatter` component in your [application config](concept-configurations.md#application-configurations)
an example configuration is shown in the following.
For more details about certain properties check out the [[yii\i18n\Formatter|API documentation of the Formatter class]].
```php
'components' => [
'formatter' => [
'dateFormat' => 'dd.MM.yyyy',
'decimalSeparator' => ',',
'thousandSeparator' => ' ',
'currencyCode' => 'EUR',
];
```
Formatting Dates
----------------
TDB
See http://site.icu-project.org/ for the format.
- [[\yii\i18n\Formatter::asDate()|date]] - the value is formatted as date.
- [[\yii\i18n\Formatter::asTime()|time]] - the value is formatted as time.
- [[\yii\i18n\Formatter::asDatetime()|datetime]] - the value is formatted as datetime.
- [[\yii\i18n\Formatter::asTimestamp()|timestamp]] - the value is formatted as a unix timestamp.
- [[\yii\i18n\Formatter::asRelativeTime()|relativeTime]] - the value is formatted as the time interval between a date
and now in human readable form.
Formatting Numbers
------------------
TDB
See http://site.icu-project.org/ for the format.
- [[\yii\i18n\Formatter::asInteger()|integer]] - the value is formatted as an integer.
- [[\yii\i18n\Formatter::asDecimal()|decimal]] - the value is formatted as a number with decimal and thousand separators.
- [[\yii\i18n\Formatter::asPercent()|percent]] - the value is formatted as a percent number.
- [[\yii\i18n\Formatter::asScientific()|scientific]] - the value is formatted as a number in scientific format.
- [[\yii\i18n\Formatter::asCurrency()|currency]] - the value is formatted as a currency value.
- [[\yii\i18n\Formatter::asSize()|size]] - the value that is a number of bytes is formatted as a human readable size.
- [[\yii\i18n\Formatter::asShortSize()|shortSize]] - the value that is a number of bytes is formatted as a human readable size.
Other formatters
----------------
TDB
Here's the bundled formatters list:
- [[\yii\i18n\Formatter::asRaw()|raw]] - the value is outputted as is.
- [[\yii\i18n\Formatter::asText()|text]] - the value is HTML-encoded. This format is used by default.
- [[\yii\i18n\Formatter::asNtext()|ntext]] - the value is formatted as an HTML-encoded plain text with newlines converted
into line breaks.
- [[\yii\i18n\Formatter::asParagraphs()|paragraphs]] - the value is formatted as HTML-encoded text paragraphs wrapped
into `<p>` tags.
- [[\yii\i18n\Formatter::asHtml()|html]] - the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can
pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`.
- [[\yii\i18n\Formatter::asEmail()|email]] - the value is formatted as a mailto link.
- [[\yii\i18n\Formatter::asImage()|image]] - the value is formatted as an image tag.
- [[\yii\i18n\Formatter::asUrl()|url]] - the value is formatted as a hyperlink.
- [[\yii\i18n\Formatter::asBoolean()|boolean]] - the value is formatted as a boolean. You can set what's rendered for
true and false values by calling `Yii::$app->formatter->booleanFormat = ['No', 'Yes'];` before outputting GridView.
......@@ -94,7 +94,7 @@ if you do not specify its class, the default one will be used.
Please refer to the [Data Access Objects](db-dao.md) section for more details.
* [[yii\base\Application::errorHandler|errorHandler]]: handles PHP errors and exceptions.
Please refer to the [Handling Errors](tutorial-handling-errors.md) section for more details.
* [[yii\base\Formatter|formatter]]: formats data when they are displayed to end users. For example, a number
* [[yii\i18n\Formatter|formatter]]: formats data when they are displayed to end users. For example, a number
may be displayed with thousand separator, a date may be formatted in long format.
Please refer to the [Data Formatting](output-formatting.md) section for more details.
* [[yii\i18n\I18N|i18n]]: supports message translation and formatting.
......
......@@ -308,7 +308,7 @@ content to end users, you should try to [internationalize and localize](tutorial
- If the extension displays messages intended for end users, the messages should be wrapped into `Yii::t()`
so that they can be translated. Messages meant for developers (such as internal exception messages) do not need
to be translated.
- If the extension displays numbers, dates, etc., they should be formatted using [[yii\base\Formatter]] with
- If the extension displays numbers, dates, etc., they should be formatted using [[yii\i18n\Formatter]] with
appropriate formatting rules.
For more details, please refer to the [Internationalization](tutorial-i18n.md) section.
......
......@@ -478,23 +478,4 @@ put there file for russian language as follows `views/site/ru-RU/index.php`.
i18n formatter
--------------
i18n formatter component is the localized version of formatter that supports formatting of date, time and numbers based
on current locale. In order to use it you need to configure formatter application component as follows:
```php
return [
// ...
'components' => [
'formatter' => [
'class' => 'yii\i18n\Formatter',
],
],
];
```
After configuring the component can be accessed as `Yii::$app->formatter`.
Note that in order to use i18n formatter you need to install and enable
[intl](http://www.php.net/manual/en/intro.intl.php) PHP extension.
In order to learn about formatter methods refer to its API documentation: [[yii\i18n\Formatter]].
See the [Date Formatter section](output-formatter.md) for details.
......@@ -217,6 +217,9 @@ Yii Framework 2 Change Log
- Enh: ListView now uses the widget ID in the base tag, consistent to gridview (cebe)
- Enh: Added `yii\web\Response::enableCsrfCookie` to support storing CSRF tokens in session (qiangxue)
- Chg #2287: Split `yii\db\ColumnSchema::typecast()` into two methods `phpTypecast()` and `dbTypecast()` to allow specifying PDO type explicitly (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`
- removed the `yii\base\Formatter` class
- 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 #2913: RBAC `DbManager` is now initialized via migration (samdark)
......
......@@ -214,6 +214,36 @@ new ones save the following code as `convert.php` that should be placed in the s
tag around each radio/checkbox when you specify labels for them. You should manually render such container tags,
or set the `item` option for `Html::radioList()`, `Html::checkboxList()` to generate the container tags.
* The formatter class has been refactored to have only one class regardless whether PHP intl extension is installed or not.
Functionality of `yii\base\Formatter` has been merged into `yii\i18n\Formatter` and `yii\base\Formatter` has been
removed so you have to replace all usage of `yii\base\Formatter` with `yii\i18n\Formatter` in your code.
Also the API of the Formatter class has changed in many ways.
The signature of the following Methods has changed:
- `asDate`
- `asTime`
- `asDateTime`
- `asSize` has been split up into `asSize` and `asShortSize`
- `asCurrency`
- `asDecimal`
- `asPercent`
- `asScientific`
The following methods have been removed, this also means that the corresponding format which may be used by a
GridView or DetailView is not available anymore:
- `asNumber`
- `asDouble`
Also due to these changes some formatting defaults have changes so you have to check all your GridView and DetailView
configuration and make sure the formatting is displayed correctly.
The configuration for `asSize()` has changed. It now uses the configuration for the number formatting from intl
and only the base is configured using `$sizeFormatBase`.
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.
* `beforeValidate()`, `beforeValidateAll()`, `afterValidate()`, `afterValidateAll()`, `ajaxBeforeSend()` and `ajaxComplete()`
are removed from `ActiveForm`. The same functionality is now achieved via JavaScript event mechanism. For example,
if you want to do something before performing validation on the client side, you can write the following
......@@ -228,3 +258,4 @@ new ones save the following code as `convert.php` that should be placed in the s
}
});
```
......@@ -22,7 +22,7 @@ use Yii;
* @property \yii\db\Connection $db The database connection. This property is read-only.
* @property \yii\web\ErrorHandler|\yii\console\ErrorHandler $errorHandler The error handler application
* component. This property is read-only.
* @property \yii\base\Formatter $formatter The formatter application component. This property is read-only.
* @property \yii\i18n\Formatter $formatter The formatter application component. This property is read-only.
* @property \yii\i18n\I18N $i18n The internationalization application component. This property is read-only.
* @property \yii\log\Dispatcher $log The log dispatcher application component. This property is read-only.
* @property \yii\mail\MailerInterface $mailer The mailer application component. This property is read-only.
......@@ -514,7 +514,7 @@ abstract class Application extends Module
/**
* Returns the formatter component.
* @return \yii\base\Formatter the formatter application component.
* @return \yii\i18n\Formatter the formatter application component.
*/
public function getFormatter()
{
......@@ -612,7 +612,7 @@ abstract class Application extends Module
return [
'log' => ['class' => 'yii\log\Dispatcher'],
'view' => ['class' => 'yii\web\View'],
'formatter' => ['class' => 'yii\base\Formatter'],
'formatter' => ['class' => 'yii\i18n\Formatter'],
'i18n' => ['class' => 'yii\i18n\I18N'],
'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
'urlManager' => ['class' => 'yii\web\UrlManager'],
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
use Yii;
use DateTime;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
/**
* Formatter provides a set of commonly used data formatting methods.
*
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
* The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
*
* Formatter is configured as an application component in [[\yii\base\Application]] by default.
* You can access that instance via `Yii::$app->formatter`.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Formatter extends Component
{
/**
* @var string the timezone to use for formatting time and date 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 default format string to be used to format a date using PHP date() function.
*/
public $dateFormat = 'Y-m-d';
/**
* @var string the default format string to be used to format a time using PHP date() function.
*/
public $timeFormat = 'H:i:s';
/**
* @var string the default format string to be used to format a date and time using PHP date() function.
*/
public $datetimeFormat = 'Y-m-d H:i:s';
/**
* @var string the text to be displayed when formatting a null. Defaults to '<span class="not-set">(not set)</span>'.
*/
public $nullDisplay;
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text display for false, the second element for true. Defaults to `['No', 'Yes']`.
*/
public $booleanFormat;
/**
* @var string the character displayed as the decimal point when formatting a number.
* If not set, "." will be used.
*/
public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
* If not set, "," will be used.
*/
public $thousandSeparator;
/**
* @var array the format used to format size (bytes). Three elements may be specified: "base", "decimals" and "decimalSeparator".
* They correspond to the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte, defaults to 1024),
* the number of digits after the decimal point (defaults to 2) and the character displayed as the decimal point.
*/
public $sizeFormat = [
'base' => 1024,
'decimals' => 2,
'decimalSeparator' => null,
];
/**
* Initializes the component.
*/
public function init()
{
if ($this->timeZone === null) {
$this->timeZone = Yii::$app->timeZone;
}
if (empty($this->booleanFormat)) {
$this->booleanFormat = [Yii::t('yii', 'No'), Yii::t('yii', 'Yes')];
}
if ($this->nullDisplay === null) {
$this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)') . '</span>';
}
}
/**
* Formats the value based on the given format type.
* This method will call one of the "as" methods available in this class to do the formatting.
* For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
* then [[asHtml()]] will be used. Format names are case insensitive.
* @param mixed $value the value to be formatted
* @param string|array $format the format of the value, e.g., "html", "text". To specify additional
* parameters of the formatting method, you may use an array. The first element of the array
* specifies the format name, while the rest of the elements will be used as the parameters to the formatting
* method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
* @return string the formatting result
* @throws InvalidParamException if the type is not supported by this class.
*/
public function format($value, $format)
{
if (is_array($format)) {
if (!isset($format[0])) {
throw new InvalidParamException('The $format array must contain at least one element.');
}
$f = $format[0];
$format[0] = $value;
$params = $format;
$format = $f;
} else {
$params = [$value];
}
$method = 'as' . $format;
if ($this->hasMethod($method)) {
return call_user_func_array([$this, $method], $params);
} else {
throw new InvalidParamException("Unknown type: $format");
}
}
/**
* Formats the value as is without any formatting.
* This method simply returns back the parameter without any format.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asRaw($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return $value;
}
/**
* Formats the value as an HTML-encoded plain text.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asText($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::encode($value);
}
/**
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asNtext($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return nl2br(Html::encode($value));
}
/**
* Formats the value as HTML-encoded text paragraphs.
* Each text paragraph is enclosed within a `<p>` tag.
* One or multiple consecutive empty lines divide two paragraphs.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asParagraphs($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return str_replace('<p></p>', '', '<p>' . preg_replace('/[\r\n]{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>');
}
/**
* Formats the value as HTML text.
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
* Use [[asRaw()]] if you do not want any purification of the value.
* @param mixed $value the value to be formatted
* @param array|null $config the configuration for the HTMLPurifier class.
* @return string the formatted result
*/
public function asHtml($value, $config = null)
{
if ($value === null) {
return $this->nullDisplay;
}
return HtmlPurifier::process($value, $config);
}
/**
* Formats the value as a mailto link.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asEmail($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::mailto(Html::encode($value), $value);
}
/**
* Formats the value as an image tag.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asImage($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::img($value);
}
/**
* Formats the value as a hyperlink.
* @param mixed $value the value to be formatted
* @return string the formatted result
*/
public function asUrl($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$url = $value;
if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
$url = 'http://' . $url;
}
return Html::a(Html::encode($value), $url);
}
/**
* Formats the value as a boolean.
* @param mixed $value the value to be formatted
* @return string the formatted result
* @see booleanFormat
*/
public function asBoolean($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
}
/**
* Formats the value as a date.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see dateFormat
*/
public function asDate($value, $format = null)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
return $this->formatTimestamp($value, $format === null ? $this->dateFormat : $format);
}
/**
* Formats the value as a time.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[timeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see timeFormat
*/
public function asTime($value, $format = null)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
return $this->formatTimestamp($value, $format === null ? $this->timeFormat : $format);
}
/**
* Formats the value as a datetime.
* @param integer|string|DateTime $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()`
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[datetimeFormat]] will be used. The format string should be one
* that can be recognized by the PHP `date()` function.
* @return string the formatted result
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
return $this->formatTimestamp($value, $format === null ? $this->datetimeFormat : $format);
}
/**
* Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
*
* @param mixed $value the datetime value to be normalized.
* @return integer the normalized datetime value
*/
protected function normalizeDatetimeValue($value)
{
if (is_string($value)) {
if (is_numeric($value) || $value === '') {
$value = (double)$value;
} else {
try {
$date = new DateTime($value);
} catch (\Exception $e) {
return false;
}
$value = (double)$date->format('U');
}
return $value;
} elseif ($value instanceof DateTime || $value instanceof \DateTimeInterface) {
return (double)$value->format('U');
} else {
return (double)$value;
}
}
/**
* @param integer $value normalized datetime value
* @param string $format the format used to convert the value into a date string.
* @return string the formatted result
*/
protected function formatTimestamp($value, $format)
{
$date = new DateTime(null, new \DateTimeZone($this->timeZone));
$date->setTimestamp($value);
return $date->format($format);
}
/**
* Formats the value as an integer.
* @param mixed $value the value to be formatted
* @return string the formatting result.
*/
public function asInteger($value)
{
if ($value === null) {
return $this->nullDisplay;
}
if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
return $matches[1];
} else {
$value = (int) $value;
return "$value";
}
}
/**
* Formats the value as a double number.
* Property [[decimalSeparator]] will be used to represent the decimal point.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatting result.
* @see decimalSeparator
*/
public function asDouble($value, $decimals = 2)
{
if ($value === null) {
return $this->nullDisplay;
}
if ($this->decimalSeparator === null) {
return sprintf("%.{$decimals}f", $value);
} else {
return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
}
}
/**
* Formats the value as a number with decimal and thousand separators.
* This method calls the PHP number_format() function to do the formatting.
* @param mixed $value the value to be formatted
* @param integer $decimals the number of digits after the decimal point
* @return string the formatted result
* @see decimalSeparator
* @see thousandSeparator
*/
public function asNumber($value, $decimals = 0)
{
if ($value === null) {
return $this->nullDisplay;
}
$ds = isset($this->decimalSeparator) ? $this->decimalSeparator : '.';
$ts = isset($this->thousandSeparator) ? $this->thousandSeparator : ',';
return number_format((float) $value, $decimals, $ds, $ts);
}
/**
* Formats the value in bytes as a size in human readable form.
* @param integer $value value in bytes to be formatted
* @param boolean $verbose if full names should be used (e.g. bytes, kilobytes, ...).
* Defaults to false meaning that short names will be used (e.g. B, KB, ...).
* @param boolean $binaryPrefix if binary prefixes should be used for base 1024
* Defaults to true meaning that binary prefixes are used (e.g. kibibyte/KiB, mebibyte/MiB, ...).
* @link http://en.wikipedia.org/wiki/Binary_prefix
* @return string the formatted result
* @see sizeFormat
*/
public function asSize($value, $verbose = false, $binaryPrefix = true)
{
$position = 0;
do {
if ($value < $this->sizeFormat['base']) {
break;
}
$value = $value / $this->sizeFormat['base'];
$position++;
} while ($position < 5);
$value = round($value, $this->sizeFormat['decimals']);
$formattedValue = isset($this->sizeFormat['decimalSeparator']) ? str_replace('.', $this->sizeFormat['decimalSeparator'], $value) : $value;
$params = ['n' => $formattedValue];
if ($binaryPrefix && $this->sizeFormat['base'] === 1024) {
switch ($position) {
case 0:
return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params);
case 1:
return $verbose ? Yii::t('yii', '{n, plural, =1{# kibibyte} other{# kibibytes}}', $params) : Yii::t('yii', '{n} KiB', $params);
case 2:
return $verbose ? Yii::t('yii', '{n, plural, =1{# mebibyte} other{# mebibytes}}', $params) : Yii::t('yii', '{n} MiB', $params);
case 3:
return $verbose ? Yii::t('yii', '{n, plural, =1{# gibibyte} other{# gibibytes}}', $params) : Yii::t('yii', '{n} GiB', $params);
case 4:
return $verbose ? Yii::t('yii', '{n, plural, =1{# tebibyte} other{# tebibytes}}', $params) : Yii::t('yii', '{n} TiB', $params);
default:
return $verbose ? Yii::t('yii', '{n, plural, =1{# pebibyte} other{# pebibytes}}', $params) : Yii::t('yii', '{n} PiB', $params);
}
}
switch ($position) {
case 0:
return $verbose ? Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params) : Yii::t('yii', '{n} B', $params);
case 1:
return $verbose ? Yii::t('yii', '{n, plural, =1{# kilobyte} other{# kilobytes}}', $params) : Yii::t('yii', '{n} KB', $params);
case 2:
return $verbose ? Yii::t('yii', '{n, plural, =1{# megabyte} other{# megabytes}}', $params) : Yii::t('yii', '{n} MB', $params);
case 3:
return $verbose ? Yii::t('yii', '{n, plural, =1{# gigabyte} other{# gigabytes}}', $params) : Yii::t('yii', '{n} GB', $params);
case 4:
return $verbose ? Yii::t('yii', '{n, plural, =1{# terabyte} other{# terabytes}}', $params) : Yii::t('yii', '{n} TB', $params);
default:
return $verbose ? Yii::t('yii', '{n, plural, =1{# petabyte} other{# petabytes}}', $params) : Yii::t('yii', '{n} PB', $params);
}
}
/**
* Formats the value as the time interval between a date and now in human readable form.
*
* @param integer|string|DateTime|\DateInterval $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()` or that can be passed to a DateInterval constructor.
* - a PHP DateTime object
* - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
*
* @param integer|string|DateTime|\DateInterval $referenceTime if specified the value is used instead of now
* @return string the formatted result
*/
public function asRelativeTime($value, $referenceTime = null)
{
if ($value === null) {
return $this->nullDisplay;
}
if ($value instanceof \DateInterval) {
$interval = $value;
} else {
$timestamp = $this->normalizeDatetimeValue($value);
if ($timestamp === false) {
// $value is not a valid date/time value, so we try
// to create a DateInterval with it
try {
$interval = new \DateInterval($value);
} catch (\Exception $e) {
// invalid date/time and invalid interval
return $this->nullDisplay;
}
} else {
$timezone = new \DateTimeZone($this->timeZone);
if ($referenceTime === null) {
$dateNow = new DateTime('now', $timezone);
} else {
$referenceTime = $this->normalizeDatetimeValue($referenceTime);
$dateNow = new DateTime(null, $timezone);
$dateNow->setTimestamp($referenceTime);
}
$dateThen = new DateTime(null, $timezone);
$dateThen->setTimestamp($timestamp);
$interval = $dateThen->diff($dateNow);
}
}
if ($interval->invert) {
if ($interval->y >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y]);
}
if ($interval->m >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m]);
}
if ($interval->d >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d]);
}
if ($interval->h >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h]);
}
if ($interval->i >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i]);
}
return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s]);
} else {
if ($interval->y >= 1) {
return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y]);
}
if ($interval->m >= 1) {
return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m]);
}
if ($interval->d >= 1) {
return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d]);
}
if ($interval->h >= 1) {
return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h]);
}
if ($interval->i >= 1) {
return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i]);
}
return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s]);
}
}
}
......@@ -28,7 +28,6 @@ return [
'yii\base\Event' => YII2_PATH . '/base/Event.php',
'yii\base\Exception' => YII2_PATH . '/base/Exception.php',
'yii\base\ExitException' => YII2_PATH . '/base/ExitException.php',
'yii\base\Formatter' => YII2_PATH . '/base/Formatter.php',
'yii\base\InlineAction' => YII2_PATH . '/base/InlineAction.php',
'yii\base\InvalidCallException' => YII2_PATH . '/base/InvalidCallException.php',
'yii\base\InvalidConfigException' => YII2_PATH . '/base/InvalidConfigException.php',
......
......@@ -63,7 +63,7 @@ class DataColumn extends Column
* @var string|array in which format should the value of each data model be displayed as (e.g. "raw", "text", "html",
* ['date', 'Y-m-d']). Supported formats are determined by the [[GridView::formatter|formatter]] used by
* the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when
* [[\yii\base\Formatter::format()]] or [[\yii\i18n\Formatter::format()]] is used.
* [[\yii\i18n\Formatter::format()]] or [[\yii\i18n\Formatter::format()]] is used.
*/
public $format = 'text';
/**
......
......@@ -9,7 +9,7 @@ namespace yii\grid;
use Yii;
use Closure;
use yii\base\Formatter;
use yii\i18n\Formatter;
use yii\base\InvalidConfigException;
use yii\helpers\Url;
use yii\helpers\Html;
......
......@@ -7,115 +7,358 @@
namespace yii\i18n;
use Yii;
use DateTime;
use DateTimeInterface;
use IntlDateFormatter;
use NumberFormatter;
use DateTime;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\helpers\HtmlPurifier;
use yii\helpers\Html;
/**
* Formatter is the localized version of [[\yii\base\Formatter]].
* Formatter provides a set of commonly used data formatting methods.
*
* The formatting methods provided by Formatter are all named in the form of `asXyz()`.
* The behavior of some of them may be configured via the properties of Formatter. For example,
* by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
*
* Formatter requires the PHP "intl" extension to be installed. Formatter supports localized
* formatting of date, time and numbers, based on the current [[locale]].
* Formatter is configured as an application component in [[\yii\base\Application]] by default.
* You can access that instance via `Yii::$app->formatter`.
*
* This Formatter can replace the `formatter` application component that is configured by default.
* To do so, add the following to your application config under `components`:
*
* ```php
* 'formatter' => [
* 'class' => 'yii\i18n\Formatter',
* ]
* ```
* The Formatter class is designed to format values according to a [[locale]]. For this feature to work
* the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed.
* Most of the methods however work also if the PHP intl extension is not installed by providing
* a fallback implementation. Without intl month and day names are in English only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Enrica Ruedin <e.ruedin@guggach.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Formatter extends \yii\base\Formatter
class Formatter extends Component
{
/**
* @var string the text to be displayed when formatting a `null` value.
* Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
* will be translated according to [[locale]].
*/
public $nullDisplay;
/**
* @var array the text to be displayed when formatting a boolean value. The first element corresponds
* to the text displayed for `false`, the second element for `true`.
* Defaults to `['No', 'Yes']`, where `Yes` and `No`
* will be translated according to [[locale]].
*/
public $booleanFormat;
/**
* @var string the locale ID that is used to localize the date and number formatting.
* For number and date formatting this is only effective when the
* [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
* If not set, [[\yii\base\Application::language]] will be used.
*/
public $locale;
/**
* @var string the default format string to be used to format a date.
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
* @var string the timezone to use for formatting time and date 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 $dateFormat = 'short';
public $timeZone;
/**
* @var string the default format string to be used to format a time.
* @var string the default format string to be used to format a [[asDate()|date]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $timeFormat = 'short';
public $dateFormat = 'medium';
/**
* @var string the default format string to be used to format a date and time.
* @var string the default format string to be used to format a [[asTime()|time]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $datetimeFormat = 'short';
public $timeFormat = 'medium';
/**
* @var array the options to be set for the NumberFormatter objects. Please refer to
* [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
* for the possible options. This property is used by [[createNumberFormatter]] when
* creating a new number formatter to format decimals, currencies, etc.
* @var string the default format string to be used to format a [[asDateTime()|date and time]].
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
*
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*/
public $numberFormatOptions = [];
public $datetimeFormat = 'medium';
/**
* @var string the character displayed as the decimal point when formatting a number.
* If not set, the decimal separator corresponding to [[locale]] will be used.
* If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
*/
public $decimalSeparator;
/**
* @var string the character displayed as the thousands separator character when formatting a number.
* @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
* If not set, the thousand separator corresponding to [[locale]] will be used.
* If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
*/
public $thousandSeparator;
/**
* @var string the international currency code displayed when formatting a number.
* @var array a list of name value pairs that are passed to the
* intl [Numberformatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all
* the number formatter objects created by [[createNumberFormatter()]].
* This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
*
* Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
* for the possible options.
*
* For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
*
* ```php
* [
* NumberFormatter::MIN_FRACTION_DIGITS => 0,
* NumberFormatter::MAX_FRACTION_DIGITS => 2,
* ]
* ```
*/
public $numberFormatterOptions = [];
/**
* @var array a list of name value pairs that are passed to the
* intl [Numberformatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all
* the number formatter objects created by [[createNumberFormatter()]].
* This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
*
* Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
* for the possible options.
*
* For example to change the minus sign for negative numbers you can configure this property like the following:
*
* ```php
* [
* NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
* ]
* ```
*/
public $numberFormatterTextOptions = [];
/**
* @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
* If not set, the currency code corresponding to [[locale]] will be used.
* Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
* is not possible to determine the default currency.
*/
public $currencyCode;
/**
* @var array the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
* Defaults to 1024.
*/
public $sizeFormatBase = 1024;
/**
* @var boolean whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
*/
private $_intlLoaded = false;
/**
* Initializes the component.
* This method will check if the "intl" PHP extension is installed and set the
* default value of [[locale]].
* @throws InvalidConfigException if the "intl" PHP extension is not installed.
* @inheritdoc
*/
public function init()
{
if (!extension_loaded('intl')) {
throw new InvalidConfigException('The "intl" PHP extension is not installed. It is required to format data values in localized formats.');
if ($this->timeZone === null) {
$this->timeZone = Yii::$app->timeZone;
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
if ($this->decimalSeparator === null || $this->thousandSeparator === null || $this->currencyCode === null) {
$formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL);
if ($this->decimalSeparator === null) {
$this->decimalSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
}
if ($this->thousandSeparator === null) {
$this->thousandSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
}
if ($this->currencyCode === null) {
$this->currencyCode = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL);
if ($this->booleanFormat === null) {
$this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
}
if ($this->nullDisplay === null) {
$this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
}
$this->_intlLoaded = extension_loaded('intl');
if (!$this->_intlLoaded) {
$this->decimalSeparator = '.';
$this->thousandSeparator = ',';
}
}
/**
* Formats the value based on the given format type.
* This method will call one of the "as" methods available in this class to do the formatting.
* For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
* then [[asHtml()]] will be used. Format names are case insensitive.
* @param mixed $value the value to be formatted.
* @param string|array $format the format of the value, e.g., "html", "text". To specify additional
* parameters of the formatting method, you may use an array. The first element of the array
* specifies the format name, while the rest of the elements will be used as the parameters to the formatting
* method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`.
* @return string the formatting result.
* @throws InvalidParamException if the format type is not supported by this class.
*/
public function format($value, $format)
{
if (is_array($format)) {
if (!isset($format[0])) {
throw new InvalidParamException('The $format array must contain at least one element.');
}
$f = $format[0];
$format[0] = $value;
$params = $format;
$format = $f;
} else {
$params = [$value];
}
$method = 'as' . $format;
if ($this->hasMethod($method)) {
return call_user_func_array([$this, $method], $params);
} else {
throw new InvalidParamException("Unknown format type: $format");
}
}
// simple formats
/**
* Formats the value as is without any formatting.
* This method simply returns back the parameter without any format.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asRaw($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return $value;
}
parent::init();
/**
* Formats the value as an HTML-encoded plain text.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asText($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::encode($value);
}
private $_dateFormats = [
'short' => IntlDateFormatter::SHORT,
'medium' => IntlDateFormatter::MEDIUM,
'long' => IntlDateFormatter::LONG,
'full' => IntlDateFormatter::FULL,
];
/**
* Formats the value as an HTML-encoded plain text with newlines converted into breaks.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asNtext($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return nl2br(Html::encode($value));
}
/**
* Formats the value as HTML-encoded text paragraphs.
* Each text paragraph is enclosed within a `<p>` tag.
* One or multiple consecutive empty lines divide two paragraphs.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asParagraphs($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/', "</p>\n<p>", Html::encode($value)) . '</p>');
}
/**
* Formats the value as HTML text.
* The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
* Use [[asRaw()]] if you do not want any purification of the value.
* @param mixed $value the value to be formatted.
* @param array|null $config the configuration for the HTMLPurifier class.
* @return string the formatted result.
*/
public function asHtml($value, $config = null)
{
if ($value === null) {
return $this->nullDisplay;
}
return HtmlPurifier::process($value, $config);
}
/**
* Formats the value as a mailto link.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asEmail($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::mailto(Html::encode($value), $value);
}
/**
* Formats the value as an image tag.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asImage($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return Html::img($value);
}
/**
* Formats the value as a hyperlink.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
*/
public function asUrl($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$url = $value;
if (strpos($url, '://') === false) {
$url = 'http://' . $url;
}
return Html::a(Html::encode($value), $url);
}
/**
* Formats the value as a boolean.
* @param mixed $value the value to be formatted.
* @return string the formatted result.
* @see booleanFormat
*/
public function asBoolean($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
}
// date and time formats
/**
* Formats the value as a date.
......@@ -132,32 +375,20 @@ class Formatter extends \yii\base\Formatter
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @throws InvalidConfigException when formatting fails due to invalid parameters.
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*
* @throws InvalidParamException if the input value can not be evaluated as a date value.
* @throws InvalidConfigException if the date format is invalid.
* @return string the formatted result.
* @see dateFormat
*/
public function asDate($value, $format = null)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->dateFormat;
}
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);
if ($formatter !== null) {
$formatter->setPattern($format);
}
}
if ($formatter === null) {
throw new InvalidConfigException(intl_get_error_message());
}
return $formatter->format($value);
return $this->formatDateTimeValue($value, $format, 'date');
}
/**
......@@ -170,37 +401,25 @@ class Formatter extends \yii\base\Formatter
* - a PHP DateTime object
*
* @param string $format the format used to convert the value into a date string.
* If null, [[dateFormat]] will be used.
* If null, [[timeFormat]] will be used.
*
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @throws InvalidConfigException when formatting fails due to invalid parameters.
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*
* @throws InvalidParamException if the input value can not be evaluated as a date value.
* @throws InvalidConfigException if the date format is invalid.
* @return string the formatted result.
* @see timeFormat
*/
public function asTime($value, $format = null)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->timeFormat;
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone);
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone);
if ($formatter !== null) {
$formatter->setPattern($format);
}
}
if ($formatter === null) {
throw new InvalidConfigException(intl_get_error_message());
}
return $formatter->format($value);
return $this->formatDateTimeValue($value, $format, 'time');
}
/**
......@@ -218,122 +437,799 @@ class Formatter extends \yii\base\Formatter
* This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
* It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
*
* @return string the formatted result
* @throws InvalidConfigException when formatting fails due to invalid parameters.
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
* PHP [date()](http://php.net/manual/de/function.date.php)-function.
*
* @throws InvalidParamException if the input value can not be evaluated as a date value.
* @throws InvalidConfigException if the date format is invalid.
* @return string the formatted result.
* @see datetimeFormat
*/
public function asDatetime($value, $format = null)
{
if ($format === null) {
$format = $this->datetimeFormat;
}
return $this->formatDateTimeValue($value, $format, 'datetime');
}
/**
* @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,
];
/**
* @var array with the standard php definition for short, medium, long an full
* format as pattern for date, time and datetime.
* This is used as fallback when the intl extension is not installed.
*/
private $_phpNameToPattern = [
'short' => [
'date' => 'n/j/y',
'time' => 'H:i',
'datetime' => 'n/j/y H:i',
],
'medium' => [
'date' => 'M j, Y',
'time' => 'g:i:s A',
'datetime' => 'M j, Y g:i:s A',
],
'long' => [
'date' => 'F j, Y',
'time' => 'g:i:sA',
'datetime' => 'F j, Y g:i:sA',
],
'full' => [
'date' => 'l, F j, Y',
'time' => 'g:i:sA T',
'datetime' => 'l, F j, Y g:i:sA T',
],
];
/**
* @param integer $value normalized datetime value
* @param string $format the format used to convert the value into a date string.
* @param string $type 'date', 'time', or 'datetime'.
* @throws InvalidConfigException if the date format is invalid.
* @return string the formatted result.
*/
private function formatDateTimeValue($value, $format, $type)
{
$value = $this->normalizeDatetimeValue($value);
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeDatetimeValue($value);
if ($format === null) {
$format = $this->datetimeFormat;
if ($this->_intlLoaded) {
$format = $this->getIntlDatePattern($format);
if (isset($this->_dateFormats[$format])) {
if ($type === 'date') {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone);
} elseif ($type === 'time') {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone);
} else {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone);
}
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format);
}
if ($formatter === null) {
throw new InvalidConfigException(intl_get_error_message());
}
return $formatter->format($value);
} else {
// replace short, medium, long and full with real patterns in case intl is not loaded.
if (isset($this->_phpNameToPattern[$format][$type])) {
$format = $this->_phpNameToPattern[$format][$type];
} else {
$format = $this->getPhpDatePattern($format);
}
$date = new DateTime(null, new \DateTimeZone($this->timeZone));
$date->setTimestamp($value);
return $date->format($format);
}
if (isset($this->_dateFormats[$format])) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone);
}
/**
* Normalizes the given datetime value as a UNIX timestamp that can be taken by various date/time formatting methods.
*
* @param mixed $value the datetime value to be normalized.
* @return float the normalized datetime value (int64)
*/
protected function normalizeDatetimeValue($value)
{
if ($value === null) {
return null;
} elseif (is_string($value)) {
if (is_numeric($value) || $value === '') {
$value = (double)$value;
} else {
$date = new DateTime($value);
$value = (double)$date->format('U');
}
return $value;
} elseif ($value instanceof DateTime || $value instanceof DateTimeInterface) {
return (double)$value->format('U');
} else {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone);
if ($formatter !== null) {
$formatter->setPattern($format);
return (double)$value;
}
}
private function getIntlDatePattern($pattern)
{
if (strpos($pattern, 'php:') === 0) {
return $this->convertPatternPhpToIcu(substr($pattern, 4));
} else {
return $pattern;
}
}
private function getPhpDatePattern($pattern)
{
if (strpos($pattern, 'php:') === 0) {
return substr($pattern, 4);
} else {
return $this->convertPatternIcuToPhp($pattern);
}
}
/**
* intlFormatter class (ICU based) and DateTime class don't have same format string.
* These format patterns are completely incompatible and must be converted.
*
* This method converts an ICU (php intl) formatted date, time or datetime string in
* a php compatible format string.
*
* @param string $pattern dateformat pattern like 'dd.mm.yyyy' or 'short'/'medium'/
* 'long'/'full' or 'db
* @return string with converted date format pattern.
* @throws InvalidConfigException
*/
private function convertPatternIcuToPhp($pattern)
{
return strtr($pattern, [
'dd' => 'd', // day with leading zeros
'd' => 'j', // day without leading zeros
'E' => 'D', // day written in short form eg. Sun
'EE' => 'D',
'EEE' => 'D',
'EEEE' => 'l', // day fully written eg. Sunday
'e' => 'N', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
'ee' => 'N', // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
// engl. ordinal st, nd, rd; it's not support by ICU but we added
'D' => 'z', // day of the year 0 to 365
'w' => 'W', // ISO-8601 week number of year, weeks starting on Monday
'W' => '', // week of the current month; isn't supported by php
'F' => '', // Day of Week in Month. eg. 2nd Wednesday in July
'g' => '', // Modified Julian day. This is different from the conventional Julian day number in two regards.
'M' => 'n', // Numeric representation of a month, without leading zeros
'MM' => 'm', // Numeric representation of a month, with leading zeros
'MMM' => 'M', // A short textual representation of a month, three letters
'MMMM' => 'F', // A full textual representation of a month, such as January or March
'Q' => '', // number of quarter not supported in php
'QQ' => '', // number of quarter '02' not supported in php
'QQQ' => '', // quarter 'Q2' not supported in php
'QQQQ' => '', // quarter '2nd quarter' not supported in php
'QQQQQ' => '', // number of quarter '2' not supported in php
'Y' => 'Y', // 4digit year number eg. 2014
'y' => 'Y', // 4digit year also
'yyyy' => 'Y', // 4digit year also
'yy' => 'y', // 2digit year number eg. 14
'r' => '', // related Gregorian year, not supported by php
'G' => '', // ear designator like AD
'a' => 'a', // Lowercase Ante meridiem and Post
'h' => 'g', // 12-hour format of an hour without leading zeros 1 to 12h
'K' => 'g', // 12-hour format of an hour without leading zeros 0 to 11h, not supported by php
'H' => 'G', // 24-hour format of an hour without leading zeros 0 to 23h
'k' => 'G', // 24-hour format of an hour without leading zeros 1 to 24h, not supported by php
'hh' => 'h', // 12-hour format of an hour with leading zeros, 01 to 12 h
'KK' => 'h', // 12-hour format of an hour with leading zeros, 00 to 11 h, not supported by php
'HH' => 'H', // 24-hour format of an hour with leading zeros, 00 to 23 h
'kk' => 'H', // 24-hour format of an hour with leading zeros, 01 to 24 h, not supported by php
'm' => 'i', // Minutes without leading zeros, not supported by php
'mm' => 'i', // Minutes with leading zeros
's' => 's', // Seconds, without leading zeros, not supported by php
'ss' => 's', // Seconds, with leading zeros
'SSS' => '', // millisecond (maximum of 3 significant digits), not supported by php
'A' => '', // milliseconds in day, not supported by php
'Z' => 'O', // Difference to Greenwich time (GMT) in hours
'ZZ' => 'O', // Difference to Greenwich time (GMT) in hours
'ZZZ' => 'O', // Difference to Greenwich time (GMT) in hours
'z' => 'T', // Timezone abbreviation
'zz' => 'T', // Timezone abbreviation
'zzz' => 'T', // Timezone abbreviation
'zzzz' => 'T', // Timzone full name, not supported by php
'V' => 'e', // Timezone identifier eg. Europe/Berlin
'VV' => 'e',
'VVV' => 'e',
'VVVV' => 'e'
]);
}
/**
* intlFormatter class (ICU based) and DateTime class don't have same format string.
* These format patterns are completely incompatible and must be converted.
*
* This method converts PHP formatted date, time or datetime string in
* an ICU (php intl) compatible format string.
*
* @param string $pattern dateformat pattern like 'd.m.Y' or 'short'/'medium'/
* 'long'/'full' or 'db
* @return string with converted date format pattern.
* @throws InvalidConfigException
*/
private function convertPatternPhpToIcu($pattern)
{
return strtr($pattern, [
'd' => 'dd', // day with leading zeros
'j' => 'd', // day without leading zeros
'D' => 'EEE', // day written in short form eg. Sun
'l' => 'EEEE', // day fully written eg. Sunday
'N' => 'e', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun
// php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year
'S' => '', // engl. ordinal st, nd, rd; it's not support by ICU
'z' => 'D', // day of the year 0 to 365
'W' => 'w', // ISO-8601 week number of year, weeks starting on Monday
// week of the current month; isn't supported by php
// Day of Week in Month. eg. 2nd Wednesday in July not supported by php
// Modified Julian day. This is different from the conventional Julian day number in two regards.
'n'=> 'M', // Numeric representation of a month, without leading zeros
'm' => 'MM', // Numeric representation of a month, with leading zeros
'M' => 'MMM', // A short textual representation of a month, three letters
'F' => 'MMMM', // A full textual representation of a month, such as January or March
// number of quarter not supported in php
// number of quarter '02' not supported in php
// quarter 'Q2' not supported in php
// quarter '2nd quarter' not supported in php
// number of quarter '2' not supported in php
'Y' => 'yyyy', // 4digit year eg. 2014
'y' => 'yy', // 2digit year number eg. 14
// related Gregorian year, not supported by php
// ear designator like AD
'a' => 'a', // Lowercase Ante meridiem and Post am. or pm.
'A' => 'a', // Upercase Ante meridiem and Post AM or PM, not supported by ICU
'g' => 'h', // 12-hour format of an hour without leading zeros 1 to 12h
// 12-hour format of an hour without leading zeros 0 to 11h, not supported by php
'G' => 'H', // 24-hour format of an hour without leading zeros 0 to 23h
// 24-hour format of an hour without leading zeros 1 to 24h, not supported by php
'h' => 'hh', // 12-hour format of an hour with leading zeros, 01 to 12 h
// 12-hour format of an hour with leading zeros, 00 to 11 h, not supported by php
'H' => 'HH', // 24-hour format of an hour with leading zeros, 00 to 23 h
// 24-hour format of an hour with leading zeros, 01 to 24 h, not supported by php
// Minutes without leading zeros, not supported by php
'i' => 'mm', // Minutes with leading zeros
// Seconds, without leading zeros, not supported by php
's' => 'ss', // Seconds, with leading zeros
// millisecond (maximum of 3 significant digits), not supported by php
// milliseconds in day, not supported by php
'O' => 'Z', // Difference to Greenwich time (GMT) in hours
'T' => 'z', // Timezone abbreviation
// Timzone full name, not supported by php
'e' => 'VV', // Timezone identifier eg. Europe/Berlin
'w' => '', // Numeric representation of the day of the week 0=Sun, 6=Sat, not sup. ICU
'L' => '', //Whether it's a leap year 1= leap, 0= normal year, not sup. ICU
'B' => '', // Swatch Internet time, 000 to 999, not sup. ICU
'u' => '', // Microseconds Note that date() will always generate 000000 since it takes an integer parameter, not sup. ICU
'P' => '', // Difference to Greenwich time (GMT) with colon between hours and minutes, not sup. ICU
'Z' => '', // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive, not sup. ICU
'c' => 'yyy-MM-dd\'T\'mm:HH:ssZ', //ISO 8601 date, it works only if nothing else than 'c' is in pattern.
'r' => 'eee, dd MMM yyyy mm:HH:ss Z', // » RFC 2822 formatted date, it works only if nothing else than 'r' is in pattern
'U' => '' // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT), not supported in ICU
]);
}
/**
* Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
* @param integer|string|DateTime|\DateInterval $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()` or that can be passed to a DateInterval constructor.
* - a PHP DateTime object
* - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
*
* @return string the formatted result.
*/
public function asTimestamp($value)
{
if ($value === null) {
return $this->nullDisplay;
}
return number_format($this->normalizeDatetimeValue($value), 0, '.', '');
}
/**
* Formats the value as the time interval between a date and now in human readable form.
*
* @param integer|string|DateTime|\DateInterval $value the value to be formatted. The following
* types of value are supported:
*
* - an integer representing a UNIX timestamp
* - a string that can be parsed into a UNIX timestamp via `strtotime()` or that can be passed to a DateInterval constructor.
* - a PHP DateTime object
* - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
*
* @param integer|string|DateTime|\DateInterval $referenceTime if specified the value is used instead of `now`.
* @return string the formatted result.
* @throws InvalidParamException if the input value can not be evaluated as a date value.
*/
public function asRelativeTime($value, $referenceTime = null)
{
if ($value === null) {
return $this->nullDisplay;
}
if ($value instanceof \DateInterval) {
$interval = $value;
} else {
$timestamp = $this->normalizeDatetimeValue($value);
if ($timestamp === false) {
// $value is not a valid date/time value, so we try
// to create a DateInterval with it
try {
$interval = new \DateInterval($value);
} catch (\Exception $e) {
// invalid date/time and invalid interval
return $this->nullDisplay;
}
} else {
$timezone = new \DateTimeZone($this->timeZone);
if ($referenceTime === null) {
$dateNow = new DateTime('now', $timezone);
} else {
$referenceTime = $this->normalizeDatetimeValue($referenceTime);
$dateNow = new DateTime(null, $timezone);
$dateNow->setTimestamp($referenceTime);
}
$dateThen = new DateTime(null, $timezone);
$dateThen->setTimestamp($timestamp);
$interval = $dateThen->diff($dateNow);
}
}
if ($formatter === null) {
throw new InvalidConfigException(intl_get_error_message());
if ($interval->invert) {
if ($interval->y >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
}
if ($interval->m >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
}
if ($interval->d >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
}
if ($interval->h >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
}
if ($interval->i >= 1) {
return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
}
return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
} else {
if ($interval->y >= 1) {
return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
}
if ($interval->m >= 1) {
return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
}
if ($interval->d >= 1) {
return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
}
if ($interval->h >= 1) {
return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
}
if ($interval->i >= 1) {
return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
}
return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
}
}
// number formats
return $formatter->format($value);
/**
* Formats the value as an integer number by removing any decimal digits without rounding.
*
* @param mixed $value the value to be formatted.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asInteger($value, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
return $f->format($value, NumberFormatter::TYPE_INT64);
} else {
return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
}
}
/**
* Formats the value as a decimal number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#details)
* for details on how to specify a format.
*
* Property [[decimalSeparator]] will be used to represent the decimal point. The
* value is rounded automatically to the defined decimal digits.
*
* @param mixed $value the value to be formatted.
* @param integer $decimals the number of digits after the decimal point. If not given the number of digits is determined from the
* [[locale]] and if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available defaults to `2`.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @see decimalSeparator
* @see thousandSeparator
*/
public function asDecimal($value, $format = null)
public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value);
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
return $f->format($value);
} else {
if ($decimals === null){
$decimals = 2;
}
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
}
}
/**
* Formats the value as a percent number with "%" sign.
*
* @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
* @param integer $decimals the number of digits after the decimal point.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded) {
$f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
return $f->format($value);
} else {
if ($decimals === null){
$decimals = 0;
}
$value = $value * 100;
return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
}
}
/**
* Formats the value as a scientific number.
*
* @param mixed $value the value to be formatted.
* @param integer $decimals the number of digits after the decimal point.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
*/
public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
return $f->format($value);
} else {
if ($decimals !== null) {
return sprintf("%.{$decimals}E", $value);
} else {
return sprintf("%.E", $value);
}
}
}
/**
* Formats the value as a currency number.
* @param mixed $value the value to be formatted
*
* This function does not requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
* to work but it is highly recommended to install it to get good formatting results.
*
* @param mixed $value the value to be formatted.
* @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
* If null, [[currencyCode]] will be used.
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#details)
* for details on how to specify a format.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
*/
public function asCurrency($value, $currency = null, $format = null)
public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
if ($currency === null){
$currency = $this->currencyCode;
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded) {
$formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
if ($currency === null) {
if ($this->currencyCode === null) {
$currency = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL);
} else {
$currency = $this->currencyCode;
}
}
return $formatter->formatCurrency($value, $currency);
} else {
if ($currency === null) {
if ($this->currencyCode === null) {
throw new InvalidConfigException('The default currency code for the formatter is not defined.');
}
$currency = $this->currencyCode;
}
return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
}
}
return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency);
/**
* Formats the value as a number spellout.
*
* This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
*
* @param mixed $value the value to be formatted
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
*/
public function asSpellout($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
return $f->format($value);
} else {
throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
}
}
/**
* Formats the value as a percent number.
* Formats the value as a ordinal value of a number.
*
* This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
*
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#details)
* for details on how to specify a format.
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
*/
public function asPercent($value, $format = null)
public function asOrdinal($value)
{
if ($value === null) {
return $this->nullDisplay;
}
$value = $this->normalizeNumericValue($value);
if ($this->_intlLoaded){
$f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
return $f->format($value);
} else {
throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
}
}
return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value);
/**
* Formats the value in bytes as a size in human readable form for example `12 KB`.
*
* This is the short form of [[asSize]].
*
* If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
* are used in the formatting result.
*
* @param integer $value value in bytes to be formatted.
* @param integer $decimals the number of digits after the decimal point.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @see sizeFormat
* @see asSize
*/
public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);
if ($this->sizeFormatBase == 1024) {
switch ($position) {
case 0: return Yii::t('yii', '{n} B', $params, $this->locale);
case 1: return Yii::t('yii', '{n} KiB', $params, $this->locale);
case 2: return Yii::t('yii', '{n} MiB', $params, $this->locale);
case 3: return Yii::t('yii', '{n} GiB', $params, $this->locale);
case 4: return Yii::t('yii', '{n} TiB', $params, $this->locale);
default: return Yii::t('yii', '{n} PiB', $params, $this->locale);
}
} else {
switch ($position) {
case 0: return Yii::t('yii', '{n} B', $params, $this->locale);
case 1: return Yii::t('yii', '{n} KB', $params, $this->locale);
case 2: return Yii::t('yii', '{n} MB', $params, $this->locale);
case 3: return Yii::t('yii', '{n} GB', $params, $this->locale);
case 4: return Yii::t('yii', '{n} TB', $params, $this->locale);
default: return Yii::t('yii', '{n} PB', $params, $this->locale);
}
}
}
/**
* Formats the value as a scientific number.
* @param mixed $value the value to be formatted
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#details)
* for details on how to specify a format.
* Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
*
* If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
* are used in the formatting result.
*
* @param integer $value value in bytes to be formatted.
* @param integer $decimals the number of digits after the decimal point.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric.
* @see sizeFormat
* @see asShortSize
*/
public function asScientific($value, $format = null)
public function asSize($value, $decimals = null, $options = [], $textOptions = [])
{
if ($value === null) {
return $this->nullDisplay;
}
return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value);
list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions);
if ($this->sizeFormatBase == 1024) {
switch ($position) {
case 0: return Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params, $this->locale);
case 1: return Yii::t('yii', '{n, plural, =1{# kibibyte} other{# kibibytes}}', $params, $this->locale);
case 2: return Yii::t('yii', '{n, plural, =1{# mebibyte} other{# mebibytes}}', $params, $this->locale);
case 3: return Yii::t('yii', '{n, plural, =1{# gibibyte} other{# gibibytes}}', $params, $this->locale);
case 4: return Yii::t('yii', '{n, plural, =1{# tebibyte} other{# tebibytes}}', $params, $this->locale);
default: return Yii::t('yii', '{n, plural, =1{# pebibyte} other{# pebibytes}}', $params, $this->locale);
}
} else {
switch ($position) {
case 0: return Yii::t('yii', '{n, plural, =1{# byte} other{# bytes}}', $params, $this->locale);
case 1: return Yii::t('yii', '{n, plural, =1{# kilobyte} other{# kilobytes}}', $params, $this->locale);
case 2: return Yii::t('yii', '{n, plural, =1{# megabyte} other{# megabytes}}', $params, $this->locale);
case 3: return Yii::t('yii', '{n, plural, =1{# gigabyte} other{# gigabytes}}', $params, $this->locale);
case 4: return Yii::t('yii', '{n, plural, =1{# terabyte} other{# terabytes}}', $params, $this->locale);
default: return Yii::t('yii', '{n, plural, =1{# petabyte} other{# petabytes}}', $params, $this->locale);
}
}
}
private function formatSizeNumber($value, $decimals, $options, $textOptions)
{
if (is_string($value) && is_numeric($value)) {
$value = (int) $value;
}
if (!is_numeric($value)) {
throw new InvalidParamException("'$value' is not a numeric value.");
}
$position = 0;
do {
if ($value < $this->sizeFormatBase) {
break;
}
$value = $value / $this->sizeFormatBase;
$position++;
} while ($position < 5);
// no decimals for bytes
if ($position === 0) {
$decimals = 0;
} elseif ($decimals !== null) {
$value = round($value, $decimals);
}
// disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
$oldThousandSeparator = $this->thousandSeparator;
$this->thousandSeparator = '';
$options[NumberFormatter::GROUPING_USED] = false;
// format the size value
$params = ['n' => $this->asDecimal($value, $decimals, $options, $textOptions)];
$this->thousandSeparator = $oldThousandSeparator;
return [$params, $position];
}
/**
* @param $value
* @return float
* @throws InvalidParamException
*/
protected function normalizeNumericValue($value)
{
if (is_string($value) && is_numeric($value)) {
$value = (float) $value;
}
if (!is_numeric($value)) {
throw new InvalidParamException("'$value' is not a numeric value.");
}
return $value;
}
/**
* Creates a number formatter based on the given type and format.
* @param integer $type the type of the number formatter
* @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#details)
*
* You may overide this method to create a number formatter based on patterns.
*
* @param integer $style the type of the number formatter.
* Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
* ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
* @param integer $decimals the number of digits after the decimal point.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return NumberFormatter the created formatter instance
*/
protected function createNumberFormatter($type, $format)
protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
{
$formatter = new NumberFormatter($this->locale, $type);
if ($format !== null) {
$formatter->setPattern($format);
$formatter = new NumberFormatter($this->locale, $style);
if ($this->decimalSeparator !== null) {
$formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
}
if (!empty($this->numberFormatOptions)) {
foreach ($this->numberFormatOptions as $name => $attribute) {
$formatter->setAttribute($name, $attribute);
}
if ($this->thousandSeparator !== null) {
$formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
}
if ($decimals !== null) {
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
$formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
}
foreach ($this->numberFormatterOptions as $name => $value) {
$formatter->setAttribute($name, $value);
}
foreach ($options as $name => $value) {
$formatter->setAttribute($name, $value);
}
foreach ($this->numberFormatterTextOptions as $name => $attribute) {
$formatter->setTextAttribute($name, $attribute);
}
foreach ($textOptions as $name => $attribute) {
$formatter->setTextAttribute($name, $attribute);
}
return $formatter;
}
}
......@@ -9,7 +9,7 @@ namespace yii\widgets;
use Yii;
use yii\base\Arrayable;
use yii\base\Formatter;
use yii\i18n\Formatter;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\base\Widget;
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\base;
use yii\base\Formatter;
use yiiunit\TestCase;
use DateTime;
use DateInterval;
/**
* @group base
*/
class FormatterTest extends TestCase
{
/**
* @var Formatter
*/
protected $formatter;
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->formatter = new Formatter();
}
protected function tearDown()
{
parent::tearDown();
$this->formatter = null;
}
public function testAsRaw()
{
$value = '123';
$this->assertSame($value, $this->formatter->asRaw($value));
$value = 123;
$this->assertSame($value, $this->formatter->asRaw($value));
$value = '<>';
$this->assertSame($value, $this->formatter->asRaw($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRaw(null));
}
public function testAsText()
{
$value = '123';
$this->assertSame($value, $this->formatter->asText($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asText($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asText($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asText(null));
}
public function testAsNtext()
{
$value = '123';
$this->assertSame($value, $this->formatter->asNtext($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asNtext($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asNtext($value));
$value = "123\n456";
$this->assertSame("123<br />\n456", $this->formatter->asNtext($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNtext(null));
}
public function testAsParagraphs()
{
$value = '123';
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = 123;
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = '<>';
$this->assertSame('<p>&lt;&gt;</p>', $this->formatter->asParagraphs($value));
$value = "123\n456";
$this->assertSame("<p>123\n456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asParagraphs(null));
}
public function testAsHtml()
{
// todo: dependency on HtmlPurifier
}
public function testAsEmail()
{
$value = 'test@sample.com';
$this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asEmail(null));
}
public function testAsImage()
{
$value = 'http://sample.com/img.jpg';
$this->assertSame("<img src=\"$value\" alt=\"\">", $this->formatter->asImage($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asImage(null));
}
public function testAsBoolean()
{
$value = true;
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = false;
$this->assertSame('No', $this->formatter->asBoolean($value));
$value = "111";
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = "";
$this->assertSame('No', $this->formatter->asBoolean($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null));
}
public function testAsDate()
{
$value = time();
$this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value, 'Y/m/d'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
}
public function testAsTime()
{
$value = time();
$this->assertSame(date('H:i:s', $value), $this->formatter->asTime($value));
$this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value, 'h:i:s A'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
}
public function testAsDatetime()
{
$value = time();
$this->assertSame(date('Y-m-d H:i:s', $value), $this->formatter->asDatetime($value));
$this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value, 'Y/m/d h:i:s A'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
}
public function testAsInteger()
{
$value = 123;
$this->assertSame("$value", $this->formatter->asInteger($value));
$value = 123.23;
$this->assertSame("123", $this->formatter->asInteger($value));
$value = 'a';
$this->assertSame("0", $this->formatter->asInteger($value));
$value = -123.23;
$this->assertSame("-123", $this->formatter->asInteger($value));
$value = "-123abc";
$this->assertSame("-123", $this->formatter->asInteger($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
}
public function testAsDouble()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDouble($value));
$this->assertSame("123.1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123;
$this->assertSame("123.00", $this->formatter->asDouble($value));
$this->formatter->decimalSeparator = ',';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDouble($value));
$this->assertSame("123,1", $this->formatter->asDouble($value, 1));
$this->assertSame("123", $this->formatter->asDouble($value, 0));
$value = 123123.123;
$this->assertSame("123123,12", $this->formatter->asDouble($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDouble(null));
}
public function testAsNumber()
{
$value = 123123.123;
$this->assertSame("123,123", $this->formatter->asNumber($value));
$this->assertSame("123,123.12", $this->formatter->asNumber($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123", $this->formatter->asNumber($value));
$this->assertSame("123 123,12", $this->formatter->asNumber($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123", $this->formatter->asNumber($value));
$this->assertSame("123123,12", $this->formatter->asNumber($value, 2));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNumber(null));
}
public function testAsSize() {
// tests for base 1000
$this->formatter->sizeFormat['base'] = 1000;
$this->assertSame("999 B", $this->formatter->asSize(999));
$this->assertSame("1.05 MB", $this->formatter->asSize(1024 * 1024));
$this->assertSame("1.05 MB", $this->formatter->asSize(1024 * 1024, false, false));
$this->assertSame("1 KB", $this->formatter->asSize(1000));
$this->assertSame("1.02 KB", $this->formatter->asSize(1023));
$this->assertSame("3 gigabytes", $this->formatter->asSize(3 * 1000 * 1000 * 1000, true));
$this->assertNotSame("3 PB", $this->formatter->asSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB
// tests for base 1024
$this->formatter->sizeFormat['base'] = 1024;
$this->assertSame("1 KiB", $this->formatter->asSize(1024));
$this->assertSame("1 MB", $this->formatter->asSize(1024 * 1024, false, false));
$this->assertSame("1023 B", $this->formatter->asSize(1023));
$this->assertSame("5 GiB", $this->formatter->asSize(5 * 1024 * 1024 * 1024));
$this->assertSame("5 gibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024,true));
$this->assertNotSame("5 PiB", $this->formatter->asSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB
//$this->assertSame("1 YiB", $this->formatter->asSize(pow(2, 80)));
$this->assertSame("2 GiB", $this->formatter->asSize(2147483647)); // round 1.999 up to 2
$this->formatter->sizeFormat['decimalSeparator'] = ',';
$this->formatter->sizeFormat['decimals'] = 3;
$this->assertSame("1,001 KiB", $this->formatter->asSize(1025));
}
public function testFormat()
{
$value = time();
$this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'date'));
$this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'DATE'));
$this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'Y/m/d']));
$this->setExpectedException('\yii\base\InvalidParamException');
$this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data'));
}
private function buildDateSubIntervals($referenceDate, $intervals)
{
$date = new DateTime($referenceDate);
foreach ($intervals as $interval) {
$date->sub($interval);
}
return $date;
}
public function testAsRelativeTime()
{
$interval_1_second = new DateInterval("PT1S");
$interval_244_seconds = new DateInterval("PT244S");
$interval_1_minute = new DateInterval("PT1M");
$interval_33_minutes = new DateInterval("PT33M");
$interval_1_hour = new DateInterval("PT1H");
$interval_6_hours = new DateInterval("PT6H");
$interval_1_day = new DateInterval("P1D");
$interval_89_days = new DateInterval("P89D");
$interval_1_month = new DateInterval("P1M");
$interval_5_months = new DateInterval("P5M");
$interval_1_year = new DateInterval("P1Y");
$interval_12_years = new DateInterval("P12Y");
// Pass a DateInterval
$this->assertSame('a second ago', $this->formatter->asRelativeTime($interval_1_second));
$this->assertSame('244 seconds ago', $this->formatter->asRelativeTime($interval_244_seconds));
$this->assertSame('a minute ago', $this->formatter->asRelativeTime($interval_1_minute));
$this->assertSame('33 minutes ago', $this->formatter->asRelativeTime($interval_33_minutes));
$this->assertSame('an hour ago', $this->formatter->asRelativeTime($interval_1_hour));
$this->assertSame('6 hours ago', $this->formatter->asRelativeTime($interval_6_hours));
$this->assertSame('a day ago', $this->formatter->asRelativeTime($interval_1_day));
$this->assertSame('89 days ago', $this->formatter->asRelativeTime($interval_89_days));
$this->assertSame('a month ago', $this->formatter->asRelativeTime($interval_1_month));
$this->assertSame('5 months ago', $this->formatter->asRelativeTime($interval_5_months));
$this->assertSame('a year ago', $this->formatter->asRelativeTime($interval_1_year));
$this->assertSame('12 years ago', $this->formatter->asRelativeTime($interval_12_years));
// Pass a DateInterval string
if (!defined('HHVM_VERSION')) {
// TODO format not supported by HHVM currently https://github.com/facebook/hhvm/issues/2952
$this->assertSame('a year ago', $this->formatter->asRelativeTime('2007-03-01T13:00:00Z/2008-05-11T15:30:00Z'));
}
$this->assertSame('a year ago', $this->formatter->asRelativeTime('2007-03-01T13:00:00Z/P1Y2M10DT2H30M'));
$this->assertSame('a year ago', $this->formatter->asRelativeTime('P1Y2M10DT2H30M/2008-05-11T15:30:00Z'));
$this->assertSame('a year ago', $this->formatter->asRelativeTime('P1Y2M10DT2H30M'));
$this->assertSame('94 months ago', $this->formatter->asRelativeTime('P94M'));
// Force the reference time and pass a past DateTime
$dateNow = new DateTime('2014-03-13');
$this->assertSame('a second ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_second]), $dateNow));
$this->assertSame('4 minutes ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_244_seconds]), $dateNow));
$this->assertSame('a minute ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_minute]), $dateNow));
$this->assertSame('33 minutes ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_33_minutes]), $dateNow));
$this->assertSame('an hour ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_hour]), $dateNow));
$this->assertSame('6 hours ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_6_hours]), $dateNow));
$this->assertSame('a day ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_day]), $dateNow));
$this->assertSame('2 months ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_89_days]), $dateNow));
$this->assertSame('a month ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_month]), $dateNow));
$this->assertSame('5 months ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_5_months]), $dateNow));
$this->assertSame('a year ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_year]), $dateNow));
$this->assertSame('12 years ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_12_years]), $dateNow));
// Tricky 31-days month stuff
// See: http://www.gnu.org/software/tar/manual/html_section/Relative-items-in-date-strings.html
$dateNow = new DateTime('2014-03-31');
$dateThen = new DateTime('2014-03-03');
$this->assertSame('28 days ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-31', [$interval_1_month]), $dateNow));
$this->assertSame('28 days ago', $this->formatter->asRelativeTime($dateThen, $dateNow));
$dateThen = new DateTime('2014-02-28');
$this->assertSame('a month ago', $this->formatter->asRelativeTime($dateThen, $dateNow));
// Invert all the DateIntervals
$interval_1_second->invert = true;
$interval_244_seconds->invert = true;
$interval_1_minute->invert = true;
$interval_33_minutes->invert = true;
$interval_1_hour->invert = true;
$interval_6_hours->invert = true;
$interval_1_day->invert = true;
$interval_89_days->invert = true;
$interval_1_month->invert = true;
$interval_5_months->invert = true;
$interval_1_year->invert = true;
$interval_12_years->invert = true;
// Pass a inverted DateInterval
$this->assertSame('in a second', $this->formatter->asRelativeTime($interval_1_second));
$this->assertSame('in 244 seconds', $this->formatter->asRelativeTime($interval_244_seconds));
$this->assertSame('in a minute', $this->formatter->asRelativeTime($interval_1_minute));
$this->assertSame('in 33 minutes', $this->formatter->asRelativeTime($interval_33_minutes));
$this->assertSame('in an hour', $this->formatter->asRelativeTime($interval_1_hour));
$this->assertSame('in 6 hours', $this->formatter->asRelativeTime($interval_6_hours));
$this->assertSame('in a day', $this->formatter->asRelativeTime($interval_1_day));
$this->assertSame('in 89 days', $this->formatter->asRelativeTime($interval_89_days));
$this->assertSame('in a month', $this->formatter->asRelativeTime($interval_1_month));
$this->assertSame('in 5 months', $this->formatter->asRelativeTime($interval_5_months));
$this->assertSame('in a year', $this->formatter->asRelativeTime($interval_1_year));
$this->assertSame('in 12 years', $this->formatter->asRelativeTime($interval_12_years));
// Pass a inverted DateInterval string
if (!defined('HHVM_VERSION')) {
// TODO format not supported by HHVM currently https://github.com/facebook/hhvm/issues/2952
$this->assertSame('in a year', $this->formatter->asRelativeTime('2008-05-11T15:30:00Z/2007-03-01T13:00:00Z'));
}
// Force the reference time and pass a future DateTime
$dateNow = new DateTime('2014-03-13');
$this->assertSame('in a second', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_second]), $dateNow));
$this->assertSame('in 4 minutes', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_244_seconds]), $dateNow));
$this->assertSame('in a minute', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_minute]), $dateNow));
$this->assertSame('in 33 minutes', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_33_minutes]), $dateNow));
$this->assertSame('in an hour', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_hour]), $dateNow));
$this->assertSame('in 6 hours', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_6_hours]), $dateNow));
$this->assertSame('in a day', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_day]), $dateNow));
$this->assertSame('in 2 months', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_89_days]), $dateNow));
$this->assertSame('in a month', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_month]), $dateNow));
$this->assertSame('in 5 months', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_5_months]), $dateNow));
$this->assertSame('in a year', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_year]), $dateNow));
$this->assertSame('in 12 years', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_12_years]), $dateNow));
// Tricky 31-days month stuff
// See: http://www.gnu.org/software/tar/manual/html_section/Relative-items-in-date-strings.html
$dateNow = new DateTime('2014-03-03');
$dateThen = new DateTime('2014-03-31');
$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));
}
}
......@@ -5,18 +5,34 @@
* @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;
use yii\base\InvalidParamException;
use yii\i18n\Formatter;
use yiiunit\TestCase;
use DateTime;
use DateInterval;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
* @group i18n
*/
class FormatterTest extends TestCase
{
public static $enableIntl;
/**
* @var Formatter
*/
......@@ -25,11 +41,22 @@ class FormatterTest extends TestCase
protected function setUp()
{
parent::setUp();
if (!extension_loaded('intl')) {
$this->markTestSkipped('intl extension is required.');
// emulate disabled intl extension
// 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([
'timeZone' => 'UTC',
'language' => 'ru-RU',
]);
$this->formatter = new Formatter(['locale' => 'en-US']);
}
......@@ -40,67 +67,728 @@ class FormatterTest extends TestCase
$this->formatter = null;
}
public function testAsDecimal()
public function testFormat()
{
$value = time();
$this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'date'));
$this->assertSame(date('M j, Y', $value), $this->formatter->format($value, 'DATE'));
$this->assertSame(date('Y/m/d', $value), $this->formatter->format($value, ['date', 'php:Y/m/d']));
$this->setExpectedException('\yii\base\InvalidParamException');
$this->assertSame(date('Y-m-d', $value), $this->formatter->format($value, 'data'));
}
public function testLocale()
{
// locale is configured explicitly
$f = new Formatter(['locale' => 'en-US']);
$this->assertEquals('en-US', $f->locale);
// if not, take from application
$f = new Formatter();
$this->assertEquals('ru-RU', $f->locale);
}
public function testAsRaw()
{
$value = '123';
$this->assertSame($value, $this->formatter->asDecimal($value));
$value = '123456';
$this->assertSame("123,456", $this->formatter->asDecimal($value));
$value = '-123456.123';
if (defined('HHVM_VERSION')) { // the default format is different on HHVM
$this->assertSame("-123,456", $this->formatter->asDecimal($value));
} else {
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
$this->assertSame($value, $this->formatter->asRaw($value));
$value = 123;
$this->assertSame($value, $this->formatter->asRaw($value));
$value = '<>';
$this->assertSame($value, $this->formatter->asRaw($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRaw(null));
}
public function testAsText()
{
$value = '123';
$this->assertSame($value, $this->formatter->asText($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asText($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asText($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asText(null));
}
public function testAsNtext()
{
$value = '123';
$this->assertSame($value, $this->formatter->asNtext($value));
$value = 123;
$this->assertSame("$value", $this->formatter->asNtext($value));
$value = '<>';
$this->assertSame('&lt;&gt;', $this->formatter->asNtext($value));
$value = "123\n456";
$this->assertSame("123<br />\n456", $this->formatter->asNtext($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asNtext(null));
}
public function testAsParagraphs()
{
$value = '123';
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = 123;
$this->assertSame("<p>$value</p>", $this->formatter->asParagraphs($value));
$value = '<>';
$this->assertSame('<p>&lt;&gt;</p>', $this->formatter->asParagraphs($value));
$value = "123\n456";
$this->assertSame("<p>123\n456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\n\n\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\r\n456";
$this->assertSame("<p>123\r\n456</p>", $this->formatter->asParagraphs($value));
$value = "123\r\n\r\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\r\n\r\n\r\n456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\r456";
$this->assertSame("<p>123\r456</p>", $this->formatter->asParagraphs($value));
$value = "123\r\r456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
$value = "123\r\r\r456";
$this->assertSame("<p>123</p>\n<p>456</p>", $this->formatter->asParagraphs($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asParagraphs(null));
}
public function testAsHtml()
{
// todo: dependency on HtmlPurifier
}
public function testAsEmail()
{
$value = 'test@sample.com';
$this->assertSame("<a href=\"mailto:$value\">$value</a>", $this->formatter->asEmail($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asEmail(null));
}
public function testAsUrl()
{
$value = 'http://www.yiiframework.com/';
$this->assertSame("<a href=\"$value\">$value</a>", $this->formatter->asUrl($value));
$value = 'https://www.yiiframework.com/';
$this->assertSame("<a href=\"$value\">$value</a>", $this->formatter->asUrl($value));
$value = 'www.yiiframework.com/';
$this->assertSame("<a href=\"http://$value\">$value</a>", $this->formatter->asUrl($value));
$value = 'https://www.yiiframework.com/?name=test&value=5"';
$this->assertSame("<a href=\"https://www.yiiframework.com/?name=test&amp;value=5&quot;\">https://www.yiiframework.com/?name=test&amp;value=5&quot;</a>", $this->formatter->asUrl($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asUrl(null));
}
public function testAsImage()
{
$value = 'http://sample.com/img.jpg';
$this->assertSame("<img src=\"$value\" alt=\"\">", $this->formatter->asImage($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asImage(null));
}
public function testAsBoolean()
{
$value = true;
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = false;
$this->assertSame('No', $this->formatter->asBoolean($value));
$value = "111";
$this->assertSame('Yes', $this->formatter->asBoolean($value));
$value = "";
$this->assertSame('No', $this->formatter->asBoolean($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null));
}
// date format
public function testIntlAsDate()
{
$this->testAsDate();
}
public function testAsDate()
{
$value = time();
$this->assertSame(date('M j, Y', $value), $this->formatter->asDate($value));
$this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value, 'php:Y/m/d'));
$this->assertSame(date('m/d/Y', $value), $this->formatter->asDate($value, 'MM/dd/yyyy'));
$this->assertSame(date('n/j/y', $value), $this->formatter->asDate($value, 'short'));
$this->assertSame(date('F j, Y', $value), $this->formatter->asDate($value, 'long'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
}
public function testIntlAsTime()
{
$this->testAsTime();
}
public function testAsTime()
{
$value = time();
$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'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTime(null));
}
public function testIntlAsDatetime()
{
$this->testAsDatetime();
}
public function testAsDatetime()
{
$value = time();
$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'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
}
public function testAsTimestamp()
{
$value = time();
$this->assertSame("$value", $this->formatter->asTimestamp($value));
$this->assertSame("$value", $this->formatter->asTimestamp((string) $value));
$this->assertSame("$value", $this->formatter->asTimestamp(date('Y-m-d H:i:s', $value)));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTimestamp(null));
}
// TODO test format conversion ICU/PHP
/**
* Test for dates before 1970
* https://github.com/yiisoft/yii2/issues/3126
*/
public function testDateRangeLow()
{
$this->assertSame('12-08-1922', $this->formatter->asDate('1922-08-12', 'dd-MM-yyyy'));
}
/**
* Test for dates after 2038
* https://github.com/yiisoft/yii2/issues/3126
*/
public function testDateRangeHigh()
{
if (PHP_INT_SIZE < 8) {
$this->markTestSkipped('Dates > 2038 only work on PHP compiled with 64bit support.');
}
$this->assertSame('17-12-2048', $this->formatter->asDate('2048-12-17', 'dd-MM-yyyy'));
}
private function buildDateSubIntervals($referenceDate, $intervals)
{
$date = new DateTime($referenceDate);
foreach ($intervals as $interval) {
$date->sub($interval);
}
return $date;
}
public function testAsRelativeTime()
{
$interval_1_second = new DateInterval("PT1S");
$interval_244_seconds = new DateInterval("PT244S");
$interval_1_minute = new DateInterval("PT1M");
$interval_33_minutes = new DateInterval("PT33M");
$interval_1_hour = new DateInterval("PT1H");
$interval_6_hours = new DateInterval("PT6H");
$interval_1_day = new DateInterval("P1D");
$interval_89_days = new DateInterval("P89D");
$interval_1_month = new DateInterval("P1M");
$interval_5_months = new DateInterval("P5M");
$interval_1_year = new DateInterval("P1Y");
$interval_12_years = new DateInterval("P12Y");
// Pass a DateInterval
$this->assertSame('a second ago', $this->formatter->asRelativeTime($interval_1_second));
$this->assertSame('244 seconds ago', $this->formatter->asRelativeTime($interval_244_seconds));
$this->assertSame('a minute ago', $this->formatter->asRelativeTime($interval_1_minute));
$this->assertSame('33 minutes ago', $this->formatter->asRelativeTime($interval_33_minutes));
$this->assertSame('an hour ago', $this->formatter->asRelativeTime($interval_1_hour));
$this->assertSame('6 hours ago', $this->formatter->asRelativeTime($interval_6_hours));
$this->assertSame('a day ago', $this->formatter->asRelativeTime($interval_1_day));
$this->assertSame('89 days ago', $this->formatter->asRelativeTime($interval_89_days));
$this->assertSame('a month ago', $this->formatter->asRelativeTime($interval_1_month));
$this->assertSame('5 months ago', $this->formatter->asRelativeTime($interval_5_months));
$this->assertSame('a year ago', $this->formatter->asRelativeTime($interval_1_year));
$this->assertSame('12 years ago', $this->formatter->asRelativeTime($interval_12_years));
// Pass a DateInterval string -> isn't possible
// $this->assertSame('a year ago', $this->formatter->asRelativeTime('2007-03-01T13:00:00Z/2008-05-11T15:30:00Z'));
// $this->assertSame('a year ago', $this->formatter->asRelativeTime('2007-03-01T13:00:00Z/P1Y2M10DT2H30M'));
// $this->assertSame('a year ago', $this->formatter->asRelativeTime('P1Y2M10DT2H30M/2008-05-11T15:30:00Z'));
// $this->assertSame('a year ago', $this->formatter->asRelativeTime('P1Y2M10DT2H30M'));
// $this->assertSame('94 months ago', $this->formatter->asRelativeTime('P94M'));
// Force the reference time and pass a past DateTime
$dateNow = new DateTime('2014-03-13');
$this->assertSame('a second ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_second]), $dateNow));
$this->assertSame('4 minutes ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_244_seconds]), $dateNow));
$this->assertSame('a minute ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_minute]), $dateNow));
$this->assertSame('33 minutes ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_33_minutes]), $dateNow));
$this->assertSame('an hour ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_hour]), $dateNow));
$this->assertSame('6 hours ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_6_hours]), $dateNow));
$this->assertSame('a day ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_day]), $dateNow));
$this->assertSame('2 months ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_89_days]), $dateNow));
$this->assertSame('a month ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_month]), $dateNow));
$this->assertSame('5 months ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_5_months]), $dateNow));
$this->assertSame('a year ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_year]), $dateNow));
$this->assertSame('12 years ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_12_years]), $dateNow));
// Tricky 31-days month stuff
// See: http://www.gnu.org/software/tar/manual/html_section/Relative-items-in-date-strings.html
$dateNow = new DateTime('2014-03-31');
$dateThen = new DateTime('2014-03-03');
$this->assertSame('28 days ago', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-31', [$interval_1_month]), $dateNow));
$this->assertSame('28 days ago', $this->formatter->asRelativeTime($dateThen, $dateNow));
$dateThen = new DateTime('2014-02-28');
$this->assertSame('a month ago', $this->formatter->asRelativeTime($dateThen, $dateNow));
// Invert all the DateIntervals
$interval_1_second->invert = true;
$interval_244_seconds->invert = true;
$interval_1_minute->invert = true;
$interval_33_minutes->invert = true;
$interval_1_hour->invert = true;
$interval_6_hours->invert = true;
$interval_1_day->invert = true;
$interval_89_days->invert = true;
$interval_1_month->invert = true;
$interval_5_months->invert = true;
$interval_1_year->invert = true;
$interval_12_years->invert = true;
// Pass a inverted DateInterval
$this->assertSame('in a second', $this->formatter->asRelativeTime($interval_1_second));
$this->assertSame('in 244 seconds', $this->formatter->asRelativeTime($interval_244_seconds));
$this->assertSame('in a minute', $this->formatter->asRelativeTime($interval_1_minute));
$this->assertSame('in 33 minutes', $this->formatter->asRelativeTime($interval_33_minutes));
$this->assertSame('in an hour', $this->formatter->asRelativeTime($interval_1_hour));
$this->assertSame('in 6 hours', $this->formatter->asRelativeTime($interval_6_hours));
$this->assertSame('in a day', $this->formatter->asRelativeTime($interval_1_day));
$this->assertSame('in 89 days', $this->formatter->asRelativeTime($interval_89_days));
$this->assertSame('in a month', $this->formatter->asRelativeTime($interval_1_month));
$this->assertSame('in 5 months', $this->formatter->asRelativeTime($interval_5_months));
$this->assertSame('in a year', $this->formatter->asRelativeTime($interval_1_year));
$this->assertSame('in 12 years', $this->formatter->asRelativeTime($interval_12_years));
// Pass a inverted DateInterval string
// $this->assertSame('in a year', $this->formatter->asRelativeTime('2008-05-11T15:30:00Z/2007-03-01T13:00:00Z'));
// Force the reference time and pass a future DateTime
$dateNow = new DateTime('2014-03-13');
$this->assertSame('in a second', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_second]), $dateNow));
$this->assertSame('in 4 minutes', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_244_seconds]), $dateNow));
$this->assertSame('in a minute', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_minute]), $dateNow));
$this->assertSame('in 33 minutes', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_33_minutes]), $dateNow));
$this->assertSame('in an hour', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_hour]), $dateNow));
$this->assertSame('in 6 hours', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_6_hours]), $dateNow));
$this->assertSame('in a day', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_day]), $dateNow));
$this->assertSame('in 2 months', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_89_days]), $dateNow));
$this->assertSame('in a month', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_month]), $dateNow));
$this->assertSame('in 5 months', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_5_months]), $dateNow));
$this->assertSame('in a year', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_1_year]), $dateNow));
$this->assertSame('in 12 years', $this->formatter->asRelativeTime($this->buildDateSubIntervals('2014-03-13', [$interval_12_years]), $dateNow));
// Tricky 31-days month stuff
// See: http://www.gnu.org/software/tar/manual/html_section/Relative-items-in-date-strings.html
$dateNow = new DateTime('2014-03-03');
$dateThen = new DateTime('2014-03-31');
$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));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time()));
}
// number format
public function testIntlAsInteger()
{
$this->testAsInteger();
}
public function testAsInteger()
{
$this->assertSame("123", $this->formatter->asInteger(123));
$this->assertSame("123", $this->formatter->asInteger(123.23));
$this->assertSame("123", $this->formatter->asInteger(123.53));
$this->assertSame("0", $this->formatter->asInteger(0));
$this->assertSame("-123", $this->formatter->asInteger(-123.23));
$this->assertSame("-123", $this->formatter->asInteger(-123.53));
$this->assertSame("123,456", $this->formatter->asInteger(123456));
$this->assertSame("123,456", $this->formatter->asInteger(123456.789));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testAsIntegerException()
{
$this->assertSame("0", $this->formatter->asInteger('a'));
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testAsIntegerException2()
{
$this->assertSame("0", $this->formatter->asInteger('-123abc'));
}
public function testIntlAsDecimal()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDecimal($value, 2));
$this->assertSame("123.1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123;
$this->assertSame("123", $this->formatter->asDecimal($value));
$this->assertSame("123.00", $this->formatter->asDecimal($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = '.';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDecimal($value));
$this->assertSame("123,1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123123.123;
$this->assertSame("123.123", $this->formatter->asDecimal($value, 0));
$this->assertSame("123.123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123,1", $this->formatter->asDecimal($value, 1));
$this->formatter->thousandSeparator = ' ';
$this->assertSame("12 31 23,1", $this->formatter->asDecimal($value, 1, [\NumberFormatter::GROUPING_SIZE => 2]));
$value = 123123.123;
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123", $this->formatter->asDecimal($value, 0));
$this->assertSame("123 123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->decimalSeparator = null;
$this->formatter->thousandSeparator = null;
$value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
}
public function testAsDecimal()
{
$value = 123.12;
$this->assertSame("123.12", $this->formatter->asDecimal($value));
$this->assertSame("123.1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123;
$this->assertSame("123.00", $this->formatter->asDecimal($value));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = '.';
$value = 123.12;
$this->assertSame("123,12", $this->formatter->asDecimal($value));
$this->assertSame("123,1", $this->formatter->asDecimal($value, 1));
$this->assertSame("123", $this->formatter->asDecimal($value, 0));
$value = 123123.123;
$this->assertSame("123.123,12", $this->formatter->asDecimal($value));
$value = 123123.123;
$this->assertSame("123.123,12", $this->formatter->asDecimal($value));
$this->assertSame("123.123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->decimalSeparator = ',';
$this->formatter->thousandSeparator = ' ';
$this->assertSame("123 123,12", $this->formatter->asDecimal($value));
$this->assertSame("123 123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->thousandSeparator = '';
$this->assertSame("123123,12", $this->formatter->asDecimal($value));
$this->assertSame("123123,12", $this->formatter->asDecimal($value, 2));
$this->formatter->decimalSeparator = null;
$this->formatter->thousandSeparator = null;
$value = '-123456.123';
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value, 3));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
}
public function testIntlAsPercent()
{
$this->testAsPercent();
}
public function testAsPercent()
{
if (defined('HHVM_VERSION')) { // the default format is different on HHVM
$this->markTestSkipped('HHVM behaves quite different in the default patterns used for formatting.');
}
$value = '123';
$this->assertSame('12,300%', $this->formatter->asPercent($value));
$value = '0.1234';
$this->assertSame("12%", $this->formatter->asPercent($value));
$value = '-0.009343';
$this->assertSame("-1%", $this->formatter->asPercent($value));
$this->assertSame('12,300%', $this->formatter->asPercent(123));
$this->assertSame('12,300%', $this->formatter->asPercent('123'));
$this->assertSame("12%", $this->formatter->asPercent(0.1234));
$this->assertSame("12%", $this->formatter->asPercent('0.1234'));
$this->assertSame("-1%", $this->formatter->asPercent(-0.009343));
$this->assertSame("-1%", $this->formatter->asPercent('-0.009343'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null));
}
public function testAsScientific()
public function testIntlAsCurrency()
{
$this->formatter->locale = 'en-US';
$this->assertSame('$123.00', $this->formatter->asCurrency('123'));
$this->assertSame('$123,456.00', $this->formatter->asCurrency('123456'));
$this->assertSame('$0.00', $this->formatter->asCurrency('0'));
$this->formatter->locale = 'en-US';
$this->formatter->currencyCode = 'USD';
$this->assertSame('$123.00', $this->formatter->asCurrency('123'));
$this->assertSame('$123,456.00', $this->formatter->asCurrency('123456'));
$this->assertSame('$0.00', $this->formatter->asCurrency('0'));
// Starting from ICU 52.1, negative currency value will be formatted as -$123,456.12
// see: http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source/data/locales/en.txt
// $value = '-123456.123';
// $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
$this->formatter->locale = 'de-DE';
$this->formatter->currencyCode = null;
$this->assertSame('123,00 €', $this->formatter->asCurrency('123'));
$this->formatter->currencyCode = 'USD';
$this->assertSame('123,00 $', $this->formatter->asCurrency('123'));
$this->formatter->currencyCode = 'EUR';
$this->assertSame('123,00 €', $this->formatter->asCurrency('123'));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
}
public function testAsCurrency()
{
$this->formatter->currencyCode = 'USD';
$this->assertSame('USD 123.00', $this->formatter->asCurrency('123'));
$this->assertSame('USD 0.00', $this->formatter->asCurrency('0'));
$this->assertSame('USD -123.45', $this->formatter->asCurrency('-123.45'));
$this->assertSame('USD -123.45', $this->formatter->asCurrency(-123.45));
$this->formatter->currencyCode = 'EUR';
$this->assertSame('EUR 123.00', $this->formatter->asCurrency('123'));
$this->assertSame('EUR 0.00', $this->formatter->asCurrency('0'));
$this->assertSame('EUR -123.45', $this->formatter->asCurrency('-123.45'));
$this->assertSame('EUR -123.45', $this->formatter->asCurrency(-123.45));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
}
public function testIntlAsScientific()
{
if (defined('HHVM_VERSION')) { // the default format is different on HHVM
$this->markTestSkipped('HHVM behaves quite different in the default patterns used for formatting.');
}
$value = '123';
$this->assertSame('1.23E2', $this->formatter->asScientific($value));
$value = '123456';
$this->assertSame("1.23456E5", $this->formatter->asScientific($value));
$value = '-123456.123';
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
}
public function testAsCurrency()
public function testAsScientific()
{
$value = '123';
$this->assertSame('$123.00', $this->formatter->asCurrency($value));
$value = '123.456';
$this->assertSame("$123.46", $this->formatter->asCurrency($value));
// Starting from ICU 52.1, negative currency value will be formatted as -$123,456.12
// see: http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source/data/locales/en.txt
// $value = '-123456.123';
// $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
$this->assertSame('1.23E+2', $this->formatter->asScientific($value, 2));
$value = '123456';
$this->assertSame("1.234560E+5", $this->formatter->asScientific($value));
$value = '-123456.123';
$this->assertSame("-1.234561E+5", $this->formatter->asScientific($value));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
}
public function testDate()
public function testIntlAsSpellout()
{
$time = time();
$this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time));
$this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long'));
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
$this->assertSame('one hundred twenty-three', $this->formatter->asSpellout(123));
$this->formatter->locale = 'de_DE';
$this->assertSame('ein­hundert­drei­und­zwanzig', $this->formatter->asSpellout(123));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSpellout(null));
}
public function testIntlAsOrdinal()
{
$this->assertSame('0th', $this->formatter->asOrdinal(0));
$this->assertSame('1st', $this->formatter->asOrdinal(1));
$this->assertSame('2nd', $this->formatter->asOrdinal(2));
$this->assertSame('3rd', $this->formatter->asOrdinal(3));
$this->assertSame('5th', $this->formatter->asOrdinal(5));
$this->formatter->locale = 'de_DE';
$this->assertSame('0.', $this->formatter->asOrdinal(0));
$this->assertSame('1.', $this->formatter->asOrdinal(1));
$this->assertSame('2.', $this->formatter->asOrdinal(2));
$this->assertSame('3.', $this->formatter->asOrdinal(3));
$this->assertSame('5.', $this->formatter->asOrdinal(5));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asOrdinal(null));
}
public function testIntlAsShortSize()
{
$this->formatter->numberFormatterOptions = [
\NumberFormatter::MIN_FRACTION_DIGITS => 0,
\NumberFormatter::MAX_FRACTION_DIGITS => 2,
];
// tests for base 1000
$this->formatter->sizeFormatBase = 1000;
$this->assertSame("999 B", $this->formatter->asShortSize(999));
$this->assertSame("1.05 MB", $this->formatter->asShortSize(1024 * 1024));
$this->assertSame("1 KB", $this->formatter->asShortSize(1000));
$this->assertSame("1.02 KB", $this->formatter->asShortSize(1023));
$this->assertNotEquals("3 PB", $this->formatter->asShortSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB
// tests for base 1024
$this->formatter->sizeFormatBase = 1024;
$this->assertSame("1 KiB", $this->formatter->asShortSize(1024));
$this->assertSame("1 MiB", $this->formatter->asShortSize(1024 * 1024));
// https://github.com/yiisoft/yii2/issues/4960
$this->assertSame("1023 B", $this->formatter->asShortSize(1023));
$this->assertSame("5 GiB", $this->formatter->asShortSize(5 * 1024 * 1024 * 1024));
$this->assertNotEquals("5 PiB", $this->formatter->asShortSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB
//$this->assertSame("1 YiB", $this->formatter->asShortSize(pow(2, 80)));
$this->assertSame("2 GiB", $this->formatter->asShortSize(2147483647)); // round 1.999 up to 2
$this->formatter->decimalSeparator = ',';
$this->formatter->numberFormatterOptions = [];
$this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
}
public function testAsShortSize()
{
// tests for base 1000
$this->formatter->sizeFormatBase = 1000;
$this->assertSame("999 B", $this->formatter->asShortSize(999));
$this->assertSame("1.05 MB", $this->formatter->asShortSize(1024 * 1024));
$this->assertSame("1.0486 MB", $this->formatter->asShortSize(1024 * 1024, 4));
$this->assertSame("1.00 KB", $this->formatter->asShortSize(1000));
$this->assertSame("1.02 KB", $this->formatter->asShortSize(1023));
$this->assertNotEquals("3 PB", $this->formatter->asShortSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB
// tests for base 1024
$this->formatter->sizeFormatBase = 1024;
$this->assertSame("1.00 KiB", $this->formatter->asShortSize(1024));
$this->assertSame("1.00 MiB", $this->formatter->asShortSize(1024 * 1024));
// https://github.com/yiisoft/yii2/issues/4960
$this->assertSame("1023 B", $this->formatter->asShortSize(1023));
$this->assertSame("5.00 GiB", $this->formatter->asShortSize(5 * 1024 * 1024 * 1024));
$this->assertNotEquals("5.00 PiB", $this->formatter->asShortSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB
//$this->assertSame("1 YiB", $this->formatter->asShortSize(pow(2, 80)));
$this->assertSame("2.00 GiB", $this->formatter->asShortSize(2147483647)); // round 1.999 up to 2
$this->formatter->decimalSeparator = ',';
$this->assertSame("1,001 KiB", $this->formatter->asShortSize(1025, 3));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
}
public function testIntlAsSize()
{
$this->formatter->numberFormatterOptions = [
\NumberFormatter::MIN_FRACTION_DIGITS => 0,
\NumberFormatter::MAX_FRACTION_DIGITS => 2,
];
// tests for base 1000
$this->formatter->sizeFormatBase = 1000;
$this->assertSame("999 bytes", $this->formatter->asSize(999));
$this->assertSame("1.05 megabytes", $this->formatter->asSize(1024 * 1024));
$this->assertSame("1 kilobyte", $this->formatter->asSize(1000));
$this->assertSame("1.02 kilobytes", $this->formatter->asSize(1023));
$this->assertSame("3 gigabytes", $this->formatter->asSize(3 * 1000 * 1000 * 1000));
$this->assertNotEquals("3 PB", $this->formatter->asSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB
// tests for base 1024
$this->formatter->sizeFormatBase = 1024;
$this->assertSame("1 kibibyte", $this->formatter->asSize(1024));
$this->assertSame("1 mebibyte", $this->formatter->asSize(1024 * 1024));
// https://github.com/yiisoft/yii2/issues/4960
// $this->assertSame("1023 bytes", $this->formatter->asSize(1023));
$this->assertSame("5 gibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024));
$this->assertNotEquals("5 pibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB
//$this->assertSame("1 YiB", $this->formatter->asSize(pow(2, 80)));
$this->assertSame("2 gibibytes", $this->formatter->asSize(2147483647)); // round 1.999 up to 2
$this->formatter->decimalSeparator = ',';
$this->formatter->numberFormatterOptions = [];
// https://github.com/yiisoft/yii2/issues/4960
// $this->assertSame("1,001 KiB", $this->formatter->asSize(1025, 3));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
}
public function testAsSize()
{
// tests for base 1000
$this->formatter->sizeFormatBase = 1000;
$this->assertSame("999 bytes", $this->formatter->asSize(999));
$this->assertSame("1.05 megabytes", $this->formatter->asSize(1024 * 1024));
// https://github.com/yiisoft/yii2/issues/4960
// $this->assertSame("1.0486 megabytes", $this->formatter->asSize(1024 * 1024, 4));
$this->assertSame("1 kilobyte", $this->formatter->asSize(1000));
$this->assertSame("1.02 kilobytes", $this->formatter->asSize(1023));
$this->assertSame("3 gigabytes", $this->formatter->asSize(3 * 1000 * 1000 * 1000));
$this->assertNotEquals("3 PB", $this->formatter->asSize(3 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000)); // this is 3 EB not 3 PB
// tests for base 1024
$this->formatter->sizeFormatBase = 1024;
$this->assertSame("1 kibibyte", $this->formatter->asSize(1024));
$this->assertSame("1 mebibyte", $this->formatter->asSize(1024 * 1024));
// https://github.com/yiisoft/yii2/issues/4960
// $this->assertSame("1023 B", $this->formatter->asSize(1023));
$this->assertSame("5 gibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024));
$this->assertNotEquals("5 pibibytes", $this->formatter->asSize(5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)); // this is 5 EiB not 5 PiB
//$this->assertSame("1 YiB", $this->formatter->asSize(pow(2, 80)));
$this->assertSame("2 gibibytes", $this->formatter->asSize(2147483647)); // round 1.999 up to 2
$this->formatter->decimalSeparator = ',';
$this->formatter->numberFormatterOptions = [];
// https://github.com/yiisoft/yii2/issues/4960
// $this->assertSame("1,001 kibibytes", $this->formatter->asSize(1025));
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
}
}
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