3
\$\begingroup\$

I am working on a PHP/MySQL application that uses a different time zone from the server it runs on, and neither of the timezones are UTC. I'm converting displayed times to be in the user's timezone, and am wondering whether my approach is sensible. I would love any feedback you might have.

Firstly, the application's current time is written to the output HTML page as a meta value:

<meta name="date" content="<?php echo date('Y-m-d H:i:s') ?>" />

And, every instance of a datetime string is wrapped thus:

<span class="datetime-convert"><?php echo $data->datetime_column ?></span>

Then, after the page is loaded, the following JQuery gets the application time, the user time, figures out the difference between these, and adds that difference to the datetime data:

$("span.datetime-convert").each(function(){
    var serverTime = parseDatetime($("meta[name='date']").attr("content"));
    var userTime = new Date();
    var userOffset = new Date(userTime.getTime() - serverTime.getTime());
    var timeToConvert = parseDatetime($(this).text());
    var convertedTime = new Date(timeToConvert.getTime() + userOffset.getTime());
    $(this).text(convertedTime.toLocaleString());
});
function parseDatetime(value) {
    var a = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(value);
    if (a) {
        return new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]);
    }
    return null;
}

What am I doing wrong?

\$\endgroup\$
1

1 Answer 1

3
\$\begingroup\$

I would think it would be easier and more reliable to use timezone offsets rather than exact server time vs. client time. You can get the client-side offset using new Date().getTimezoneOffset() which returns the offset in minutes from UTC. When the timezone is +2 then the offset is -120.

PHP

<meta name="timezoneoffset" content="<?php echo date('P') ?>" />

HTML

<meta name="timezoneoffset" content="+0200" />

JavaScript

var serverTimeZone= $("meta[name='timezoneoffset']").attr("content").substring(0,3);
// getTimezoneOffset returns (client timezone * -60). 
// With timezones the the formula would be (clientTz - serverTz) * 60 * 60000
var timezoneOffset = (new Date().getTimezoneOffset() + (serverTimeZone * 60)) * 60000;
$("span.datetime-convert").each(function(){
    var date = parseDatetime($(this).text());
    if(date) { // If date is not wellformed the script would crash without this guard
      var offsetDate = new Date(date.getTime() + timezoneOffset);
      $(this).text(offsetDate .toLocaleString());
    }
});
function parseDatetime(value) {
    var a = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(value);
    if (a) {
        return new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]);
    }
    return null;
}

Also there's no need to calculate the offset for each individual date. It can be actually a bit misleading when the offset is slighlty different for each date.

http://jsfiddle.net/xXVPC/1/

More functional:

var serverTimeZone= $("meta[name='timezoneoffset']").attr("content").substring(0,3);
// getTimezoneOffset returns (client timezone * -60). 
// With timezones the the formula would be (clientTz - serverTz) * 60 * 60000
var timezoneOffset = (new Date().getTimezoneOffset() + (serverTimeZone * 60)) * 60000;
$("span.datetime-convert").each(function(){
    var e = $(this);
    parseDatetime(e.text(), new function(date) {
      var offsetDate = new Date(date.getTime() + timezoneOffset);
      e.text(offsetDate.toLocaleString());
    });
});
function parseDatetime(value, callback) {
    var a = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(value);
    if (a) {
        callback(new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ All very good points. Thank you very much! I've incorporated them into my project. \$\endgroup\$
    – Sam Wilson
    Jul 19, 2011 at 6:45
  • \$\begingroup\$ I ended up changing the timezoneoffset meta value to be in seconds, to allow for offsets that are not multiples of 1 hour, and avoid the string manipulation. Also, what's your thinking behind adding the callback to parseDatetime? (See my update for my current solution.) Thanks for helping! \$\endgroup\$
    – Sam Wilson
    Jul 20, 2011 at 2:51
  • \$\begingroup\$ The idea is to avoid duplication of the condition if(a) and to get rid of one assignment operation. The first reason is the important one as you may or may not remember to check for nulls and undefined as you go along and use parseDatetime again in some other context of the program or if you happen to reuse the function is some other context altogether. \$\endgroup\$ Jul 20, 2011 at 3:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.