Browse Source

better fix for #5448 not using customized DateTime class

fixes #6263 and has no problem with PHP Bug:

> it is a PHP bug: https://bugs.php.net/bug.php?id=45543
> Fixed in this commit: php/php-src@22dba2f#diff-7b738accc3d60f74c259da18588ddc5dL2996
>
> Fixed in PHP >5.4.26 and >5.5.10. http://3v4l.org/mlZX7
tags/2.0.1
Carsten Brandt 10 years ago
parent
commit
7325fdd7ed
  1. 97
      framework/i18n/DateTimeExtended.php
  2. 51
      framework/i18n/Formatter.php
  3. 25
      tests/unit/framework/i18n/FormatterDateTest.php
  4. 2
      tests/unit/framework/i18n/FormatterNumberTest.php
  5. 3
      tests/unit/framework/i18n/FormatterTest.php

97
framework/i18n/DateTimeExtended.php

@ -1,97 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
/**
* DateTimeExtended is an extended version of the [PHP DateTime class](http://php.net/manual/en/class.datetime.php).
*
* It provides more accurate handling of date-only values which can not be converted between timezone as
* they do not include any time information.
*
* **Important Note:** This implementation was created to be used by [[Formatter]] internally, it may not behave
* as expected when used directly in your code. Normally you should not need to use this class in your application code.
* Use the original PHP [DateTime](http://php.net/manual/en/class.datetime.php) class instead.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0.1
*/
class DateTimeExtended extends \DateTime
{
private $_isDateOnly = false;
/**
* The DateTimeExtended constructor.
*
* @param string $time
* @param \DateTimeZone $timezone
* @return DateTimeExtended
* @see http://php.net/manual/en/datetime.construct.php
*/
public function __construct ($time = 'now', \DateTimeZone $timezone = null)
{
parent::__construct($time, $timezone);
$info = date_parse($time);
if ($info['hour'] === false && $info['minute'] === false && $info['second'] === false) {
$this->_isDateOnly = true;
} else {
$this->_isDateOnly = false;
}
}
/**
* Parse a string into a new DateTime object according to the specified format
* @param string $format Format accepted by date().
* @param string $time String representing the time.
* @param \DateTimeZone $timezone A DateTimeZone object representing the desired time zone.
* @return DateTimeExtended
* @link http://php.net/manual/en/datetime.createfromformat.php
*/
public static function createFromFormat ($format, $time, $timezone = null)
{
if (($originalDateTime = parent::createFromFormat($format, $time, $timezone)) === false) {
return false;
}
$info = date_parse_from_format($format, $time);
/** @var $dateTime \DateTime */
$dateTime = new static;
if ($info['hour'] === false && $info['minute'] === false && $info['second'] === false) {
$dateTime->_isDateOnly = true;
} else {
$dateTime->_isDateOnly = false;
}
$dateTime->setTimezone($originalDateTime->getTimezone());
$dateTime->setTimestamp($originalDateTime->getTimestamp());
return $dateTime;
}
public function isDateOnly()
{
return $this->_isDateOnly;
}
public function getTimezone()
{
if ($this->_isDateOnly) {
return false;
} else {
return parent::getTimezone();
}
}
public function getOffset()
{
if ($this->_isDateOnly) {
return false;
} else {
return parent::getOffset();
}
}
}

51
framework/i18n/Formatter.php

