Commit 18b57af5 by Kartik Visweswaran Committed by Carsten Brandt

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
parent a8ed099b
......@@ -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,6 +293,12 @@ 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();
}
/**
......@@ -290,10 +307,8 @@ class FormatterTest extends TestCase
*/
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));
}
}
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