Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate the UTC offset given a TimeZone string in JavaScript

Using the standard JS library (ECMA5), without using momentjs or external libs, how do you calculate the UTC offset given a TimeZone string such as "Europe/Rome" or "America/Los_Angeles"?

UTC Offsets may depend on whether it is DST or not, so it would make sense if the solution required converting the local client date to the specified Timezone String. The goal is just to know the offset from UTC.

function getUtcOffset(timezone) {
  // return int value. 
  // > 0 if +GMT 
  // < 0 if -GMT.
}
like image 535
GM Lucid Avatar asked Mar 20 '16 10:03

GM Lucid


People also ask

How to get UTC offset in JavaScript?

JavaScript Date getTimezoneOffset() getTimezoneOffset() returns the difference between UTC time and local time. getTimezoneOffset() returns the difference in minutes. For example, if your time zone is GMT+2, -120 will be returned.

How to calculate timezone offset js?

The JavaScript getTimezoneOffset() method is used to find the timezone offset. It returns the timezone difference in minutes, between the UTC and the current local time. If the returned value is positive, local timezone is behind the UTC and if it is negative, the local timezone if ahead of UTC.

How do I get timezone from UTC offset?

Knowing just the offset from UTC, you can't tell what timezone you are in, because of DST. You could consider looking at the time part of the time to try to guess whether DST was in effect then or not, but political considerations make that nearly impossible, as different jurisdictions change the definition of DST.


4 Answers

There is no function in ECMAScript (ECMA-262) that can perform the operation you requested. This is simply because standard ECMAScript does not know anything about time zones other than that of the local computer, and UTC.

However, in browsers that support the ECMAScript Internationalization API (ECMA-402), and fully support the IANA time zone database identifiers, you can hack together a function like this:

function getTimezoneOffset(d, tz) {
  const a = d.toLocaleString("ja", {timeZone: tz}).split(/[/\s:]/);
  a[1]--;
  const t1 = Date.UTC.apply(null, a);
  const t2 = new Date(d).setMilliseconds(0);
  return (t2 - t1) / 60 / 1000;
}

This will work in current versions of Chrome, and perhaps in a few other places. but it is certainly not guaranteed to work everywhere. In particular, it won't work in Internet Explorer browsers of any version.

Example usage (in Chrome):

getTimezoneOffset(new Date(2016, 0, 1), "America/New_York") // 300
getTimezoneOffset(new Date(2016, 6, 1), "America/New_York") // 240
getTimezoneOffset(new Date(2016, 0, 1), "Europe/Paris") // -60
getTimezoneOffset(new Date(2016, 6, 1), "Europe/Paris") // -120

A few things to note about this particular function:

  • Like I mentioned, It's not going to work everywhere. Eventually, as all browsers catch up to modern standards it will, but it won't currently.

  • The date you pass in will indeed affect the result. This is due daylight saving time and other time zone anomalies. You can pass the current date just with new Date(), but the result will change based on when you call the function. See "time zone != offset" in the timezone tag wiki.

  • The results of this function are the same as Date.getTimezoneOffset - in terms of minutes, with positive values being West of UTC. If you are working with ISO8601 offsets, you'll need to convert to hours and invert the sign.

  • The function relies on the time zone formatting functionality of the toLocaleString function. I picked the 'ja' culture, because the date parts were already in the correct order for the array. This is a hack indeed. Ideally there would be an API that would let you access time zone information without binding it to a locale when formatting. Unfortunately, the designers of this particular API have made the mistake of associating time zone with locale. This is a mistake that's been made in a few other APIs from various languages, and unfortunately was carried into JavaScript here.

    Restated plainly: The only time zone functionality in ECMA-402 is to apply a time zone when formatting a string, which is a design flaw, IMHO.

  • There's a bug in my example usage section above, that exemplifies part of why this API is a mistake. Specifically, there's no way to specify a time zone when you create a Date object. The Jan 1st and July 1st dates I pass in are created in the local time zone, not in the time zone specified. Therefore, the output may not be exactly what you expect near a transition. This could be hacked even more to work around this problem, but I will leave that as an exercise to you.

Again - Though this answer satisfies the criteria asked for, as there are no external libraries involved, I strongly recommend against using this in any production code. If you're planning on doing anything important with this, I'd use one of the libraries I listed here.

