Permalink
Browse files

Added some more intelligence to the CakeTime class

  • Loading branch information...
Magnus Johansson authored and markstory committed May 5, 2012
1 parent 14b7210 commit 0729aca706e65a6d7198af22a81c25c10d88638d
Showing with 128 additions and 30 deletions.
  1. +128 −30 lib/Cake/Utility/CakeTime.php
@@ -30,7 +30,7 @@
class CakeTime {
/**
- * The format to use when formatting a time using `TimeHelper::nice()`
+ * The format to use when formatting a time using `CakeTime::nice()`
*
* The format should use the locale strings as defined in the PHP docs under
* `strftime` (http://php.net/manual/en/function.strftime.php)
@@ -39,6 +39,40 @@ class CakeTime {
* @see CakeTime::format()
*/
public static $niceFormat = '%a, %b %eS %Y, %H:%M';
+
+/**
+ * The format to use when formatting a time using `CakeTime::timeAgoInWords()`
+ * and the difference is more than `CakeTime::$wordEnd`
+ *
+ * @var string
+ * @see CakeTime::timeAgoInWords()
+ */
+ public static $wordFormat = 'j/n/y';
+
+/**
+ * The format to use when formatting a time using `CakeTime::timeAgoInWords()`
+ * and the difference is less than `CakeTime::$wordEnd`
+ *
+ * @var array
+ * @see CakeTime::timeAgoInWords()
+ */
+ public static $wordAccuracy = array(
+ 'year' => "day",
+ 'month' => "day",
+ 'week' => "day",
+ 'day' => "hour",
+ 'hour' => "minute",
+ 'minute' => "minute",
+ 'second' => "second",
+ );
+
+/**
+ * The end of relative time telling
+ *
+ * @var string
+ * @see CakeTime::timeAgoInWords()
+ */
+ public static $wordEnd = '+1 month';
/**
* Temporary variable containing timestamp value, used internally convertSpecifiers()
@@ -317,7 +351,9 @@ public static function nice($dateString = null, $timezone = null, $format = null
* Returns a formatted descriptive date string for given datetime string.
*
* If the given date is today, the returned string could be "Today, 16:54".
+ * If the given date is tomorrow, the returned string could be "Tomorrow, 16:54".
* If the given date was yesterday, the returned string could be "Yesterday, 16:54".
+ * If the given date is within next or last week, the returned string could be "On Thursday, 16:54".
* If $dateString's year is the current year, the returned string does not
* include mention of the year.
*
@@ -330,11 +366,21 @@ public static function niceShort($dateString = null, $timezone = null) {
$date = $dateString ? self::fromString($dateString, $timezone) : time();
$y = self::isThisYear($date) ? '' : ' %Y';
+
+ $d = self::_strftime("%w", $date);
+ $day = array(__d('cake', 'Sunday'), __d('cake', 'Monday'), __d('cake', 'Tuesday'),
+ __d('cake', 'Wednesday'), __d('cake', 'Thursday'), __d('cake', 'Friday'), __d('cake', 'Saturday'));
if (self::isToday($dateString, $timezone)) {
$ret = __d('cake', 'Today, %s', self::_strftime("%H:%M", $date));
} elseif (self::wasYesterday($dateString, $timezone)) {
$ret = __d('cake', 'Yesterday, %s', self::_strftime("%H:%M", $date));
+ } elseif (self::isTomorrow($dateString, $timezone)) {
+ $ret = __d('cake', 'Tomorrow, %s', self::_strftime("%H:%M", $date));
+ } elseif (self::wasWithinLast('7 days', $dateString, $timezone)) {
+ $ret = sprintf('%s, %s', $day[$d], self::_strftime("%H:%M", $date));
+ } elseif (self::isWithinNext('7 days', $dateString, $timezone)) {
+ $ret = __d('cake', 'On %s, %s', $day[$d], self::_strftime("%H:%M", $date));
} else {
$format = self::convertSpecifiers("%b %eS{$y}, %H:%M", $date);
$ret = self::_strftime($format, $date);
@@ -576,8 +622,17 @@ public static function toRSS($dateString, $timezone = null) {
* ### Options:
*
* - `format` => a fall back format if the relative time is longer than the duration specified by end
+ * - `accuracy` => Specifies how accurate the date should be described (array)
+ * - year => The format if years > 0 (default "day")
+ * - month => The format if months > 0 (default "day")
+ * - week => The format if weeks > 0 (default "day")
+ * - day => The format if weeks > 0 (default "hour")
+ * - hour => The format if hours > 0 (default "minute")
+ * - minute => The format if minutes > 0 (default "minute")
+ * - second => The format if seconds > 0 (default "second")
* - `end` => The end of relative time telling
* - `userOffset` => Users offset from GMT (in hours)
+ * - `element` => A wrapping HTML element (e.g. span or div)
*
* Relative dates look something like this:
* 3 weeks, 4 days ago
@@ -595,21 +650,26 @@ public static function toRSS($dateString, $timezone = null) {
*/
public static function timeAgoInWords($dateTime, $options = array()) {
$timezone = null;
+ $format = self::$wordFormat;
+ $end = self::$wordEnd;
+ $element = false;
+ $accuracy = self::$wordAccuracy;
+
if (is_array($options)) {
if (isset($options['userOffset'])) {
$timezone = $options['userOffset'];
} elseif (isset($options['timezone'])) {
$timezone = $options['timezone'];
}
- }
- $now = self::fromString(time(), $timezone);
- $inSeconds = self::fromString($dateTime, $timezone);
- $backwards = ($inSeconds > $now);
- $format = 'j/n/y';
- $end = '+1 month';
+ if (isset($options['accuracy'])) {
+ $accuracy = array_merge($accuracy, $options['accuracy']);
+ }
+
+ if (isset($options['element'])) {
+ $element = $options['element'];
+ }
- if (is_array($options)) {
if (isset($options['format'])) {
$format = $options['format'];
unset($options['format']);
@@ -622,6 +682,12 @@ public static function timeAgoInWords($dateTime, $options = array()) {
$format = $options;
}
+ extract($accuracy, EXTR_PREFIX_ALL, 'format');
+
+ $now = self::fromString(time(), $timezone);
+ $inSeconds = self::fromString($dateTime, $timezone);
+ $backwards = ($inSeconds > $now);
+
if ($backwards) {
$futureTime = $inSeconds;
$pastTime = $now;
@@ -710,40 +776,46 @@ public static function timeAgoInWords($dateTime, $options = array()) {
$relativeDate = __d('cake', 'on %s', date($format, $inSeconds));
} else {
if ($years > 0) {
- // years and months and days
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d year', '%d years', $years, $years);
- $relativeDate .= $months > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d month', '%d months', $months, $months) : '';
- $relativeDate .= $weeks > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d week', '%d weeks', $weeks, $weeks) : '';
- $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days) : '';
+ $f = $format_year;
} elseif (abs($months) > 0) {
- // months, weeks and days
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d month', '%d months', $months, $months);
- $relativeDate .= $weeks > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d week', '%d weeks', $weeks, $weeks) : '';
- $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days) : '';
+ $f = $format_month;
} elseif (abs($weeks) > 0) {
- // weeks and days
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d week', '%d weeks', $weeks, $weeks);
- $relativeDate .= $days > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days) : '';
+ $f = $format_week;
} elseif (abs($days) > 0) {
- // days and hours
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days);
- $relativeDate .= $hours > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d hour', '%d hours', $hours, $hours) : '';
+ $f = $format_day;
} elseif (abs($hours) > 0) {
- // hours and minutes
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d hour', '%d hours', $hours, $hours);
- $relativeDate .= $minutes > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d minute', '%d minutes', $minutes, $minutes) : '';
+ $f = $format_hour;
} elseif (abs($minutes) > 0) {
- // minutes only
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d minute', '%d minutes', $minutes, $minutes);
+ $f = $format_minute;
} else {
- // seconds only
- $relativeDate .= ($relativeDate ? ', ' : '') . __dn('cake', '%d second', '%d seconds', $seconds, $seconds);
+ $f = $format_second;
}
+ $f = str_replace(array('year', 'month', 'week', 'day', 'hour', 'minute', 'second'), array(1, 2, 3, 4, 5, 6, 7), $f);
+
+ $relativeDate .= $f >= 1 && $years > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d year', '%d years', $years, $years) : '';
+ $relativeDate .= $f >= 2 && $months > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d month', '%d months', $months, $months) : '';
+ $relativeDate .= $f >= 3 && $weeks > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d week', '%d weeks', $weeks, $weeks) : '';
+ $relativeDate .= $f >= 4 && $days > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days) : '';
+ $relativeDate .= $f >= 5 && $hours > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d hour', '%d hours', $hours, $hours) : '';
+ $relativeDate .= $f >= 6 && $minutes > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d minute', '%d minutes', $minutes, $minutes) : '';
+ $relativeDate .= $f >= 7 && $seconds > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d second', '%d seconds', $seconds, $seconds) : '';
+
if (!$backwards) {
$relativeDate = __d('cake', '%s ago', $relativeDate);
}
}
+
+ // If within the last or next 7 days
+ if (self::wasWithinLast('7 days', $dateTime, $timezone) || self::isWithinNext('7 days', $dateTime, $timezone)) {

This comment has been minimized.

Show comment Hide comment
@sitedyno

sitedyno Jun 24, 2012

Contributor

Hardcoded :(

Unless I'm missing something its no longer possible to do 'starting in 5 hours, 23 minutes'. Relative time > assuming people can handle timezones :/

@sitedyno

sitedyno Jun 24, 2012

Contributor

Hardcoded :(

Unless I'm missing something its no longer possible to do 'starting in 5 hours, 23 minutes'. Relative time > assuming people can handle timezones :/

This comment has been minimized.

Show comment Hide comment
@markstory

markstory Jun 24, 2012

Owner

So what would you suggest instead? I don't really understand the issue I guess.

@markstory

markstory Jun 24, 2012

Owner

So what would you suggest instead? I don't really understand the issue I guess.

This comment has been minimized.

Show comment Hide comment
@sitedyno

sitedyno Jun 24, 2012

Contributor

I was using timeAgoInWords to denote a count down until something starts "Next: Meeting x starting in 5 hours, 12 minutes (Sun Jun 24th 2012 @ 8:30 AM EDT)". With that verbage no one needs to do timezone calculations. After this commit my example becomes "Next: Meeting x starting in Today, 08:30 (Sun Jun 24th 2012 @ 8:30 AM EDT)" In this case it very much matters that you can calculate what time that is in your timezone if it differs from EDT.

I would at least like to see an option added 'niceShort' => false to avoid using CakeTime::niceShort()

@sitedyno

sitedyno Jun 24, 2012

Contributor

I was using timeAgoInWords to denote a count down until something starts "Next: Meeting x starting in 5 hours, 12 minutes (Sun Jun 24th 2012 @ 8:30 AM EDT)". With that verbage no one needs to do timezone calculations. After this commit my example becomes "Next: Meeting x starting in Today, 08:30 (Sun Jun 24th 2012 @ 8:30 AM EDT)" In this case it very much matters that you can calculate what time that is in your timezone if it differs from EDT.

I would at least like to see an option added 'niceShort' => false to avoid using CakeTime::niceShort()

This comment has been minimized.

Show comment Hide comment
@AD7six

AD7six Jun 24, 2012

Member

A test demonstrating your example would be nice.

@AD7six

AD7six Jun 24, 2012

Member

A test demonstrating your example would be nice.

This comment has been minimized.

Show comment Hide comment

This comment has been minimized.

Show comment Hide comment
@markstory

markstory Jun 25, 2012

Owner

I reverted the changes in behaviour in [3f78216]. I missed this when reviewing the original changes sorry about that.

@markstory

markstory Jun 25, 2012

Owner

I reverted the changes in behaviour in [3f78216]. I missed this when reviewing the original changes sorry about that.

+ $relativeDate = self::niceShort($dateTime , $timezone);
+ }
+
+ // Apply HTML element
+ if ($element) {
+ $relativeDate = '<' . $element . ' title="' . $dateTime . '" class="' . $element . '-date">' . $relativeDate . '</' . $element . '>';
+ }
+
return $relativeDate;
}
@@ -772,6 +844,32 @@ public static function wasWithinLast($timeInterval, $dateString, $timezone = nul
return false;
}
+
+/**
+ * Returns true if specified datetime is within the interval specified, else false.
+ *
+ * @param mixed $timeInterval the numeric value with space then time type.
+ * Example of valid types: 6 hours, 2 days, 1 minute.
+ * @param mixed $dateString the datestring or unix timestamp to compare
+ * @param mixed $timezone Timezone string or DateTimeZone object
+ * @return boolean
+ * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
+ */
+ public static function isWithinNext($timeInterval, $dateString, $timezone = null) {
+ $tmp = str_replace(' ', '', $timeInterval);
+ if (is_numeric($tmp)) {
+ $timeInterval = $tmp . ' ' . __d('cake', 'days');
+ }
+
+ $date = self::fromString($dateString, $timezone);
+ $interval = self::fromString('+' . $timeInterval);
+
+ if ($date <= $interval && $date >= time()) {
+ return true;
+ }
+
+ return false;
+ }
/**
* Returns gmt as a UNIX timestamp.

0 comments on commit 0729aca

Please sign in to comment.