One from the archive: resurrecting time_since()

Since the disastrous demise of my once dependable hosting company, I have lost a lot of data and subsequently broken permalinks to various entries of mine around the internets. Using the wonders of the Internet archive and the wayback machine I managed to ferret out one of my older posts that got a lot of coverage back in June 2003.

Yep, I know that was a long time ago now, I was a 21 year old, fresh faced computer scientist and this was one of the first bits of PHP I wrote and then released (the very first was a ShoutBox application), my first languages being Visual Basic and Java. I am not republishing this as an indication of my current coding ability or for any other self publicising reason. I decided to resurrect this post exactly as it was back then, mainly so permalinks could be updated now I have a more stable hosting provider, but also to provide a bit of an update as to the function’s usage over the years.

Since its initial launch, the time_since() function has been translated into various different languages, both spoken (of which I can only find links to the German translation) and written programming languages - many of which are much improvised and improved implementations.

But what I am most pleased with, if you can excuse my temporary lack of British modesty, was its inclusion in various notable applications or plugins for applications.

So, without further ado, and with great respect to everyone who has altered, re-written and improved the function over the past 3 or 4 years … here is the post as originally presented.

The original post … “The time_since() function”

I have changed the function that displays the ‘time since’ each blog entry, managing not only to reduce it from 68 lines of code to 41, but also making it more maintainable in the process.

The basic requirements were that it should display the following:

  1. minutes up until 1 hour,
  2. hours and minutes up until 5 hours
  3. hours up until 1 day
  4. days and hours up until 1 week
  5. weeks and days up until 1 month
  6. months until 1 year
  7. years and months until 5 years
  8. years after that.

This was fine but the code was messy, with 7 if-statements used to calculate minutes, hours etc. separately.

Looking at the algorithm closer (and ignoring items 2 and 7) I decided that a better way to represent it would be through a series of time periods or ‘chunks’, where it displays the largest and the second largest chunks that will fit into it (eg. for 9 years 6 months 2 weeks and 1 day it would display ‘9 years, 6 months’).

Using this data structure as the core behaviour of the function, and representing it as an array of arrays, turned out to be a much neater way of doing things. The subarrays each have 2 elements in them, with the first element being the number of seconds in that time period, and the second being the singular name for that period. The first part of the function is as follows:


/* Works out the time since the entry post, takes a an
argument in unix time (seconds) */
function time_since($original) {
    // array of time period chunks
    $chunks = array(
        array(60 * 60 * 24 * 365 , 'year'),
        array(60 * 60 * 24 * 30 , 'month'),
        array(60 * 60 * 24 * 7, 'week'),
        array(60 * 60 * 24 , 'day'),
        array(60 * 60 , 'hour'),
        array(60 , 'minute'),
    );

After this we work out the number of seconds since the time passed to the function:


    $today = time(); /* Current unix time in seconds  */
    $since = $today - $original;

A ‘for‘ loop is used find the largest ‘chunk’ by cycling through the ‘$chunks‘ array and checking if the number of chunks that fit in to the time period is not zero. This is why the ‘$chunks‘ array is reversed with the largest chunk first, so that if doesn’t match that one it skips to the next.


    // $j saves performing the count function each time around the loop
    for ($i = 0, $j = count($chunks); $i < $j; $i++) {

        $seconds = $chunks[$i][0];
        $name = $chunks[$i][1];

        // finding the biggest chunk (if the chunk fits, break)
        if (($count = floor($since / $seconds)) != 0) {
            break;
        }
    }

By having a ‘$print‘ variable to output, it is possible to now use PHP’s string concatenation ‘.=‘ operator to add the smaller chunk to the beginning of the ‘$print‘ variable. The ‘floor()‘ function knocks off the remainder, so 2.5 becomes 2.


    $print = ($count == 1) ? '1 '.$name : "$count {$name}s";

By using a ternary operator here, this can cope with having 1 week and x weeks, by checking if the number of chunks is one or not.

The next item in the array, ‘$chunks[$i + 1]‘ is the next smaller time chunk, so we need to display how many of these occur in the time period once the number of seconds taken up in the first chunk have been accounted for.

The second chunk is only added if its count is greater than zero; if it is not then the ‘$print‘ variable only contains the number of the larger chunks that occur.


    if ($i + 1 < $j) {
        // now getting the second item
        $seconds2 = $chunks[$i + 1][0];
        $name2 = $chunks[$i + 1][1];

        // add second item if it's count greater than 0
        if (($count2 = floor(($since - ($seconds * $count)) / $seconds2)) != 0) {
            $print .= ($count2 == 1) ? ', 1 '.$name2 : ", $count2 {$name2}s";
        }
    }

Now we can return the ‘$print‘ variable and finish the function.


    return $print;
}

The full function can be found in this plain text file.

6 Responses

  1. Interesting, I just programmed something like this yesterday. My code looks almost the same…

    
    function timetostr($duration = 0) {
      // calculate days, hours, minutes, seconds
      $d = (int)($duration/(60*60*24));
      $h = (int)(($duration % (60*60*24))/(60*60));
      $m = (int)(($duration % (60*60))/60);
      $s = (int)($duration % 60);
    
      // generate output
      $r = \"\";
      $start = \"\";
      if ($d) {
        $r = $d.\" day\".($d == 1 ? \"\" : \"s\");
        $start = \", \";
      }
      if ($h) {
        $r .= $start.$h.\" hour\".($h == 1 ? \"\" : \"s\");
        $start = \", \";
      }
      if ($m) {
        $r .= $start.$m.\" minute\".($m == 1 ? \"\" : \"s\");
        $start = \", \";
      }
      $r .= $start.$s.\" second\".($s == 1 ? \"\" : \"s\");
    
      return $r;
    }
    

    If I want to have a shorter display, I then use print preg_replace(\"/ ([a-z])[a-z]*,?/\”, \”\\${1}\”, timetostr($somedate))

    Karsten - January 27th, 2007 at 3:54 pm
  2. What perfect timing on this post, Nat: I was looking for something like this for an application being developed at work. Cheers :-)

    davee
    The One Who Drove You To LugradioLive2006

    davee - February 5th, 2007 at 9:33 am
  3. [...] I found it incredibly difficult to find any form of time_since function in php so to save anyone else the trouble to hunting through hundereds of useless websites here is the function you may want to use, written by Natalie Downe (you dont want to know how long it took to hunt it down). [...]

    Photogabble » time_since - April 20th, 2007 at 1:15 am
  4. You wont believe how long it has taken me to find this code! Every one kept linking to a website that doesn’t excist anymore (I guess it was an old one of yours). I am glad I found it tho, helped me out a great deal, thanks.

    Simon - April 20th, 2007 at 1:28 am
  5. [...] On the Photogapple.co.uk blog today, there’s some handy code that can definitely be useful when working with dates in PHP - a function to find the difference between the current time and a timestamp you give it. I found it incredibly difficult to find any form of time_since function in php so to save anyone else the trouble to hunting through hundreds of useless websites here is the function you may want to use, written by Natalie Downe (you don’t want to know how long it took to hunt it down). [...]

    developercast.com » Photogapple.co.uk: time_since - April 21st, 2007 at 4:55 pm
  6. In EXCEL, how can you sum up hours and minutes when hours can exceed 24 and minutes can exceed 60.
    EX. 23:30
    22:45
    sum 46:15

    s. smith - August 2nd, 2007 at 4:02 pm

Leave a Reply