From b5d0baf98bd8eb7db11df78ebd9b675c072857b0 Mon Sep 17 00:00:00 2001 From: nate Date: Thu, 28 Feb 2008 05:06:37 +0000 Subject: [PATCH] Fixing TimeHelper::timeAgoInWords()/relativeTime(), fixes #4241 git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@6485 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/libs/view/helpers/time.php | 111 +++++++++--- .../cases/libs/view/helpers/time.test.php | 160 ++++++++++++++++-- 2 files changed, 227 insertions(+), 44 deletions(-) diff --git a/cake/libs/view/helpers/time.php b/cake/libs/view/helpers/time.php index 438d602ef..0a750b5f6 100644 --- a/cake/libs/view/helpers/time.php +++ b/cake/libs/view/helpers/time.php @@ -260,18 +260,15 @@ class TimeHelper extends AppHelper { * * @param string $date_string Datetime string or Unix timestamp * @param array $options Default format if timestamp is used in $date_string - * @param string $backwards False if $date_string is in the past, true if in the future * @return string Relative time string. */ - function timeAgoInWords($dateTime, $options = array(), $backwards = null) { + function timeAgoInWords($dateTime, $options = array()) { $in_seconds = $this->fromString($dateTime); - - if ($backwards === null && $in_seconds > time()) { - $backwards = true; - } + $backwards = ($in_seconds > time()); $format = 'j/n/y'; - $end = '+1 month';//when to show format + $end = '+1 month'; + $now = time(); if (is_array($options)) { if (isset($options['format'])) { @@ -287,29 +284,92 @@ class TimeHelper extends AppHelper { } if ($backwards) { - $start = floor(abs($in_seconds - time())); + $future_time = $in_seconds; + $past_time = $now; } else { - $start = floor(abs(time() - $in_seconds)); + $future_time = $now; + $past_time = $in_seconds; } + $diff = $future_time - $past_time; - $months = floor($start / 2638523.0769231); - $diff = $start - $months * 2638523.0769231; - $weeks = floor($diff / 604800); - $diff -= $weeks * 604800; - $days = floor($diff / 86400); - $diff -= $days * 86400; - $hours = floor($diff / 3600); - $diff -= $hours * 3600; - $minutes = floor($diff / 60); - $diff -= $minutes * 60; - $seconds = $diff; + // If more than a week, then take into account the length of months + if($diff >= 604800) { + $current = array(); + $date = array(); + + list($future['H'], $future['i'], $future['s'], $future['d'], $future['m'], $future['Y']) = explode('/', date('H/i/s/d/m/Y', $future_time)); + list($past['H'], $past['i'], $past['s'], $past['d'], $past['m'], $past['Y']) = explode('/', date('H/i/s/d/m/Y', $past_time)); + $years = $months = $weeks = $days = $hours = $minutes = $seconds = 0; + if($future['Y'] == $past['Y'] && $future['m'] == $past['m']) { + $months = 0; + $years = 0; + } else { + if($future['Y'] == $past['Y']) { + $months = $future['m'] - $past['m']; + } else { + $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 { + $days_in_past_month = date('t', $past_time); + $days_in_future_month = date('t', mktime(0, 0, 0, $future['m'] - 1, 1, $future['Y'])); + $days = ($days_in_future_month - $past['d']) + $future['d']; + + if($future['m'] != $past['m']) { + $months --; + } + } + if($months == 0 && $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; + } $relative_date = ''; + $diff = $future_time - $past_time; - if ($start > abs(time() - $this->fromString($end))) { + if ($diff > abs($now - $this->fromString($end))) { $relative_date = 'on ' . date($format, $in_seconds); } else { - if (abs($months) > 0) { + if ($years > 0) { + // years and months and days + $relative_date .= ($relative_date ? ', ' : '') . $years . ' year' . ($years > 1 ? 's' : ''); + $relative_date .= $months > 0 ? ($relative_date ? ', ' : '') . $months . ' month' . ($months > 1 ? 's' : '') : ''; + $relative_date .= $weeks > 0 ? ($relative_date ? ', ' : '') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; + $relative_date .= $days > 0 ? ($relative_date ? ', ' : '') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($months) > 0) { // months, weeks and days $relative_date .= ($relative_date ? ', ' : '') . $months . ' month' . ($months > 1 ? 's' : ''); $relative_date .= $weeks > 0 ? ($relative_date ? ', ' : '') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; @@ -341,7 +401,7 @@ class TimeHelper extends AppHelper { return $this->output($relative_date); } /** - * Alias for timeAgoInWords, but can also calculate dates in the future + * Alias for timeAgoInWords * * @param mixed $dateTime Datetime string (strtotime-compatible) or Unix timestamp * @param mixed $options Default format string, if timestamp is used in $dateTime, or an array of options to be passed @@ -349,9 +409,8 @@ class TimeHelper extends AppHelper { * @return string Relative time string. * @see TimeHelper::timeAgoInWords */ - function relativeTime($dateTime, $format = 'j/n/y') { - $date = $this->fromString($dateTime); - return $this->timeAgoInWords($dateTime, $format, (strtotime("now") <= $date)); + function relativeTime($dateTime, $options = array()) { + return $this->timeAgoInWords($dateTime, $options); } /** * Returns true if specified datetime was within the interval specified, else false. diff --git a/cake/tests/cases/libs/view/helpers/time.test.php b/cake/tests/cases/libs/view/helpers/time.test.php index f953797d4..77e905e01 100644 --- a/cake/tests/cases/libs/view/helpers/time.test.php +++ b/cake/tests/cases/libs/view/helpers/time.test.php @@ -59,6 +59,142 @@ class TimeTest extends UnitTestCase { } function testTimeAgoInWords() { + $result = $this->Time->timeAgoInWords(strtotime('4 months, 2 weeks, 3 days'), array('end' => '8 years'), true); + $this->assertEqual($result, '4 months, 2 weeks, 3 days'); + + $result = $this->Time->timeAgoInWords(strtotime('4 months, 2 weeks, 2 days'), array('end' => '8 years'), true); + $this->assertEqual($result, '4 months, 2 weeks, 2 days'); + + $result = $this->Time->timeAgoInWords(strtotime('4 months, 2 weeks, 1 day'), array('end' => '8 years'), true); + $this->assertEqual($result, '4 months, 2 weeks, 1 day'); + + $result = $this->Time->timeAgoInWords(strtotime('3 months, 2 weeks, 1 day'), array('end' => '8 years'), true); + $this->assertEqual($result, '3 months, 2 weeks, 1 day'); + + $result = $this->Time->timeAgoInWords(strtotime('3 months, 2 weeks'), array('end' => '8 years'), true); + $this->assertEqual($result, '3 months, 2 weeks'); + + $result = $this->Time->timeAgoInWords(strtotime('3 months, 1 week, 6 days'), array('end' => '8 years'), true); + $this->assertEqual($result, '3 months, 1 week, 6 days'); + + $result = $this->Time->timeAgoInWords(strtotime('2 months, 2 weeks, 1 day'), array('end' => '8 years'), true); + $this->assertEqual($result, '2 months, 2 weeks, 1 day'); + + $result = $this->Time->timeAgoInWords(strtotime('2 months, 2 weeks'), array('end' => '8 years'), true); + $this->assertEqual($result, '2 months, 2 weeks'); + + $result = $this->Time->timeAgoInWords(strtotime('2 months, 1 week, 6 days'), array('end' => '8 years'), true); + $this->assertEqual($result, '2 months, 1 week, 6 days'); + + $result = $this->Time->timeAgoInWords(strtotime('1 month, 1 week, 6 days'), array('end' => '8 years'), true); + $this->assertEqual($result, '1 month, 1 week, 6 days'); + + for($i = 0; $i < 200; $i ++) { + $years = rand(0, 3); + $months = rand(0, 11); + $weeks = rand(0, 3); + $days = rand(0, 6); + $hours = 0; + $minutes = 0; + $seconds = 0; + $relative_date = ''; + + if($years > 0) { + // years and months and days + $relative_date .= ($relative_date ? ', -' : '-') . $years . ' year' . ($years > 1 ? 's' : ''); + $relative_date .= $months > 0 ? ($relative_date ? ', -' : '-') . $months . ' month' . ($months > 1 ? 's' : '') : ''; + $relative_date .= $weeks > 0 ? ($relative_date ? ', -' : '-') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; + $relative_date .= $days > 0 ? ($relative_date ? ', -' : '-') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($months) > 0) { + // months, weeks and days + $relative_date .= ($relative_date ? ', -' : '-') . $months . ' month' . ($months > 1 ? 's' : ''); + $relative_date .= $weeks > 0 ? ($relative_date ? ', -' : '-') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; + $relative_date .= $days > 0 ? ($relative_date ? ', -' : '-') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($weeks) > 0) { + // weeks and days + $relative_date .= ($relative_date ? ', -' : '-') . $weeks . ' week' . ($weeks > 1 ? 's' : ''); + $relative_date .= $days > 0 ? ($relative_date ? ', -' : '-') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($days) > 0) { + // days and hours + $relative_date .= ($relative_date ? ', -' : '-') . $days . ' day' . ($days > 1 ? 's' : ''); + $relative_date .= $hours > 0 ? ($relative_date ? ', -' : '-') . $hours . ' hour' . ($hours > 1 ? 's' : '') : ''; + } elseif (abs($hours) > 0) { + // hours and minutes + $relative_date .= ($relative_date ? ', -' : '-') . $hours . ' hour' . ($hours > 1 ? 's' : ''); + $relative_date .= $minutes > 0 ? ($relative_date ? ', -' : '-') . $minutes . ' minute' . ($minutes > 1 ? 's' : '') : ''; + } elseif (abs($minutes) > 0) { + // minutes only + $relative_date .= ($relative_date ? ', -' : '-') . $minutes . ' minute' . ($minutes > 1 ? 's' : ''); + } else { + // seconds only + $relative_date .= ($relative_date ? ', -' : '-') . $seconds . ' second' . ($seconds != 1 ? 's' : ''); + } + + if(date('j/n/y', strtotime($relative_date)) != '1/1/70') { + $result = $this->Time->timeAgoInWords(strtotime($relative_date), array('end' => '8 years'), true); + if($relative_date == '0 seconds') { + $relative_date = '0 seconds ago'; + } + $relative_date = str_replace('-', '', $relative_date) . ' ago'; + $this->assertEqual($result, $relative_date); + } + } + + for($i = 0; $i < 200; $i ++) { + $years = rand(0, 3); + $months = rand(0, 11); + $weeks = rand(0, 3); + $days = rand(0, 6); + $hours = 0; + $minutes = 0; + $seconds = 0; + + $relative_date = ''; + + if($years > 0) { + // years and months and days + $relative_date .= ($relative_date ? ', ' : '') . $years . ' year' . ($years > 1 ? 's' : ''); + $relative_date .= $months > 0 ? ($relative_date ? ', ' : '') . $months . ' month' . ($months > 1 ? 's' : '') : ''; + $relative_date .= $weeks > 0 ? ($relative_date ? ', ' : '') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; + $relative_date .= $days > 0 ? ($relative_date ? ', ' : '') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($months) > 0) { + // months, weeks and days + $relative_date .= ($relative_date ? ', ' : '') . $months . ' month' . ($months > 1 ? 's' : ''); + $relative_date .= $weeks > 0 ? ($relative_date ? ', ' : '') . $weeks . ' week' . ($weeks > 1 ? 's' : '') : ''; + $relative_date .= $days > 0 ? ($relative_date ? ', ' : '') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($weeks) > 0) { + // weeks and days + $relative_date .= ($relative_date ? ', ' : '') . $weeks . ' week' . ($weeks > 1 ? 's' : ''); + $relative_date .= $days > 0 ? ($relative_date ? ', ' : '') . $days . ' day' . ($days > 1 ? 's' : '') : ''; + } elseif (abs($days) > 0) { + // days and hours + $relative_date .= ($relative_date ? ', ' : '') . $days . ' day' . ($days > 1 ? 's' : ''); + $relative_date .= $hours > 0 ? ($relative_date ? ', ' : '') . $hours . ' hour' . ($hours > 1 ? 's' : '') : ''; + } elseif (abs($hours) > 0) { + // hours and minutes + $relative_date .= ($relative_date ? ', ' : '') . $hours . ' hour' . ($hours > 1 ? 's' : ''); + $relative_date .= $minutes > 0 ? ($relative_date ? ', ' : '') . $minutes . ' minute' . ($minutes > 1 ? 's' : '') : ''; + } elseif (abs($minutes) > 0) { + // minutes only + $relative_date .= ($relative_date ? ', ' : '') . $minutes . ' minute' . ($minutes > 1 ? 's' : ''); + } else { + // seconds only + $relative_date .= ($relative_date ? ', ' : '') . $seconds . ' second' . ($seconds != 1 ? 's' : ''); + } + + if(date('j/n/y', strtotime($relative_date)) != '1/1/70') { + $result = $this->Time->timeAgoInWords(strtotime($relative_date), array('end' => '8 years'), true); + if($relative_date == '0 seconds') { + $relative_date = '0 seconds ago'; + } + $relative_date = str_replace('-', '', $relative_date) . ''; + $this->assertEqual($result, $relative_date); + } + } + + $result = $this->Time->timeAgoInWords(strtotime('-2 years, -5 months, -2 days'), array('end' => '3 years'), true); + $this->assertEqual($result, '2 years, 5 months, 2 days ago'); + $result = $this->Time->timeAgoInWords('2007-9-25'); $this->assertEqual($result, 'on 25/9/07'); @@ -83,26 +219,14 @@ class TimeTest extends UnitTestCase { $result = $this->Time->timeAgoInWords(strtotime('2 months, 12 days'), array('end' => '3 month')); $this->assertPattern('/2 months, 1 week/', $result); - if (date('n') == '2') { - $result = $this->Time->timeAgoInWords(strtotime('3 months, 5 days'), array('end' => '4 month')); - $this->assertEqual($result, '3 months, 3 days'); + $result = $this->Time->timeAgoInWords(strtotime('3 months, 5 days'), array('end' => '4 month')); + $this->assertEqual($result, '3 months, 5 days'); - $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); - $this->assertEqual($result, '2 months, 2 days ago'); + $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); + $this->assertEqual($result, '2 months, 2 days ago'); - $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); - $this->assertEqual($result, '2 months, 2 days ago'); - } else { - // These tests fail in the month of February - $result = $this->Time->timeAgoInWords(strtotime('3 months, 5 days'), array('end' => '4 month')); - $this->assertEqual($result, '3 months, 4 days'); - - $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); - $this->assertEqual($result, '2 months, 1 day ago'); - - $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); - $this->assertEqual($result, '2 months, 1 day ago'); - } + $result = $this->Time->timeAgoInWords(strtotime('-2 months, -2 days'), array('end' => '3 month')); + $this->assertEqual($result, '2 months, 2 days ago'); $result = $this->Time->timeAgoInWords(strtotime('2 months, 2 days'), array('end' => '3 month')); $this->assertPattern('/2 months/', $result);