like image 199
Matt Johnson-Pint Avatar answered Oct 11 '22 15:10

Matt Johnson-Pint


Did you check moment-timezone?

moment.tz("America/Los_Angeles").utcOffset();
like image 17
ymranx Avatar answered Oct 11 '22 14:10

ymranx


Youve got to:

  • get the current datetime (will give you the datetime in the current timezone of the current device)
  • get the current timezone offset
  • convert the datetime to the new/required timezone
  • get the difference between the current datetime and the converted datetime
  • add said difference to the current timezone offset

Eg returning timezone in hours:

function getTimezoneOffset(tz, hereDate) {
    hereDate = new Date(hereDate || Date.now());
    hereDate.setMilliseconds(0); // for nice rounding
    
    const
    hereOffsetHrs = hereDate.getTimezoneOffset() / 60 * -1,
    thereLocaleStr = hereDate.toLocaleString('en-US', {timeZone: tz}),
    thereDate = new Date(thereLocaleStr),
    diffHrs = (thereDate.getTime() - hereDate.getTime()) / 1000 / 60 / 60,
    thereOffsetHrs = hereOffsetHrs + diffHrs;

    console.log(tz, thereDate, 'UTC'+(thereOffsetHrs < 0 ? '' : '+')+thereOffsetHrs);
    return thereOffsetHrs;
}


getTimezoneOffset('America/New_York', new Date(2016, 0, 1));
getTimezoneOffset('America/New_York', new Date(2016, 6, 1));
getTimezoneOffset('Europe/Paris', new Date(2016, 0, 1));
getTimezoneOffset('Europe/Paris', new Date(2016, 6, 1));
getTimezoneOffset('Australia/Sydney', new Date(2016, 0, 1));
getTimezoneOffset('Australia/Sydney', new Date(2016, 6, 1));
getTimezoneOffset('Australia/Sydney');
getTimezoneOffset('Australia/Adelaide');

Which outputs like

America/New_York 2015-12-30T22:00:00.000Z UTC-5
America/New_York 2016-06-30T01:00:00.000Z UTC-4
Europe/Paris 2015-12-31T04:00:00.000Z UTC+1
Europe/Paris 2016-06-30T07:00:00.000Z UTC+2
Australia/Sydney 2015-12-31T14:00:00.000Z UTC+11
Australia/Sydney 2016-06-30T15:00:00.000Z UTC+10
Australia/Sydney 2019-08-14T03:04:21.000Z UTC+10
Australia/Adelaide 2019-08-14T02:34:21.000Z UTC+9.5
like image 5
ekerner Avatar answered Oct 11 '22 14:10

ekerner


The chosen answer doesn't really answer the question. What the author want is a function that can input a timezone name and then return an offset.

After some investigations and digging the source code of icu4c, turns out the follow snippet can do what you need:

const getUtcOffset = (timeZone) => {
  const timeZoneName = Intl.DateTimeFormat("ia", {
    timeZoneName: "short",
    timeZone,
  })
    .formatToParts()
    .find((i) => i.type === "timeZoneName").value;
  const offset = timeZoneName.slice(3);
  if (!offset) return 0;

  const matchData = offset.match(/([+-])(\d+)(?::(\d+))?/);
  if (!matchData) throw `cannot parse timezone name: ${timeZoneName}`;

  const [, sign, hour, minute] = matchData;
  let result = parseInt(hour) * 60;
  if (sign === "-") result *= -1;
  if (minute) result + parseInt(minute);

  return result;
};

console.log(getUtcOffset("US/Eastern"));
console.log(getUtcOffset("Atlantic/Reykjavik"));
console.log(getUtcOffset("Asia/Tokyo"));

Note that the locale ia used here is Interlingua. The reason is that according to the source code of icu4c, the timezone name differs per locale. Even you use the same locale, the format of the timezone name can still vary based on different timezone.

With Interlingua (ia), the format is always the same pattern like GMT+NN:NN which can be easily parsed.

It's a little tircy but it works well in my own products.

Hope it helps you as well :)

like image 4
Weihang Jian Avatar answered Oct 11 '22 15:10

Weihang Jian