Skip to content

Commit

Permalink
Merge pull request #7744 from cakephp/immutable-time
Browse files Browse the repository at this point in the history
Add Immutable dates/times to the ORM
  • Loading branch information
lorenzo committed Nov 29, 2015
2 parents 618550e + 8e5b9e3 commit 34d7259
Show file tree
Hide file tree
Showing 14 changed files with 1,142 additions and 530 deletions.
67 changes: 56 additions & 11 deletions src/Database/Type/DateTimeType.php
Expand Up @@ -31,7 +31,11 @@ class DateTimeType extends Type
/**
* The class to use for representing date objects
*
* This property can only be used before an instance of this type
* class is constructed. After that use `useMutable()` or `useImmutable()` instead.
*
* @var string
* @deprecated Use DateTimeType::useMutable() or DateTimeType::useImmutable() instead.
*/
public static $dateTimeClass = 'Cake\I18n\Time';

Expand Down Expand Up @@ -65,18 +69,20 @@ class DateTimeType extends Type
*/
protected $_datetimeInstance;

/**
* The classname to use when creating objects.
*
* @var string
*/
protected $_className;

/**
* {@inheritDoc}
*/
public function __construct($name = null)
{
parent::__construct($name);

if (!class_exists(static::$dateTimeClass)) {
static::$dateTimeClass = 'DateTime';
}

$this->_datetimeInstance = new static::$dateTimeClass;
$this->_setClassName(static::$dateTimeClass, 'DateTime');
}

/**
Expand All @@ -92,7 +98,8 @@ public function toDatabase($value, Driver $driver)
return $value;
}
if (is_int($value)) {
$value = new static::$dateTimeClass('@' . $value);
$class = $this->_className;
$value = new $class('@' . $value);
}
return $value->format($this->_format);
}
Expand Down Expand Up @@ -130,7 +137,7 @@ public function marshal($value)
return $value;
}

$class = static::$dateTimeClass;
$class = $this->_className;
try {
$compare = $date = false;
if ($value === '' || $value === null || $value === false || $value === true) {
Expand Down Expand Up @@ -196,12 +203,12 @@ public function useLocaleParser($enable = true)
$this->_useLocaleParser = $enable;
return $this;
}
if (method_exists(static::$dateTimeClass, 'parseDateTime')) {
if (method_exists($this->_className, 'parseDateTime')) {
$this->_useLocaleParser = $enable;
return $this;
}
throw new RuntimeException(
sprintf('Cannot use locale parsing with the %s class', static::$dateTimeClass)
sprintf('Cannot use locale parsing with the %s class', $this->_className)
);
}

Expand All @@ -220,6 +227,44 @@ public function setLocaleFormat($format)
return $this;
}

/**
* Change the preferred class name to the FrozenTime implementation.
*
* @return $this
*/
public function useImmutable()
{
$this->_setClassName('Cake\I18n\FrozenTime', 'DateTimeImmutable');
return $this;
}

/**
* Set the classname to use when building objects.
*
* @param string $class The classname to use.
* @param string $fallback The classname to use when the preferred class does not exist.
* @return void
*/
protected function _setClassName($class, $fallback)
{
if (!class_exists($class)) {
$class = $fallback;
}
$this->_className = $class;
$this->_datetimeInstance = new $this->_className;
}

/**
* Change the preferred class name to the mutable Time implementation.
*
* @return $this
*/
public function useMutable()
{
$this->_setClassName('Cake\I18n\Time', 'DateTime');
return $this;
}

/**
* Converts a string into a DateTime object after parseing it using the locale
* aware parser with the specified format.
Expand All @@ -229,7 +274,7 @@ public function setLocaleFormat($format)
*/
protected function _parseValue($value)
{
$class = static::$dateTimeClass;
$class = $this->_className;
return $class::parseDateTime($value, $this->_localeFormat);
}
}
44 changes: 43 additions & 1 deletion src/Database/Type/DateType.php
Expand Up @@ -22,7 +22,11 @@ class DateType extends DateTimeType
/**
* The class to use for representing date objects
*
* This property can only be used before an instance of this type
* class is constructed. After that use `useMutable()` or `useImmutable()` instead.
*
* @var string
* @deprecated Use DateType::useMutable() or DateType::useImmutable() instead.
*/
public static $dateTimeClass = 'Cake\I18n\Date';