@ -529,16 +529,18 @@ class Formatter extends Component
*/
private function formatDateTimeValue($value, $format, $type)
{
$timestamp = $this->normalizeDatetimeValue($value);
if ($timestamp === null) {
return $this->nullDisplay;
}
$timeZone = $this->timeZone;
// avoid time zone conversion for date-only values
if ($type === 'date' && $timestamp->isDateOnly()) {
$timeZone = $this->defaultTimeZone;
if ($type === 'date') {
list($timestamp, $hasTimeInfo) = $this->normalizeDatetimeValue($value, true);
if (!$hasTimeInfo) {
$timeZone = $this->defaultTimeZone;
}
} else {
$timeZone = $this->timeZone;
$timestamp = $this->normalizeDatetimeValue($value);
}
if ($timestamp === null) {
return $this->nullDisplay;
}
if ($this->_intlLoaded) {
@ -584,11 +586,19 @@ class Formatter extends Component
* The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
* - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
*
* @return DateTimeExtended the normalized datetime value
* @param boolean $checkTimeInfo whether to also check if the date/time value has some time information attached.
* Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
* timestamp and the second a boolean indicating whether the timestamp has time information or it is just a date value.
* This parameter is available since version 2.0.1.
* @return DateTime|array the normalized datetime value.
* Since version 2.0.1 this may also return an array if `$checkTimeInfo` is true.
* The first element of the array is the normalized timestamp and the second is a boolean indicating whether
* the timestamp has time information or it is just a date value.
* @throws InvalidParamException if the input value can not be evaluated as a date value.
*/
protected function normalizeDatetimeValue($value)
protected function normalizeDatetimeValue($value, $checkTimeInfo = false)
{
// checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
// skip any processing
return $value;
@ -598,18 +608,23 @@ class Formatter extends Component
}
try {
if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
if (($timestamp = DateTimeExtended::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) {
if (($timestamp = DateTime::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) {
throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp.");
}
return $timestamp;
} elseif (($timestamp = DateTimeExtended::createFromFormat('Y-m-d', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
return $timestamp;
} elseif (($timestamp = DateTimeExtended::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
return $timestamp;
return $checkTimeInfo ? [$timestamp, true] : $timestamp;
} elseif (($timestamp = DateTime::createFromFormat('Y-m-d', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
return $checkTimeInfo ? [$timestamp, false] : $timestamp;
} elseif (($timestamp = DateTime::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
return $checkTimeInfo ? [$timestamp, true] : $timestamp;
}
// finally try to create a DateTime object with the value
$timestamp = new DateTimeExtended($value, new DateTimeZone($this->defaultTimeZone));
return $timestamp;
if ($checkTimeInfo) {
$timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
$info = date_parse($value);
return [$timestamp, !($info['hour'] === false && $info['minute'] === false && $info['second'] === false)];
} else {
return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
}
} 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);

25
tests/unit/framework/i18n/FormatterDateTest.php

@ -2,7 +2,6 @@
namespace yiiunit\framework\i18n;
use NumberFormatter;
use yii\i18n\Formatter;
use Yii;
use yiiunit\TestCase;
@ -501,4 +500,28 @@ class FormatterDateTest extends TestCase
$this->assertSame('2014-08-01', $this->formatter->asDate('2014-08-01', 'yyyy-MM-dd'));
}
/**
* https://github.com/yiisoft/yii2/issues/6263
*
* it is a PHP bug: https://bugs.php.net/bug.php?id=45543
* Fixed in this commit: https://github.com/php/php-src/commit/22dba2f5f3211efe6c3b9bb24734c811ca64c68c#diff-7b738accc3d60f74c259da18588ddc5dL2996
* Fixed in PHP >5.4.26 and >5.5.10. http://3v4l.org/mlZX7
*
* @dataProvider provideTimezones
*/
public function testIssue6263($dtz)
{
$this->formatter->defaultTimeZone = $dtz;
$this->formatter->timeZone = 'UTC';
$this->assertEquals('24.11.2014 11:48:53', $this->formatter->format(1416829733, ['date', 'php:d.m.Y H:i:s']));
$this->formatter->timeZone = 'Europe/Berlin';
$this->assertEquals('24.11.2014 12:48:53', $this->formatter->format(1416829733, ['date', 'php:d.m.Y H:i:s']));
$this->assertFalse(DateTime::createFromFormat('Y-m-d', 1416829733));
$this->assertFalse(DateTime::createFromFormat('Y-m-d', '2014-05-08 12:48:53'));
$this->assertFalse(DateTime::createFromFormat('Y-m-d H:i:s', 1416829733));
$this->assertFalse(DateTime::createFromFormat('Y-m-d H:i:s', '2014-05-08'));
}
}

2
tests/unit/framework/i18n/FormatterNumberTest.php

@ -6,8 +6,6 @@ use NumberFormatter;
use yii\i18n\Formatter;
use Yii;
use yiiunit\TestCase;
use DateTime;
use DateInterval;
/**
* @group i18n

3
tests/unit/framework/i18n/FormatterTest.php

@ -2,12 +2,9 @@
namespace yiiunit\framework\i18n;
use NumberFormatter;
use yii\i18n\Formatter;
use Yii;
use yiiunit\TestCase;
use DateTime;
use DateInterval;
/**
* Test for basic formatter functions

Loading…
Cancel
Save