Monday, November 22, 2010

Displaying timestamps in client's(viewer's) timezone with jQuery date format 1.0

While I was going through the Google App Engine tutorial to create a nice looking guestbook. I wanted each message to display its timestamp in the viewer's timezone. Each message was already timestamped in the datastore in Coordinate Universal Time (UTC). My tasks are to format them into desire format "MM/dd/yyyy hh:mm:ss a" and display the times in the viewer's timezone.

I searched javascript utilities and came across the jQuery dateFormat 1.0. However, I had to modify a few things. The original code is here and below:

jquery.dateFormat-1.0.js
  132 case "hh":
  133     retValue += (time.hour === 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12);
  134     pattern = "";
  135     break;
  136 case "mm":
  137     retValue += time.minute;
  138     pattern = "";
  139     break;
  140 case "ss":
  141     retValue += time.second;
  142     pattern = "";
  143     break;



There are two issues within these lines of code:
  1. Notice on line 133 the triple equals (===) is used to compared. Ideally sounds and looks great, however the value in "time.hour" sometimes are "00" as string. To fix it I use the double equals (==) comparison operator instead.
  2. time.second contains the second information as well as the timezone information. Therefore, if you specified "ss" as part of your format it will include the viewer's current timezone information and it will just clutter your screen because there is no point displaying the same information for each message. To fix it I further parsed the time.second and made a "tz" format for displaying timezones.
Below are the changes:

jquery.dateFormat-1.0.dc.js
  132 case "hh":
  133     //time.hour is "00" as string == is used instead of ===
  134     retValue += (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12);
  135     pattern = "";
  136     break;
  137 case "mm":
  138     retValue += time.minute;
  139     pattern = "";
  140     break;
  141 case "ss":
  142     //ensure only seconds are added to the return string
  143     retValue += time.second.substring(0, 2);
  144     pattern = "";
  145     break;
  146 case "tz"://parse out the timezone information
  147     retValue += time.second.substring(3, time.second.length);
  148     pattern = "";
  149     break;

You can see my full version of the jQuery date format here. Once I fixed the format I then found a post on stackoverflow.com about converting UTC to local user timezone. Thanks to CodeGrue's help.
    3 (function ($) {
    4     $.fn.localTimeFromUTC = function (format) {
    5         return this.each(function () {
    6 
    7             // get time offset from browser
    8             var currentDate = new Date();
    9             var offset = -(currentDate.getTimezoneOffset() / 60);
   10 
   11             // get provided date
   12             var tagText = $(this).html();
   13             var givenDate = new Date($.format.date(tagText, format));
   14 
   15             // apply offset
   16             var hours = givenDate.getHours();
   17             hours += offset;
   18             givenDate.setHours(hours);
   19 
   20             // format the date
   21             var localDateString = $.format.date(givenDate, format);
   22             $(this).html(localDateString);
   23         });
   24     };
   25 })(jQuery);


http://emplementation.appspot.com is the final product. I'm currently working on OpenID integration and will blog about when it is finished.

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello docchang, thanks a lot for your modification. Can you send it to me as pull request on github?

    https://github.com/phstc/jquery-dateFormat

    Regards,
    Pablo Cantero

    ReplyDelete
  3. Hello docchang I added a comment in your pull request

    https://github.com/phstc/jquery-dateFormat/pull/3

    Thanks again!

    Regards,
    Pablo Cantero

    ReplyDelete
  4. Hi there,

    First off thanks for your code, it works wonderful and I'm positive it helps out a lot of people.

    I noticed a small problem however;

    AM timestamps are displayed in hh format (example: 04:09:09 AM)
    PM timestamps are displayed in h format (example: 4:09:14 PM)

    the inconsistency of this can cause issues for some. It would be best to display both the same way. For example 4:09:14 PM and 4:09:14 AM ----- or 04:09:14 PM and 04:09:14 AM

    For myself since I needed a quick way to have "hh" format in both AM/PM cases I changed the following:

    Old code snippet:
    case "hh":
    /* time.hour is "00" as string == is used instead of === */
    retValue += (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12);

    New code snippet:
    case "hh":
    /* time.hour is "00" as string == is used instead of === */
    retValue += (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour);

    After that I removed "a" from localTimeFromUTC('yyyy-MM-dd hh:mm:ss a');
    (that's the part that displays AM/PM)

    It's not an ideal solution but it might help out some people who need to keep "hh" format and don't mind displaying 24-hour format without AM/PM

    Regards,
    Detlev Joshua Kemps

    ReplyDelete
  5. I posted a comment over at stackoverflow on this as well.

    When I was using this I was sending my times in RFC1123 format (e.g. ddd, dd MMM yyyy HH:mm:ss GMT). The script would create a new Date() from this string (all javascript dates are stored internally as UTC, so the 'stored' date and the 'visible' date are the same) but then when the script got to the line

    var hours = givenDate.getHours()

    hours would be set to the hours in Local time, not UTC, so the math would come out incorrectly.

    The documentation on what javascript's new Date(dateString) does is woefully inadequate. I might have to write a blog post on that!

    ReplyDelete