Expand All @@ -33,6 +37,28 @@ class DateType extends DateTimeType
*/
protected $_format = 'Y-m-d';

/**
* Change the preferred class name to the FrozenDate implementation.
*
* @return $this
*/
public function useImmutable()
{
$this->_setClassName('Cake\I18n\FrozenDate', 'DateTimeImmutable');
return $this;
}

/**
* Change the preferred class name to the mutable Date implementation.
*
* @return $this
*/
public function useMutable()
{
$this->_setClassName('Cake\I18n\Date', 'DateTime');
return $this;
}

/**
* Convert request data into a datetime object.
*
Expand Down Expand Up @@ -69,7 +95,23 @@ public function toPHP($value, Driver $driver)
*/
protected function _parseValue($value)
{
$class = static::$dateTimeClass;
$class = $this->_className;
return $class::parseDate($value, $this->_localeFormat);
}

/**
* Test that toImmutable changes all the methods to create frozen time instances.
*
* @return void
*/
public function testToImmutableAndToMutable()
{
$this->type->useImmutable();
$this->assertInstanceOf('DateTimeImmutable', $this->type->marshal('2015-11-01'));
$this->assertInstanceOf('DateTimeImmutable', $this->type->toPhp('2015-11-01', $this->driver));

$this->type->useMutable();
$this->assertInstanceOf('DateTime', $this->type->marshal('2015-11-01'));
$this->assertInstanceOf('DateTime', $this->type->toPhp('2015-11-01', $this->driver));
}
}
2 changes: 1 addition & 1 deletion src/Database/Type/TimeType.php
Expand Up @@ -34,7 +34,7 @@ class TimeType extends DateTimeType
*/
protected function _parseValue($value)
{
$class = static::$dateTimeClass;
$class = $this->_className;
return $class::parseTime($value, $this->_localeFormat);
}
}
161 changes: 1 addition & 160 deletions src/I18n/Date.php
Expand Up @@ -130,165 +130,6 @@ class Date extends MutableDate implements JsonSerializable
*/
public function timeAgoInWords(array $options = [])
{
$date = $this;

$options += [
'from' => static::now(),
'timezone' => null,
'format' => static::$wordFormat,
'accuracy' => static::$wordAccuracy,
'end' => static::$wordEnd,
'relativeString' => __d('cake', '%s ago'),
'absoluteString' => __d('cake', 'on %s'),
];
if (is_string($options['accuracy'])) {
foreach (static::$wordAccuracy as $key => $level) {
$options[$key] = $options['accuracy'];
}
} else {
$options['accuracy'] += static::$wordAccuracy;
}
if ($options['timezone']) {
$date = $date->timezone($options['timezone']);
}

$now = $options['from']->format('U');
$inSeconds = $date->format('U');
$backwards = ($inSeconds > $now);

$futureTime = $now;
$pastTime = $inSeconds;
if ($backwards) {
$futureTime = $inSeconds;
$pastTime = $now;
}
$diff = $futureTime - $pastTime;

if (!$diff) {
return __d('cake', 'today');
}

if ($diff > abs($now - (new static($options['end']))->format('U'))) {
return sprintf($options['absoluteString'], $date->i18nFormat($options['format']));
}

// If more than a week, then take into account the length of months
if ($diff >= 604800) {
list($future['H'], $future['i'], $future['s'], $future['d'], $future['m'], $future['Y']) = explode('/', date('H/i/s/d/m/Y', $futureTime));

list($past['H'], $past['i'], $past['s'], $past['d'], $past['m'], $past['Y']) = explode('/', date('H/i/s/d/m/Y', $pastTime));
$weeks = $days = $hours = $minutes = $seconds = 0;

$years = $future['Y'] - $past['Y'];
$months = $future['m'] + ((12 * $years) - $past['m']);

if ($months >= 12) {
$years = floor($months / 12);
$months = $months - ($years * 12);
}
if ($future['m'] < $past['m'] && $future['Y'] - $past['Y'] === 1) {
$years--;
}

if ($future['d'] >= $past['d']) {
$days = $future['d'] - $past['d'];
} else {
$daysInPastMonth = date('t', $pastTime);
$daysInFutureMonth = date('t', mktime(0, 0, 0, $future['m'] - 1, 1, $future['Y']));

if (!$backwards) {
$days = ($daysInPastMonth - $past['d']) + $future['d'];
} else {
$days = ($daysInFutureMonth - $past['d']) + $future['d'];
}

if ($future['m'] != $past['m']) {
$months--;
}
}

if (!$months && $years >= 1 && $diff < ($years * 31536000)) {
$months = 11;
$years--;
}

if ($months >= 12) {
$years = $years + 1;
$months = $months - 12;
}

if ($days >= 7) {
$weeks = floor($days / 7);
$days = $days - ($weeks * 7);
}
} else {
$years = $months = $weeks = 0;
$days = floor($diff / 86400);

$diff = $diff - ($days * 86400);

$hours = floor($diff / 3600);
$diff = $diff - ($hours * 3600);

$minutes = floor($diff / 60);
$diff = $diff - ($minutes * 60);
$seconds = $diff;
}

$fWord = $options['accuracy']['day'];
if ($years > 0) {
$fWord = $options['accuracy']['year'];
} elseif (abs($months) > 0) {
$fWord = $options['accuracy']['month'];
} elseif (abs($weeks) > 0) {
$fWord = $options['accuracy']['week'];
} elseif (abs($days) > 0) {
$fWord = $options['accuracy']['day'];
}

$fNum = str_replace(['year', 'month', 'week', 'day'], [1, 2, 3, 4], $fWord);

$relativeDate = '';
if ($fNum >= 1 && $years > 0) {
$relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '{0} year', '{0} years', $years, $years);
}
if ($fNum >= 2 && $months > 0) {
$relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '{0} month', '{0} months', $months, $months);
}
if ($fNum >= 3 && $weeks > 0) {
$relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '{0} week', '{0} weeks', $weeks, $weeks);
}
if ($fNum >= 4 && $days > 0) {
$relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '{0} day', '{0} days', $days, $days);
}

