The corporate future is built on a digital
foundation. At Celtic Productions,
we construct this new world.
Michael Magan
CEO
Celtic Productions :: CompanyCeltic Productions :: ServicesCeltic Productions :: ArticlesCeltic Productions :: SystemCeltic Productions :: ContactCeltic Productions :: Initiate
  X-Y Ago
  ALL ARTICLES

Title: X-Y Ago
Posted: Sun, 8th April 2007
Category: PHP


I couldn't quite think of an intelligible title for this article
so I've decided to run with the PHP function name. The function
takes a Unix timestamp as a parameter and returns a nicely
formatted textual description of how long ago that was, e.g. 2
weeks ago, hence the unimaginative title, x y ago where 'x' is
the number, 'y' is the unit and 'ago' is, eh, ago. You see this
date difference functionality all over the web. It's particularly
popular in forums where you often see headers of the form 'Posted
4 minutes ago by duke'.

The function is strongly related to VBScript's DateDiff, the
difference being that DateDiff returns the number of intervals
between 2 dates whereas xyago automatically assumes
the second date's value as now. You can pass in a different value
for $dateto but the result wouldn't really make a
whole lot of sense given that 'ago' has quite a specific meaning
in the English language. I've included the ability to pass in the
second date simply if you want to modify this function to operate
more like DateDiff. The second difference between this function
and DateDiff is that with DateDiff the interval is specified
whereas with xyago the interval is determined.

The general logic is as follows: If $datefrom is not
specified or is equal to '0', just return "A long time ago" as
it's probably an error rather than someone having posted a
message at the exact second of the epoch. Just playing the
statistics here with that line but you can actually return the
correct value, i.e. 37 years ago or whatever by uncommenting the
line.

Firstly, we determine the difference between the 2 dates in terms
of the number of seconds. We then send that value down an
if-else ladder looking for the most appropriate
interval. This is to determine the 'y' of the function return,
i.e. not how many of the units, but what are the actual units. If
the difference between the 2 timestamps is less than 60 seconds,
seconds seems an approriate interval, similarly, if the
difference is between 60 and 60*60 (an hour), minutes is an
approriate interval. It continues along like that until it gets
to between 7 and 30 days. Less than 7, we set the interval to be
'days', greater than or equal to 30, we set the interval to be
'months', in between is 'weeks'. 30 was chosen reasonably
arbitrarily given that there aren't, of course, 30 days in every
month, it just makes it possible to return '4 weeks ago'.

In this sense, the function will return an 'incorrect' value if
the difference between the 2 timestamps is in this region. It's
not exact, but if you wanted to print out the exact time
that something happened you would probably just use
date("r",$mytimestamp). The nature of this function
is that it obfuscates to some degree the exact time that
something happened.

Ok, here's the function:



function xyago($datefrom,$dateto=-1)
{
// Defaults and assume if 0 is passed in that
// its an error rather than the epoch

if($datefrom==0) { return "A long time ago"; }
if($dateto==-1) { $dateto = time(); }

// Calculate the difference in seconds betweeen
// the two timestamps

$difference = $dateto - $datefrom;

// If difference is less than 60 seconds,
// seconds is a good interval of choice

if($difference < 60)
{
$interval = "s";
}

// If difference is between 60 seconds and
// 60 minutes, minutes is a good interval
elseif($difference >= 60 && $difference<60*60)
{
$interval = "n";
}

// If difference is between 1 hour and 24 hours
// hours is a good interval
elseif($difference >= 60*60 && $difference<60*60*24)
{
$interval = "h";
}

// If difference is between 1 day and 7 days
// days is a good interval
elseif($difference >= 60*60*24 && $difference<60*60*24*7)
{
$interval = "d";
}

// If difference is between 1 week and 30 days
// weeks is a good interval
elseif($difference >= 60*60*24*7 && $difference <
60*60*24*30)
{
$interval = "ww";
}

// If difference is between 30 days and 365 days
// months is a good interval, again, the same thing
// applies, if the 29th February happens to exist
// between your 2 dates, the function will return
// the 'incorrect' value for a day
elseif($difference >= 60*60*24*30 && $difference <
60*60*24*365)
{
$interval = "m";
}

// If difference is greater than or equal to 365
// days, return year. This will be incorrect if
// for example, you call the function on the 28th April
// 2008 passing in 29th April 2007. It will return
// 1 year ago when in actual fact (yawn!) not quite
// a year has gone by
elseif($difference >= 60*60*24*365)
{
$interval = "y";
}

// Based on the interval, determine the
// number of units between the two dates
// From this point on, you would be hard
// pushed telling the difference between
// this function and DateDiff. If the $datediff
// returned is 1, be sure to return the singular
// of the unit, e.g. 'day' rather 'days'

switch($interval)
{
case "m":
$months_difference = floor($difference / 60 / 60 / 24 /
29);
while (mktime(date("H", $datefrom), date("i", $datefrom),
date("s", $datefrom), date("n", $datefrom)+($months_difference),
date("j", $dateto), date("Y", $datefrom)) < $dateto)
{
$months_difference++;
}
$datediff = $months_difference;

// We need this in here because it is possible
// to have an 'm' interval and a months
// difference of 12 because we are using 29 days
// in a month

if($datediff==12)
{
$datediff--;
}

$res = ($datediff==1) ? "$datediff month ago" : "$datediff
months ago";
break;

case "y":
$datediff = floor($difference / 60 / 60 / 24 / 365);
$res = ($datediff==1) ? "$datediff year ago" : "$datediff
years ago";
break;

case "d":
$datediff = floor($difference / 60 / 60 / 24);
$res = ($datediff==1) ? "$datediff day ago" : "$datediff
days ago";
break;

case "ww":
$datediff = floor($difference / 60 / 60 / 24 / 7);
$res = ($datediff==1) ? "$datediff week ago" : "$datediff
weeks ago";
break;

case "h":
$datediff = floor($difference / 60 / 60);
$res = ($datediff==1) ? "$datediff hour ago" : "$datediff
hours ago";
break;

case "n":
$datediff = floor($difference / 60);
$res = ($datediff==1) ? "$datediff minute ago" :
"$datediff minutes ago";
break;

case "s":
$datediff = $difference;
$res = ($datediff==1) ? "$datediff second ago" :
"$datediff seconds ago";
break;
}
return $res;
}



