mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-24 02:27:22 +08:00
Better date parsing and formatting including 32 bit support
Enhances `normalizeDateTimeValue` to return a DateTime object instead of a converted double value, that fails. The DateTime object input is supported by 32 bit, 64 bit, as well as the `IntlDateFormatter` to format all years. (including fixing of the Y2K38 bug). Fixes issue in #4989. close #5000
This commit is contained in:
committed by
Carsten Brandt
parent
a8ed099b99
commit
18b57af5ac
@@ -494,8 +494,8 @@ class Formatter extends Component
|
||||
*/
|
||||
private function formatDateTimeValue($value, $format, $type)
|
||||
{
|
||||
$value = $this->normalizeDatetimeValue($value);
|
||||
if ($value === null) {
|
||||
$timestamp = $this->normalizeDatetimeValue($value);
|
||||
if ($timestamp === null) {
|
||||
return $this->nullDisplay;
|
||||
}
|
||||
|
||||
@@ -517,42 +517,49 @@ class Formatter extends Component
|
||||
if ($formatter === null) {
|
||||
throw new InvalidConfigException(intl_get_error_message());
|
||||
}
|
||||
return $formatter->format($value);
|
||||
return $formatter->format($timestamp);
|
||||
} else {
|
||||
if (strncmp($format, 'php:', 4) === 0) {
|
||||
$format = substr($format, 4);
|
||||
} else {
|
||||
$format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
|
||||
}
|
||||
$date = new DateTime(null, new \DateTimeZone($this->timeZone));
|
||||
$date->setTimestamp($value);
|
||||
return $date->format($format);
|
||||
if ($this->timeZone != null) {
|
||||
$timestamp->setTimezone(new \DateTimeZone($this->timeZone));
|
||||
}
|
||||
return $timestamp->format($format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given datetime value as a UNIX timestamp that can be taken by various date/time formatting methods.
|
||||
* Normalizes the given datetime value as a DateTime object 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)
|
||||
* @return DateTime the normalized datetime value
|
||||
* @throws InvalidParamException if the input value can not be evaluated as a date value.
|
||||
*/
|
||||
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');
|
||||
}
|
||||
if ($value === null || $value instanceof DateTime) {
|
||||
// skip any processing
|
||||
return $value;
|
||||
|
||||
} elseif ($value instanceof DateTime || $value instanceof DateTimeInterface) {
|
||||
return (double)$value->format('U');
|
||||
} else {
|
||||
return (double)$value;
|
||||
}
|
||||
if (empty($value)) {
|
||||
$value = 0;
|
||||
}
|
||||
try {
|
||||
if (is_numeric($value)) {
|
||||
// process as unix timestamp
|
||||
if (($timestamp = DateTime::createFromFormat('U', $value)) === false) {
|
||||
throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp.");
|
||||
}
|
||||
return $timestamp;
|
||||
}
|
||||
$timestamp = new DateTime($value);
|
||||
return $timestamp;
|
||||
} catch(\Exception $e) {
|
||||
throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage()
|
||||
. "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +580,8 @@ class Formatter extends Component
|
||||
if ($value === null) {
|
||||
return $this->nullDisplay;
|
||||
}
|
||||
return number_format($this->normalizeDatetimeValue($value), 0, '.', '');
|
||||
$timestamp = $this->normalizeDatetimeValue($value);
|
||||
return number_format($timestamp->format('U'), 0, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -617,13 +625,11 @@ class Formatter extends Component
|
||||
if ($referenceTime === null) {
|
||||
$dateNow = new DateTime('now', $timezone);
|
||||
} else {
|
||||
$referenceTime = $this->normalizeDatetimeValue($referenceTime);
|
||||
$dateNow = new DateTime(null, $timezone);
|
||||
$dateNow->setTimestamp($referenceTime);
|
||||
$dateNow = $this->normalizeDatetimeValue($referenceTime);
|
||||
$dateNow->setTimezone($timezone);
|
||||
}
|
||||
|
||||
$dateThen = new DateTime(null, $timezone);
|
||||
$dateThen->setTimestamp($timestamp);
|
||||
$dateThen = $timestamp->setTimezone($timezone);
|
||||
|
||||
$interval = $dateThen->diff($dateNow);
|
||||
}
|
||||
@@ -1020,6 +1026,9 @@ class Formatter extends Component
|
||||
*/
|
||||
protected function normalizeNumericValue($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return 0;
|
||||
}
|
||||
if (is_string($value) && is_numeric($value)) {
|
||||
$value = (float) $value;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(''));
|
||||
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(0));
|
||||
$this->assertSame('Jan 1, 1970', $this->formatter->asDate(false));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDate(null));
|
||||
}
|
||||
@@ -258,6 +259,11 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDatetime(null));
|
||||
}
|
||||
|
||||
public function testIntlAsTimestamp()
|
||||
{
|
||||
$this->testAsTimestamp();
|
||||
}
|
||||
|
||||
public function testAsTimestamp()
|
||||
{
|
||||
$value = time();
|
||||
@@ -275,6 +281,11 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asTimestamp(null));
|
||||
}
|
||||
|
||||
public function testIntlDateRangeLow()
|
||||
{
|
||||
$this->testDateRangeLow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for dates before 1970
|
||||
* https://github.com/yiisoft/yii2/issues/3126
|
||||
@@ -282,18 +293,22 @@ class FormatterTest extends TestCase
|
||||
public function testDateRangeLow()
|
||||
{
|
||||
$this->assertSame('12-08-1922', $this->formatter->asDate('1922-08-12', 'dd-MM-yyyy'));
|
||||
$this->assertSame('14-01-1732', $this->formatter->asDate('1732-01-14', 'dd-MM-yyyy'));
|
||||
}
|
||||
|
||||
/**
|
||||
public function testIntlDateRangeHigh()
|
||||
{
|
||||
$this->testDateRangeHigh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
$this->assertSame('17-12-3048', $this->formatter->asDate('3048-12-17', 'dd-MM-yyyy'));
|
||||
}
|
||||
|
||||
private function buildDateSubIntervals($referenceDate, $intervals)
|
||||
@@ -305,6 +320,11 @@ class FormatterTest extends TestCase
|
||||
return $date;
|
||||
}
|
||||
|
||||
public function testIntlAsRelativeTime()
|
||||
{
|
||||
$this->testAsRelativeTime();
|
||||
}
|
||||
|
||||
public function testAsRelativeTime()
|
||||
{
|
||||
$interval_1_second = new DateInterval("PT1S");
|
||||
@@ -431,6 +451,36 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asRelativeTime(null, time()));
|
||||
}
|
||||
|
||||
public function dateInputs()
|
||||
{
|
||||
return [
|
||||
[false, '2014-13-01', 'yii\base\InvalidParamException'],
|
||||
[false, 'asdfg', 'yii\base\InvalidParamException'],
|
||||
// [(string)strtotime('now'), 'now'], // fails randomly
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dateInputs
|
||||
*/
|
||||
public function testIntlDateInput($expected, $value, $expectedException = null)
|
||||
{
|
||||
$this->testDateInput($expected, $value, $expectedException);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dateInputs
|
||||
*/
|
||||
public function testDateInput($expected, $value, $expectedException = null)
|
||||
{
|
||||
if ($expectedException !== null) {
|
||||
$this->setExpectedException($expectedException);
|
||||
}
|
||||
$this->assertSame($expected, $this->formatter->asDate($value, 'php:U'));
|
||||
$this->assertSame($expected, $this->formatter->asTime($value, 'php:U'));
|
||||
$this->assertSame($expected, $this->formatter->asDatetime($value, 'php:U'));
|
||||
}
|
||||
|
||||
|
||||
// number format
|
||||
|
||||
@@ -452,6 +502,10 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame("123,456", $this->formatter->asInteger(123456));
|
||||
$this->assertSame("123,456", $this->formatter->asInteger(123456.789));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asInteger(null));
|
||||
}
|
||||
@@ -472,22 +526,6 @@ class FormatterTest extends TestCase
|
||||
$this->formatter->asInteger('-123abc');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \yii\base\InvalidParamException
|
||||
*/
|
||||
public function testAsIntegerException3()
|
||||
{
|
||||
$this->formatter->asInteger('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \yii\base\InvalidParamException
|
||||
*/
|
||||
public function testAsIntegerException4()
|
||||
{
|
||||
$this->formatter->asInteger(false);
|
||||
}
|
||||
|
||||
public function testIntlAsDecimal()
|
||||
{
|
||||
$value = 123.12;
|
||||
@@ -523,6 +561,10 @@ class FormatterTest extends TestCase
|
||||
$value = '-123456.123';
|
||||
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
|
||||
}
|
||||
@@ -560,6 +602,10 @@ class FormatterTest extends TestCase
|
||||
$value = '-123456.123';
|
||||
$this->assertSame("-123,456.123", $this->formatter->asDecimal($value, 3));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asDecimal(null));
|
||||
}
|
||||
@@ -578,6 +624,10 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame("-1%", $this->formatter->asPercent(-0.009343));
|
||||
$this->assertSame("-1%", $this->formatter->asPercent('-0.009343'));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asPercent(null));
|
||||
}
|
||||
@@ -607,6 +657,10 @@ class FormatterTest extends TestCase
|
||||
$this->formatter->currencyCode = 'EUR';
|
||||
$this->assertSame('123,00 €', $this->formatter->asCurrency('123'));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
|
||||
}
|
||||
@@ -625,6 +679,10 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame('EUR -123.45', $this->formatter->asCurrency('-123.45'));
|
||||
$this->assertSame('EUR -123.45', $this->formatter->asCurrency(-123.45));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asCurrency(null));
|
||||
}
|
||||
@@ -638,6 +696,10 @@ class FormatterTest extends TestCase
|
||||
$value = '-123456.123';
|
||||
$this->assertSame("-1.23456123E5", $this->formatter->asScientific($value));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
|
||||
}
|
||||
@@ -651,6 +713,10 @@ class FormatterTest extends TestCase
|
||||
$value = '-123456.123';
|
||||
$this->assertSame("-1.234561E+5", $this->formatter->asScientific($value));
|
||||
|
||||
// empty input
|
||||
$this->assertSame("0", $this->formatter->asInteger(false));
|
||||
$this->assertSame("0", $this->formatter->asInteger(""));
|
||||
|
||||
// null display
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asScientific(null));
|
||||
}
|
||||
@@ -808,12 +874,20 @@ class FormatterTest extends TestCase
|
||||
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asSize(null));
|
||||
}
|
||||
|
||||
public function testIntlAsSizeConfiguration()
|
||||
{
|
||||
$this->assertSame("1023 bytes", $this->formatter->asSize(1023));
|
||||
$this->formatter->thousandSeparator = '.';
|
||||
$this->assertSame("1023 bytes", $this->formatter->asSize(1023));
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/yiisoft/yii2/issues/4960
|
||||
*/
|
||||
public function testAsSizeConfiguration()
|
||||
{
|
||||
// $this->formatter->thousandSeparator = '';
|
||||
$this->assertSame("1023 bytes", $this->formatter->asSize(1023));
|
||||
$this->formatter->thousandSeparator = '.';
|
||||
$this->assertSame("1023 bytes", $this->formatter->asSize(1023));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user