Mike West — Web Application Developer

Via Simon Willison’s Blogmarks, I came across an interesting idea regarding the display of timestamps on web pages, and how the nasty annoyance of time-zones can be dealt with in a fairly elegant manner. In a nutshell, “Showing Perfect Time” describes a method of displaying timestamps to visitors in the visitor’s own time zone, using JavaScript to convert a seconds-since-the-GMT-epoch stamp into something pretty and localized.

I love the idea, but the implementation doesn’t work for me. Importantly, it relies on <noscript> to display times for users without JavaScript turned on and document.write to dump out times for those who do. I’m not a fan of this method, so I stood on the author’s shoulders (who, by the way, was standing on shoulders already), and wrote my own version.

PerfectTime.js is a self-contained JavaScript class that runs onload, and unobtrusively replaces the contents of a specially constructed span with a properly formatted timestamp. The code on the webpage might look something like

<span class='PerfectTime' gmt_time='1111396060'>3/21/2005 1:03 CST</span>

which I find to be more semantically meaningful, and accessible. This article details the process, but if you’d like to skip ahead and play with the code yourself, the PerfectTime JavaScript class is available for download.


Writing this class begins in much the same way as the other classes I’ve written about on this site. We ought to map out what, exactly, we’re trying to achieve in order to give ourselves a clear goal to code towards.

The final goal is to take a GMT timestamp that’s sitting in our database, and display it to the user in her own time-zone. We know that her browser can make this translation for us if she’s using JavaScript, so we just have to figure out a nice method of making that translation possible while at the same time maintaining the semantic meaning of our page’s markup, and providing an alternative to those users visiting without JavaScript enabled. To me, this semantic HTML code looks like:

<span class='PerfectTime' gmt_time='60'>Jan 1, 1970 00:01 GMT</span>

A span surrounds the time in some baseline acceptable format (e.g. whatever you’re currently writing out), and has a custom attribute gmt_time that contains the seconds-since-epoch integer associated with that timestamp.

Without JavaScript, nothing more happens, and your users see the timestamp in some specific time-zone. With JavaScript enabled, we can take this a step further, and dynamically replace the contents of this span with a timestamp keyed off the user’s local timezone.

The steps, therefore, are straightforward:

  1. Find all the spans on the page of class PerfectTime.
  2. Extract the GMT stamp from each span.
  3. Translate each GMT stamp to a local timestamp.
  4. Replace the content of each span with the new timestamp.

So, let’s start coding.

We pick a name (“PerfectTime” sounds catchy) to distinguish this class from others we might use in the future, and create the package as per usual aliasing this to avoid scoping issues, as per usual.

function PerfectTime() {
    var self = this;

    ...

    self.instantiate = function () {
       var spans = document.getElementsByTagName('span');
       for (i=0, numSpans=spans.length; i < numSpans; i++) {
           if (spans[i].className.match(/PerfectTime/)) {
               self.processSpan(spans[i]);
           }
       }
    }

    ...

    handleEvent(window, 'load', self.instantiate);
}

We’re looking for all the spans on the page with a className of “PerfectTime”, and calling a method called (astoundingly enough) processSpan on each one. That’s where we’ll make the magic happen. As it turns out, that magic is quite straightforward:

    self.processSpan = function (theSpan) {
        var GMT = parseInt(theSpan.getAttribute('gmt_time')) * 1000;
        var newDate = new Date(GMT);
        theSpan.innerHTML = self.strftime(newDate);
    }

So, what’s going on in these three lines?

The first line grabs the gmt_time attribute off the span, and turns it into an integer using parseInt (because the getAttribute method always returns a string). We also have to multiply this number by 1000 to account for the fact that JavaScript’s Date object expects microseconds as opposed to seconds.

The second line creates our Date object, and populates it with our GMT stamp.

The third line calls a method called strftime to translate the GMT timestamp into a localized timestamp, and sticks that information into our span’s innerHTML. We’ve taken care, therefore, of steps #1, #2, and #4.
strftime() handles the heavy lifting in #3.

