Showing Date Span – PHP and JavaScript Version

It is common to see date/time span in feeds and on most social networking sites. If you don’t get it, it is the line that says “An hour ago”, or “Yesterday”, or “21 minutes ago” sort of for human friendly time span. There is a Kohana version for this but I applied some modifications and also ported to JavaScript.

Overview

In one of my personal projects, I needed to display a time span for a certain feed / post so that it will be easy for the visitor to know what date / time it was posted. Using Kohana’s Date::fuzzy_span() seems to be too simple. I wanted to use the Friendfeed style and add a bit of my own.

For the PHP version, the date span is shown as the page is displayed. However, as the page is cached or because the user may not refresh the page for a long time, a JavaScript solution is needed. For every given post, I put a timestamp somewhere on a span so that JavaScript will know when that certain post is posted. Something like this:

<a class="fuzzy_span" id="ts_1281067818" href="some_url">40 seconds ago</a>

Significant span are:

  1. Seconds, ex: "45 seconds ago"
  2. Minutes with allowance of up to 59 seconds for the first minute
  3. Hours with allowance of up to 59 minutes for the first hour
  4. Yesterday with allowance of 23 hours
  5. Few days ago but not more than a week – shows the day only like "Last Friday"
  6. A week ago with 5 hours allowance to display "a week ago"
  7. Month and date when not more than 3 months, ex: "Jul 26"
  8. Complete date when too long ago, ex: "Feb 14, 2009

PHP version

Since I’m using the Kohaha PHP framework, I used some of it’s constants. It can be re-written for another framework as you like provided that you supply the missing constant’s value. This is written for a Kohana module so it is a class.

<?php defined('SYSPATH') or die('No direct script access.');

/**
 * @uses Kohana_Date
 */
class Dc_Date
{
	public static function fuzzy_span($timestamp)
	{
		// Determine the difference in seconds
		$offset = abs(time() - $timestamp);
		$span = '';
		
		if ($offset < Kohana_Date::MINUTE)
		{
			$span = "$offset seconds ago";
		}
		elseif ($offset <= (Kohana_Date::MINUTE + 59))
		{
			$span = "a minute ago";
		}
		elseif ($offset < Kohana_Date::HOUR)
		{
			$span = floor($offset / Kohana_Date::MINUTE) . ' minutes ago';
		}
		elseif ($offset <= (Kohana_Date::HOUR * 2) - 1)
		{
			$span = "an hour ago";
		}
		elseif ($offset < Kohana_Date::DAY)
		{
			$span = floor($offset / Kohana_Date::HOUR) . ' hours ago';
		}
		elseif ($offset <= (Kohana_Date::DAY * 2) - 1)
		{
			$span = "Yesterday";
		}
		elseif ($offset < Kohana_Date::WEEK)
		{
			$span = "last " . date('l', $timestamp);
		}
		elseif ($offset <= (Kohana_Date::WEEK + (Kohana_Date::HOUR * 5)))
		{
			$span = "a week ago";
		}
		elseif ($offset < (Kohana_Date::MONTH * 3))
		{
			$span = date('M j', $timestamp);
		}
		else
		{
			$span = date('M j, Y', $timestamp);
		}
		return $span;
	}
}
&#91;/sourcecode&#93;

To use this in your views, simply: 

&#91;sourcecode language='php'&#93;
$ts = strtotime($feed_content&#91;'date'&#93;);	
$span = Dc_Date::fuzzy_span($ts);

echo "Article posted: $span";
&#91;/sourcecode&#93;

<h2>JavaScript version</h2>

The JavaScript versions is a bit tricky because PHP and JavaScript dates is not really that compatible. Our PHP version is dependent on the UNIX timestamp. However, JavaScript timestamp is in milliseconds. A trick was to convert that milliseconds into seconds by dividing by 1000. Let's try:


function DcDate(){}

DcDate.prototype = {
	// date constants probably
	YEAR: 	31556926,
	MONTH: 	2629744,
	WEEK: 	604800,
	DAY: 	86400,
	HOUR:	3600,
	MINUTE:	60,

	// days in a week, long
	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

	// months in a year, short
	months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],

	/** 
	 * Returns a human readable format time span
	 * for a given date in the past against a current date/time
	 *
	 * @param int timestamp		unix timestamp in the past
	 * @param Date currentDate	Date object containing current date
	 * @return string
	 */
	fuzzySpan: function(timestamp, currentDate)
	{
		if (typeof current == "undefined")
		{
			// get the current time stamp
			currentDate = new Date();
		}

		// get the offset in seconds and other related objects
		// need for pre calculations of time span
		var offset = (Math.round(currentDate.getTime() / 1000) - timestamp);
		var subjectDate = new Date(timestamp * 1000);
		var span = '';
		
		// determine the span in human readable format
		if (offset < this.MINUTE)
		{
			span = offset + ' seconds ago';
		}
		else if (offset <= (this.MINUTE + 59))
		{
			span = 'a minute ago';
		}
		else if (offset < this.HOUR)
		{
			span = Math.floor(offset / this.MINUTE) + ' minutes ago';
		}
		else if (offset <= (this.HOUR * 2) - 1)
		{
			span = 'an hour ago';
		}
		else if (offset < this.DAY)
		{
			span = Math.floor(offset / this.HOUR) + ' hours ago';
		}
		else if (offset <= (this.DAY * 2) - 1)
		{
			span = 'Yesterday';
		}
		else if (offset < this.WEEK)
		{
			span = 'last ' + this.days&#91;subjectDate.getDay()&#93;;
		}
		else if (offset <= (this.WEEK + (this.HOUR * 5)))
		{
			span = 'a week ago';
		}
		else if (offset < (this.MONTH * 3))
		{
			span = this.months&#91;subjectDate.getMonth()&#93; + ' ' + subjectDate.getDate();
		}
		else
		{
			span = this.months&#91;subjectDate.getMonth()&#93; + ' ' + subjectDate.getDate() + ', ' + subjectDate.getFullYear();
		}
		return span;
	}
}
&#91;/sourcecode&#93;

For performance reason, we added a parameter currentDate on our fuzzySpan method since JavaScript is pretty heavy. We will use this class to modify the html values for the anchor element which will contain the date/time span. As we pointed earlier, the container is an anchor element having the class <strong>fuzzy_span</strong>. Then, we put timestamp to the elements id like this: 

<strong>ts_1281067818</strong> - some prefix, separated by underscore and then our timestamp.

To get the timestamp, we must first get the element's ID, break them into parts using the underscore separator, then get the second part as the timestamp. Here is the usage:


function updateDateSpan()
{
	var currentDate = new Date();
	var dcDate = new DcDate();
	$(".fuzzy_span").each(function(){

		// find the timestamp from the id
		var id = this.id + '';
		var chunks = id.split('_');
		if (chunks.length <= 1)
		{
			return false;
		}

		var timestamp = parseInt(chunks&#91;1&#93;);
		if (isNaN(timestamp))
		{
			return false;
		}

		// update the date span now
		var span = dcDate.fuzzySpan(timestamp, currentDate);
		if (span.length > 0)
		{
			$(this).html(span);
		}
	});
}

Then we will apply the span update during page load (since our page is cached statically on a file) and in every minute interval. Here is the code:

// delay update a bit
setTimeout('updateDateSpan()', 150);
// schedule span update every 1 minute
setInterval('updateDateSpan()', 60000);

Applied on my site: http://ff2fb.lysender.co.cc/

This entry was posted in JavaScript, php and tagged , , , , , , . Bookmark the permalink.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *