Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculating BST time from Date object?

I've reviewed a few questions on similar topics already, but none of them address calculating a destination timezone, taking its DST (daylight savings time) into account. I'm trying to write a simple widget that displays a live local time of a specific timezone to anyone visiting the page. The timezone of interest is BST (what is BST?), but if possible I'd love to see a generic implementation for any locale. Here's my attempt, using vanilla JavaScript:

function getBST() {
    var date = new Date(),
        utc = date.getTime() + date.getTimezoneOffset() * 60000,
        local = new Date(utc), // this is GMT, not BST
        day = local.getDate(),
        mon = local.getMonth() + 1,
        year = local.getFullYear(),
        hour = local.getHours(),
        minute = ('0' + local.getMinutes()).slice(-2),
        second = ('0' + local.getSeconds()).slice(-2),
        suffix = hour < 12 ? 'AM' : 'PM';

    hour = (hour - 24) % 12 + 12;

    return mon + '/' + day + '/' + year + ', ' + hour + ':' + minute + ':' + second + ' ' + suffix;
}

setInterval(function () {
  document.body.textContent = getBST();
}, 1000);

Based on @RobG's answer, I've written this code:

function resetDay(date) {
  date.setUTCMilliseconds(0);
  date.setUTCSeconds(0);
  date.setUTCMinutes(0);
  date.setUTCHours(1);

  return date;
}

function lastDay(date, day) {
  while (date.getUTCDay() !== day) {
    date.setUTCDate(date.getUTCDate() - 1);
  }

  return date;
}

function adjustDST(date, begin, end) {
  if (date >= begin && date < end) {
    date.setUTCHours(date.getUTCHours() + 1);
  }

  return date;
}

function updateBST() {
  var date = new Date();
  var begin = resetDay(new Date());

  begin.setUTCMonth(3, -1);
  begin = lastDay(begin, 0);

  var end = resetDay(new Date());

  end.setUTCMonth(10, -1);
  end = lastDay(end, 0);

  date = adjustDST(date, begin, end);

  var day = date.getUTCDate(),
    mon = date.getUTCMonth() + 1,
    year = date.getUTCFullYear(),
    hour = date.getUTCHours(),
    minute = ('0' + date.getUTCMinutes()).slice(-2),
    second = ('0' + date.getUTCSeconds()).slice(-2),
    suffix = hour < 12 ? 'AM' : 'PM';

  hour = (hour - 24) % 12 + 12;

  return mon + '/' + day + '/' + year + ', ' + hour + ':' + minute + ':' + second + ' ' + suffix;
}

setInterval(function () {
  document.body.textContent = updateBST();
}, 1000);

I tested the BST by pausing in the debugger and changing the month of the current date, and the output was an hour later so it seems to work properly. Thanks for all your help everyone!

like image 752
Patrick Roberts Avatar asked Sep 15 '25 22:09

Patrick Roberts


2 Answers

It's not possible in pure JS, just using the Date methods, but there's (of course) a lib for that: http://momentjs.com/timezone/

Example:

moment.tz("Europe/London").format(); // 2016-01-15T09:21:08-07:00
like image 89
Ilya Avatar answered Sep 17 '25 12:09

Ilya


It's not difficult to support one timezone provided you know when it transitions in and out of daylight saving.

A javascript Date consists of a time value in UTC and a timezone offset based on the host system settings. So all you need to do is apply the timezone offset that you want to the UTC time and presto, there's your time in any timezone.

There is no standardised system for abbreviating time zones, though there are some defacto standards (e.g. IATA timezone codes and IANA timezones). I guess by BST you mean British Summer Time, also known as British Daylight Time (BDT) and British Daylight Saving Time (BDST). It might also be Bangladesh Standard Time or Bougainville Standard Time which are also known as "BST".

There are various libraries (such as Moment Timezone) that use the IANA codes and can provide the time for any supported time zone for (more or less) any time.

BST starts at 01:00 UTC on the last Sunday in March and ends at 01:00 UTC on the last Sunday in October each year, so the algorithm is:

  1. Create a new date based on the system's local settings (e.g. new Date())
  2. Create dates for the start and end of BST based on UTC values (for this year, 2016-03-27T01:00:00Z and 2016-03-30T02:00:00Z)
  3. See if the current UTC time falls in that range
  4. If so, add 1 hour to the UTC time of the date created in #1
  5. Output a formatted string based on the date's UTC values

That's it, the only hard part is finding the appropriate Sunday dates. You don't need to consider the local timezone offset at all, since everything is based on UTC and Date objects are too.

Right now I don't have time to provide code, so have a go and I can get back to you in about 10 hrs.

Edit

So here's the code. The first two function are helpers, one gets the last Sunday in a month, the other formats an ISO 8601 string with offset. Most of the work is in those two functions. Hopefully the comments are sufficient, if more explanation is required, just ask.

I haven't included milliseconds in the string, feel free to add them if you want, add + '.' + ('00' + d.getUTCMilliseconds()).slice(-3) before the offset part of the formatted string.

Note that the function will need to be modified if the dates for starting or stopping daylight saving are changed, but that is infrequent. Historic dates of course will need a small database of when daylight saving starts and stops for particular years and periods.

/* Return a Date for the last Sunday in a month
** @param {number} year - full year number (e.g. 2015)
** @param {number} month - calendar month number (jan=1)
** @returns {Date} date for last Sunday in given month
*/
function getLastSunday(year, month) {
  // Create date for last day in month
  var d = new Date(year, month, 0);
  // Adjust to previous Sunday
  d.setDate(d.getDate() - d.getDay());
  return d;
}

/* Format a date string as ISO 8601 with supplied offset
** @param {Date} date - date to format
** @param {number} offset - offset in minutes (+east, -west), will be
**                          converted to +/-00:00
** @returns {string} formatted date and time
**
** Note that javascript Date offsets are opposite: -east, +west but 
** this function doesn't use the Date's offset.
*/
function formatDate(d, offset) {
  function z(n){return ('0'+n).slice(-2)}
  // Default offset to 0
  offset = offset || 0;
  // Generate offset string
  var offSign = offset < 0? '-' : '+';
  offset = Math.abs(offset);
  var offString = offSign + ('0'+(offset/60|0)).slice(-2) + ':' + ('0'+(offset%60)).slice(-2);
  // Generate date string
  return d.getUTCFullYear() + '-' + z(d.getUTCMonth()+1) + '-' + z(d.getUTCDate()) +
         'T' + z(d.getUTCHours()) + ':' + z(d.getUTCMinutes()) + ':' + z(d.getUTCSeconds()) +
         offString;
}

/* Return Date object for current time in London. Assumes
** daylight saving starts at 01:00 UTC on last Sunday in March
** and ends at 01:00 UTC on the last Sunday in October.
** @param {Date} d - date to test. Default to current
**                   system date and time
** @param {boolean, optional} obj - if true, return a Date object. Otherwise, return
**                        an ISO 8601 formatted string
*/
function getLondonTime(d, obj) {
  // Use provided date or default to current date and time
  d = d || new Date();

  // Get start and end dates for daylight saving for supplied date's year
  // Set UTC date values and time to 01:00
  var dstS = getLastSunday(d.getFullYear(), 3);
  var dstE = getLastSunday(d.getFullYear(), 10);
  dstS = new Date(Date.UTC(dstS.getFullYear(), dstS.getMonth(), dstS.getDate(),1));
  dstE = new Date(Date.UTC(dstE.getFullYear(), dstE.getMonth(), dstE.getDate(),1));
  // If date is between dstStart and dstEnd, add 1 hour to UTC time
  // and format using +60 offset
  if (d > dstS && d < dstE) {
    d.setUTCHours(d.getUTCHours() +1);
    return formatDate(d, 60);
  }
  // Otherwise, don't adjust and format with 00 offset
  return obj? d : formatDate(d);
}

document.write('Current London time: ' + getLondonTime(new Date()));
like image 24
RobG Avatar answered Sep 17 '25 12:09

RobG