Time Ago: How to Display Publish Dates as Time Since Posted

    Osvaldas Valutis

    It’s common to present dates on the Web in a format such as Published on September 12th, 2015, or 09/12/2015, 09:41:23 and 2015-09-12.

    Each of these examples tells the full date and/or time of some kind of activity – be it a published article, or a reader comment, or perhaps an uploaded video.

    Date formats like this might seem perfectly reasonable. After all, they’re informative and human-readable! Well yes, but “human-readable” doesn’t necessary mean users will readily be able to understand how recently the activity has occurred. The Web is a fast-moving place, and giving your content a sense of freshness could be the key to engaging with your audience! So, let’s look at how we could improve on these common date formats.

    A Little Trick

    Once again, let’s say you have stumbled on a post that was actually published just a couple of minutes ago, but the subheading of the post states this:

    Published on September 12th, 2016

    … or this:

    Published on 2016-09-12, 09:41:23

    The problem with these messages is that they don’t communicate the feeling that your website has just been updated or that you do that very often. So, surely it be more inviting – and much clearer – to present the time this way:

    Published 2 minutes ago

    Seen this many times before? But how many of you have built this into your projects? Take Facebook for example: what would it look like if they presented the dates for the latest content as in my first example? Compare the left and right columns in the following image:

    timestamp formats compared

    The left column’s dates aren’t that attractive, are they? I’ll wager the right hand examples are more appealing to you. Knowing that the content is fresh is very important – especially on the social networks, where people are more likely to ignore content that’s old or not clearly timestamped.

    Printing Better Dates

    In order to present better dates, you’ll need some server-side scripting, and I’ll use PHP for this demo. I created a tiny function called time_ago(), as shown here:

        define( TIMEBEFORE_NOW,         'now' );
        define( TIMEBEFORE_MINUTE,      '{num} minute ago' );
        define( TIMEBEFORE_MINUTES,     '{num} minutes ago' );
        define( TIMEBEFORE_HOUR,        '{num} hour ago' );
        define( TIMEBEFORE_HOURS,       '{num} hours ago' );
        define( TIMEBEFORE_YESTERDAY,   'yesterday' );
        define( TIMEBEFORE_FORMAT,      '%e %b' );
        define( TIMEBEFORE_FORMAT_YEAR, '%e %b, %Y' );
        function time_ago( $time )
            $out    = ''; // what we will print out
            $now    = time(); // current time
            $diff   = $now - $time; // difference between the current and the provided dates
            if( $diff < 60 ) // it happened now
                return TIMEBEFORE_NOW;
            elseif( $diff < 3600 ) // it happened X minutes ago
                return str_replace( '{num}', ( $out = round( $diff / 60 ) ), $out == 1 ? TIMEBEFORE_MINUTE : TIMEBEFORE_MINUTES );
            elseif( $diff < 3600 * 24 ) // it happened X hours ago
                return str_replace( '{num}', ( $out = round( $diff / 3600 ) ), $out == 1 ? TIMEBEFORE_HOUR : TIMEBEFORE_HOURS );
            elseif( $diff < 3600 * 24 * 2 ) // it happened yesterday
                return TIMEBEFORE_YESTERDAY;
            else // falling back on a usual date format as it happened later than yesterday
                return strftime( date( 'Y', $time ) == date( 'Y' ) ? TIMEBEFORE_FORMAT : TIMEBEFORE_FORMAT_YEAR, $time );

    Let’s look at some details of this code.

    • The only argument you must provide is $time, and it’s a date in Unix Timestamp – such as time_ago( 1442082961 ).
    • Examples of what the function will return in respect of the $time passed:
      • now – if it happened less than 60 seconds ago (TIMEBEFORE_NOW)
      • 3 minutes ago – if less than 60 minutes ago (TIMEBEFORE_MINUTE(S))
      • 8 hours ago – if less than 24 hours ago (TIMEBEFORE_HOUR(S))
      • yesterday – if less than 48 hours ago (TIMEBEFORE_YESTERDAY)
      • 12 Sep – if more than 48 hours and happened this year (TIMEBEFORE_FORMAT)
      • 12 Sep, 2015 – if happened not this year (TIMEBEFORE_FORMAT_YEAR).
    • The PHP definitions are for separating the config-like data from the function code (it would be a good practice to put all of the define() occurrences into a config file and the function in helpers file).
    • {num} in definitions are replaced with actual numbers (minutes or hours).
    • I use strftime() instead of date() to avoid language/locale issues.

    So, for example, if you want to get this onto your WordPress site, you’d simply write this:

    <?=time_ago( get_the_time( 'U' ) )?>

    Or if it was some other hypothetical CMS:

    <?=time_ago( $post->date_created )?>

    Or the static way:

    <?=time_ago( 1447571705 )?>

    Accessibility & Usability

    There’s a specific HTML element that you should use for presenting dates: <time>. In our case, when using the time_ago function, the value of the time element is not always in a valid date format (like yesterday or 3 minutes ago). Therefore, you should also provide a fallback value by using [datetime] attribute:

    Published <time 
        datetime="<?=date( 'Y-m-d', $time )?>" 
        title="<?=strftime( date( 'Y', $time ) == 
            date( 'Y' ) ? TIMEBEFORE_FORMAT : TIMEBEFORE_FORMAT_YEAR, $time )?>">
        <?=time_ago( $time )?>

    This would result in better accessibility, for example:

    Published <time datetime="2015-09-12" title="September 12">3 minutes ago</time>

    Did you spot the [title] attribute? It’s a tiny usability improvement: putting the cursor over date text shows a message presented in the title attribute. That’s for the users who, for some reason, were looking for the “real” dates. Here’s a little CSS trick to beef up the feeling that there is something more:

        cursor: help;

    CSS help cursor

    JavaScript Enhancement

    There’s one more thing we can do! Have you noticed that Facebook also increments the dates in real time? Just watch at 3 mins for a minute and it will turn into 4 mins and so on. So, there are types of websites were this works out as a really nice enhancement. It wouldn’t be useful on an article post like this, but It’s perfect on a site like Readerrr:

    <time data-time="<?=$time?>" ...

    Finally, we need to convert the PHP code into a JavaScript equivalent. I’ve done this for you in vanilla JS (though a jQuery version is available as well). The script walks through each time[data-time] element once every minute (setTimeout( updateDates, 1000 * 60 )) and updates the values:

    // ...
    var elements    = document.querySelectorAll( 'time[data-time]' ),
        updateDates = function()
            Array.prototype.forEach.call( elements, function( entry )
                var out = '';
                // ...
                entry.textContent = out;
            setTimeout( updateDates, 1000 * 60 );
    setTimeout( updateDates, 1000 * 60 );
    // ...

    A JavaScript demo

    Online Demo and Code Download

    You can check out an online demo of the above code or download the full demo code.

    One more thing

    In the examples above, the full date is presented if the activity has occurred three or more days ago. But it’s quite easy to extend the script for presenting the time in the ways like 5 days ago, 2 weeks ago and 1 month ago, and so on:

    // ...
    define( TIMEBEFORE_DAYS,    '{num} days ago' );
    define( TIMEBEFORE_WEEK,    '{num} week ago' );
    define( TIMEBEFORE_WEEKS,   '{num} weeks ago' );
    define( TIMEBEFORE_MONTH,   '{num} month ago' );
    define( TIMEBEFORE_MONTHS,  '{num} months ago' );
    function time_before( $time )
        // ...
        elseif( $diff < 3600 * 24 * 7 )
            return str_replace( '{num}', round( $diff / ( 3600 * 24 ) ), TIMEBEFORE_DAYS );
        elseif( $diff < 3600 * 24 * 7 * 4 )
            return str_replace( '{num}', ( $out = round( $diff / ( 3600 * 24 * 7 ) ) ), $out == 1 ? TIMEBEFORE_WEEK : TIMEBEFORE_WEEKS );
        elseif( $diff < 3600 * 24 * 7 * 4 * 12 )
            return str_replace( '{num}', ( $out = round( $diff / ( 3600 * 24 * 7 * 4 ) ) ), $out == 1 ? TIMEBEFORE_MONTH : TIMEBEFORE_MONTHS );
            // ...

    Wrap Up

    User experience and satisfaction is in details. Sometimes a simple detail – such as a dynamic date format – is enough to make our websites a little bit better.

    So, what do you think of this solution? Would you consider using it on your next project? Do you have any questions about it? Please let me know in the comments.

    By the way, I hope somebody can tell Instagram people that 122w isn’t cool, and that 2.4yrs would be much easier to understand. That would be better, wouldn’t it?