// When time has passed
if (!$backwards && $relativeDate) {
return sprintf($options['relativeString'], $relativeDate);
}
if (!$backwards) {
$aboutAgo = [
'day' => __d('cake', 'about a day ago'),
'week' => __d('cake', 'about a week ago'),
'month' => __d('cake', 'about a month ago'),
'year' => __d('cake', 'about a year ago')
];

return $aboutAgo[$fWord];
}

// When time is to come
if (!$relativeDate) {
$aboutIn = [
'day' => __d('cake', 'in about a day'),
'week' => __d('cake', 'in about a week'),
'month' => __d('cake', 'in about a month'),
'year' => __d('cake', 'in about a year')
];

return $aboutIn[$fWord];
}

return $relativeDate;
return (new RelativeTimeFormatter($this))->dateAgoInWords($options);
}
}
3 changes: 2 additions & 1 deletion src/I18n/DateFormatTrait.php
Expand Up @@ -127,8 +127,9 @@ public function i18nFormat($format = null, $timezone = null, $locale = null)
$time = $this;

if ($timezone) {
// Handle the immutable and mutable object cases.
$time = clone $this;
$time->timezone($timezone);
$time = $time->timezone($timezone);
}

$format = $format !== null ? $format : static::$_toStringFormat;
Expand Down

0 comments on commit 34d7259

Please sign in to comment.