That's it, the function can of course be modified to return
difference phrasing or difference results based on different
intervals. The scope of this article is simply to provide a
framework from where you can determine how many of some
particular unit of time have passed. To utilise this function,
you could call the following:



$now = time();

// Prints '5 seconds ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now)-5,date("m",$now),
date("d",$now),date("Y",$now)))."\r\n";

// Prints '5 minutes ago'
print xyago(mktime(
date("H",$now),date("i",$now)-5,
date("s",$now),date("m",$now),
date("d",$now),date("Y",$now)))."\r\n";

// Prints '5 hours ago'
print xyago(mktime(
date("H",$now)-5,date("i",$now),
date("s",$now),date("m",$now),
date("d",$now),date("Y",$now)))."\r\n";

// Prints '5 days ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-5,date("Y",$now)))."\r\n";

// Prints '2 weeks ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-14,date("Y",$now)))."\r\n";

// Prints '4 weeks ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-30,date("Y",$now)))."\r\n";

// Prints '1 month ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-31,date("Y",$now)))."\r\n";

// Prints '11 months ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-364,date("Y",$now)))."\r\n";

// Prints '1 year ago'
print xyago(mktime(
date("H",$now),date("i",$now),
date("s",$now),date("m",$now),
date("d",$now)-365,date("Y",$now)))."\r\n";



Due to the messy nature of months, this function can and will
return incorrect values if leap years are involved, if you
require a more accurate representation of a timestamp, use a
function call like the following:
date("r",$mytimestamp). If you need
xyago to return really accurate values, the
following points might prove useful:

1) Ensure that all timestamps are in UTC (Universal Time
Coordinated) or failing that, at least ensure all timestamps are
of the same timezone.

2) Don't rely on there being 86400 seconds in a calendar day,
particularly in any non-UTC timezone. It's technically incorrect
to assume this and if you need to add a day to a Unix timestamp,
use $i = strtotime("+1 day", $i) instead of $i
+= 86400
.

3) It goes without saying that if you can't be sure there are
86400 seconds in a calendar day, don't assume there are 31536000
seconds in a calendar year &c. Calendar time is surprisingly
complex with irregular length months, leap years, leap seconds,
ephemeris time &c. so just because Google tells you there are
31,556,926seconds in a year, don't necessarily
believe them!

4) Even absorption spectroscopy atomic clocks can't measure time
exactly without synchronisation, so don't be surprised if your
PHP code misses a beat here and there!

That's it, hope this function proves useful.

As with all articles on Celtic Productions, this article is
protected by international copyright laws. It may be linked to
(we are of course most grateful of links to our articles),
however, it may never be reproduced without the prior express
permission of its owners, Celtic Productions.


del.icio.us   .   Digg It   .   BlinkList   .   Fark   .   Google   .   Ma.gnolia   .   Netvouz
NewsVine   .   RawSugar   .   Shadows   .   Stumble   .   Technorati








Telecoms:

(tel) +33 811 03 59 95
(fax) +33 811 03 43 43
(sms) +353 (0)87 125 40 33
Address:

Media Suite
Boulevard des Moulins
Monaco 98000
© 1997-2008 Celtic Productions Ltd.

Privacy Policy