strftime() is based upon whytheluckystiff’s reworking of Johan Sundström’s’s clever formatTime method. I’ve simply moved the code around so that it fits into our self-contained environment. The function takes a Date object as it’s only argument, and returns the properly formatted time string just as it would have been returned from any other strftime() implementation (say, PHP’s strftime, which happens to have nicely available documentation). The formatting string is set when the class is instantiated with the following code:

self.defaultFormat = '%d %b %Y at %H:%M';
self.format        = (arguments[0])?arguments[0]:self.defaultFormat;

We use the arguments array of the constructor to determine if a formatting string was passed in. If one exists, we use it. If not, we use the default format string.

And that’s it. Simple, eh?

I’ve set up an example proof-of-concept PHP script that writes out various Unix timestamps (in fact, the timestamps of each and every blog post on mikewest.org) that will hopefully make the process clear.

The demo is PerfectTimeDemo.php. The PHP source code for the example is PerfectTimeDemo.phps. The JavaScript class is PerfectTime.js.

Mike West is a web application developer living in Munich, Germany. Professionally programming for the web since 2000, he's available for contract work now a web developer at Yahoo! Germany. Read Mike's bio, or drop him an e-mail.

Recent Articles


Comments

Great stuff, but I cringe at adding a non-standard attribute (gmt_time) when it isn’t necessary (probably the way you cringed at the NOSCRIPT element).

Tantek Çelik wrote about using the abbr element to store both computer and human-readable times. http://tantek.com/log/2005/01.html

This is widely used in the hCalendar microformat.http://microformats.org/wiki/hcalendar

I don’t see why a variant of your code couldn’t do use the same syntax, and help out those of us who use hCalendar…

Posted By: Daniel Morrison 14. February 2006, 02:08

Isn’t it possible to just have the javascript parse whats inbetween the tags (including the specified timezone) and use that as a reference point to change the time from (rather than adding a non-standard attribute)…

Posted By: Matthew Elder 14. February 2006, 11:17

Excellent point, Daniel. I completely forgot about hCalendar… I’ve updated the script to support the hCalendar format, and talked a little about the changes over here.

Posted By: Mike West 14. February 2006, 11:58

For some reason, it’s not possible to post comments to your next post, id 21.

Posted By: Johan Sundström 14. February 2006, 12:41

Yup, my fault. That’s been fixed.

Posted By: Mike West 14. February 2006, 13:49

I’m going to be using something a little bit like this on my site. Instead of using a “gmt_time” attribute, I just put the GMT time in the element I want to adjust.


  <span class="gmt_time">Tue Feb 14 08:58:31 GMT 2006</span>

Then I go through them with this (requires the prototype library):


  var now = new Date();
  document.getElementsByClassName('gmt_time').each(function(e) {
    d = new Date(Date.parse(e.innerHTML) - (now.getTimezoneOffset()*1000));
    e.innerHTML = d.strftime("%month %day, %year at %hours:%minutes %meridian");
  });

The strftime function is my own. This gives me “February 14, 2006 at 12:50 AM”, which is okay, but it is mysteriously 8 minutes off. I feel like I’m missing something really silly, but I don’t know what.

Posted By: Michael Daines 14. February 2006, 21:39

i think you meant “miliseconds” not “microseconds.” microseconds is 10 to the minus 6 seconds whereas miliseconds is 10 to the minus 3 seconds.

Posted By: zzztimbo 30. August 2006, 03:14

Great stuff!! Only thing is, what if I wanted to use this to call different formats from each span? Like this…

<span class=”gmt_time” format_type=”Full”>Tue Feb 14 08:58:31 GMT 2006</span>

<span class=”gmt_time” format_type=”timeonly”>08:58 AM</span>

In other words, I want to be able to control the format used in

var timeThing = new PerfectTime(‘%A %B %d, %Y %H:%M%p’);

and make it changeable at the tag level. Any ideas?

Posted By: doug 3. August 2007, 18:24

Comments Closed

Comments are closed after 6 weeks to avoid the icky comment spam we all loathe. Sorry if you missed the opportunity, but you can always drop me an e-mail!

flickr Photostream

Yahoo!'s Munich Office
atom:title
atom:title
atom:title