Created
May 11, 2011 23:14
-
-
Save ternarybit/967604 to your computer and use it in GitHub Desktop.
Express difference between two timestamps in various English time units
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
time_delta() returns the difference between two times | |
expressed in many ways. | |
(mixed) $start: The initial time to calculate from | |
(mixed) $target: The time in the past or future to calculate the difference between | |
(int) $precision: | |
0: Generic - ex. "About 7 hours ago" | |
1: Approximate - ex. "7 hours, 15 minutes ago" | |
2: Exact - ex. "7 hours, 15 minutes and 6 seconds ago" | |
s: seconds | |
m: minutes | |
h: hours | |
d: days | |
w: weeks | |
mo: months | |
y: years | |
(int) $labels: | |
0: No labels - ex. "7 15 6 ago" | |
1: Short - ex. "7h 15m 6s ago" | |
2: Long - ex. "7 hours, 15 minutes and 6 seconds ago" | |
(bool) $suffix: Whether or not to add "ago" or "from now" | |
*/ | |
function time_delta($start='now', $target='', $precision=0, $labels=2, $suffix=true) | |
{ | |
date_default_timezone_set('America/Los_Angeles'); // PHP complains if this isn't set, for some reason | |
// Define all time units in terms of seconds | |
$units = array( | |
'y' => array('year', 31556926), // Source: Google calculator | |
'mo' => array('month', 2629744), // Source: Google calculator | |
'w' => array('week', 604800), | |
'd' => array('day', 86400), | |
'h' => array('hour', 3600), | |
'm' => array('minute', 60), | |
's' => array('second', 1) | |
); | |
// Some basic sanity checking | |
if (empty($target)) return "No target time specified.\n"; | |
if ($start < 0) return "Invalid start time.\n"; | |
if (!array_key_exists($precision, $units) AND $precision > 2) return "Improper value for precision.\n"; | |
if (!is_int($labels) OR !is_bool($suffix)) return "Improper values for labels and/or suffix.\n"; | |
if (!is_int($target) AND !strtotime($target)) return "Could not understand your target time.\n"; | |
if (!is_int($start) AND !strtotime($start)) return "Could not understand your start time.\n"; | |
// Set some sensible defaults | |
$fuzz_factor = 0.8; // How close to the next value will we call it "about" something? | |
if ($precision < 0 OR $precision > 2) $precision = 2; | |
if ($labels < 0 OR $labels > 2) $labels = 2; | |
if (!is_int($start)) $start = strtotime($start); | |
if (!is_int($target)) $target = strtotime($target); | |
// Are we past or future? | |
$ending = ($target > $start) ? " from now" : " ago"; | |
// Calculate time difference & initialize output string | |
$delta = abs($target - $start); | |
$out = ''; | |
// Calculate for single-unit precision | |
if (is_string($precision)) | |
{ | |
if ($delta < $units[$precision][1]) | |
{ | |
$out .= "Less than one {$units[$precision][0]}"; | |
return ($suffix === true) ? $out.$ending : $out; | |
} | |
else | |
{ | |
$out .= intval(($delta / $units[$precision][1])); | |
if ($labels == 0) return $out; | |
$out .= ($labels == 1) ? $precision : ' '.$units[$precision][0] | |
. (($out > 1 AND $labels >= 2) ? 's' : ''); | |
return ($suffix === true) ? $out.$ending : $out; | |
} | |
} | |
/* Calculate fuzzy precision | |
------------------------- | |
Fuzzy precision should output only one unit of precision | |
and use the modifier "about" if the remainder is > $fuzz_factor. | |
*/ | |
if ($precision == 0) | |
{ | |
foreach ($units as $unit => $type) | |
{ | |
if ($delta >= $type[1] * $fuzz_factor) | |
{ | |
$fuzzy = (fmod(($delta / $type[1]), 1) > $fuzz_factor) ? true : false; | |
if ($labels > 0 AND $labels >= 2) $out .= ($fuzzy === true) ? 'About ' : ''; | |
$diff = ($fuzzy === true) ? ceil($delta / $type[1]) : intval($delta / $type[1]); | |
if ($diff == 1 AND $fuzzy === true) $out .= ($unit == 'h') ? 'an ' : 'a '; | |
else $out .= $diff; | |
if ($labels == 0) return $diff; | |
$out .= ($labels > 1 OR $fuzzy === false) ? ' ' : ''; | |
$out .= ($labels == 1) ? $unit : $type[0]; | |
$out .= ($diff > 1 AND $labels > 1) ? 's' : ''; | |
return ($suffix === true) ? $out.$ending : $out; | |
} | |
} | |
} | |
/* Calculate approximate and exact precision | |
----------------------------------------- | |
Approximate precision outputs up to 2 units of measure, exact prints | |
as many as we have. | |
*/ | |
else | |
{ | |
$max = ($precision == 1) ? 2 : count($units); // Iterate twice if approximate precision | |
$i = 0; | |
foreach ($units as $unit => $type) | |
{ | |
if ($delta >= $type[1] AND $i < $max) | |
{ | |
$diff = intval($delta / $type[1]); | |
$out .= $diff.(($labels > 1) ? (' '.$type[0]) : (($labels == 0) ? ' ' : $unit)).(($diff > 1 AND $labels > 1) ? ('s') : ('')); | |
$delta -= intval($delta / $type[1]) * $type[1]; | |
$out .= ($i == 0 AND $precision == 1 AND $labels > 0 AND !empty($diff)) ? ' and ' : ''; | |
$next_index = array_search($unit,array_keys($units)) + 1; | |
$units_numeric = array_values($units); | |
if (array_key_exists($next_index, $units_numeric)) $next = $units_numeric[$next_index][1]; | |
$and = ($precision == 2 AND $labels > 0 AND ($delta % $next == 0) AND $unit != 's'); | |
$out .= $and ? ' and ' : (($labels > 0 AND $unit != 's' AND $precision != 1) ? ', ' : '' ); | |
$i++; | |
} | |
} | |
return ($suffix === true) ? $out.$ending : $out; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment