30

I need a Javascript function that given a timezone, returns the current UTC offset.

For example, theFuncIneed('US/Eastern') -> 240

Michael M.
  • 10,486
  • 9
  • 18
  • 34
user3124032
  • 409
  • 1
  • 4
  • 4
  • Create an object with the timezone names as properties and the offset as value. Note that there is no standard for timezone names, they change from time to time and there are duplicates (e.g. EST). – RobG Dec 20 '13 at 21:48
  • Check out moment.js, it has a `zone()` method. – Barmar Dec 20 '13 at 21:50
  • @RobG - `US/Eastern` is a valid [IANA time zone](http://iana.org/time-zones) identifier. You can find it near the bottom of the list [here](http://en.wikipedia.org/wiki/List_of_tz_database_time_zones). – Matt Johnson-Pint Dec 20 '13 at 22:37
  • 1
    @MattJohnson—I'm not sure what your point is. IANA provide timezone offsets for particular locations. IANA data isn't a standard or even authoritative and there is no pretence that it is (the header of the data file says "This data is by no means authoritative; if you think you know better, go ahead and edit the file"). – RobG Dec 21 '13 at 11:54
  • 1
    @RobG - Agreed. The IANA time zone database not a standard in the way that ISO 8601 is a standard. But it's a *de facto standard* by convention that it's used ubiquitously throughout multiple operating systems, programming languages, and libraries. So while there are no *guarantees*, it's still highly likely that `US/Eastern` will be recognized by all implementations. – Matt Johnson-Pint Dec 21 '13 at 17:45
  • 1
    @MattJohnson—I think this is way off track. My point was to caution that there is no standard for timezone designators. The IANA database includes a number of exceptions for the timezone observed in a place compared to its geographic location and for the start and end of daylight saving where it's contrary to "official" dates. – RobG Dec 21 '13 at 21:49

5 Answers5

12

It has become possible nowaday with Intl API:

The implementation of Intl is based on icu4c. If you dig the source code, you'll find that timezone name differs per locale, for example:

for (const locale of ["ja", "en", "fr"]) {
  const timeZoneName = Intl.DateTimeFormat(locale, {
    timeZoneName: "short",
    timeZone: "Asia/Tokyo",
  })
    .formatToParts()
    .find((i) => i.type === "timeZoneName").value;
  console.log(timeZoneName);
}

Fortunately, there is a locale, Interlingua (the langauge tag is ia), which uses the same pattern (ex. GMT+11:00) for timezone names.

The snippet below can meed your need:

const getOffset = (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(getOffset("US/Eastern")); // 240
console.log(getOffset("Atlantic/Reykjavik")); // 0
console.log(getOffset("Asia/Tokyo")); // -540

This way can be a little tricky but it works well in my production project. I hope it helps you too :)

Update

Many thanks to Bort for pointing out the typo. I have corrected the snippet.

kleinfreund
  • 6,546
  • 4
  • 30
  • 60
Weihang Jian
  • 7,826
  • 4
  • 44
  • 55
  • 1
    There's a typo at `if (minute) result + parseInt(minute);`. Should that be `+=`? – Bort Nov 03 '20 at 19:45
  • Also this does not take into account daylight saving time changes. – Bort Nov 03 '20 at 19:55
  • 1
    This does not work for the values in the example - returns 0 for "US/Eastern" and "Atlantic/Reykjavik" – Avraham Mar 04 '21 at 15:54
  • @Avraham This returns `0` for `getOffset("Atlantic/Reykjavik")` because that’s the current offset of that time zone to UTC. For `getOffset("US/Eastern")` it returns `240`, not `0`. – kleinfreund May 11 '21 at 11:53
  • 1
    @kleinfreund Strange, when I run the second code snippet, I get `0` for `console.log(getOffset("US/Eastern"));` – Avraham May 12 '21 at 17:18
  • @Avraham What does `Intl.DateTimeFormat("ia", { timeZoneName: "short", timeZone: "US/Eastern", }).formatToParts().find((i) => i.type === "timeZoneName").value` return for you? Perhaps something that’s *not* starting with `"GMT"`? And if so, are you running this in a browser or perhaps a Node.js environment? – kleinfreund May 12 '21 at 19:59
  • For me `timeZoneName` will be `MESZ` in Chrome and this leads to `Cannot parse timezone name`. Input: `Europe/Zurich`. In Firefox it works. – dude Jul 07 '21 at 09:12
  • 2
    Changing `timeZoneName: "short",` to `timeZoneName: "shortOffset",` seems to fix this snippet so it works in Chrome 100, Firefox 99, and Node 18. I haven't tested other browsers or node versions. Otherwise, specifying a timezone of "US/Eastern" returns a timeZoneName of "EDT" which does not contain a parseable GMT offset. – Ray Waldin Apr 20 '22 at 01:34
  • genius thanks, combined with ` Intl.supportedValuesOf('timeZone')` you can filter out list of zone given an offset – mikakun Dec 05 '22 at 01:47
  • 1
    There's a mistake in sign/minute rows. It should be `if (minute) result += parseInt(minute);` first and then `if (sign === "+") result *= -1;` Otherwise all the timezone offsets which have minutes are calculated incorrectly. For example, Asia/Calcutta offset is 5.5 meaning it should be -330 but your code gives -270. – Kasheftin May 25 '23 at 09:58
  • Also daylight time is not used in the provided function. – Kasheftin May 25 '23 at 10:20
  • The correct way is to send a date altogether with the timezone and substitute the value into `formatToParts`. – Kasheftin May 25 '23 at 10:46
  • Got 0, 0, -540 on console logs, also tried, America/New_York and got 0 and US/Central and got 0. I need this exact type of thing but this is not working for me in Chrome. – Greggory Wiley May 31 '23 at 16:57
10

In general, this is not possible.

  • US/Eastern is an identifier for a time zone. (It's actually an alias to America/New_York, which is the real identifier.)

  • 240 is a time zone offset. It's more commonly written as -04:00 (Invert the sign, divide by 60).

  • The US Eastern Time Zone is comprised of both Eastern Standard Time, which has the offset of -05:00 and Eastern Daylight Time, which has the offset of -04:00.

So it is not at all accurate to say US/Eastern = 240. Please read the timezone tag wiki, especially the section titled "Time Zone != Offset".

Now you did ask for the current offset, which is possible. If you supply a date+time reference, then you can resolve this.

  • For the local time zone of the computer where the javascript code is executing, this is built in with .getTimezoneOffset() from any instance of a Date object.

  • But if you want it for a specific time zone, then you will need to use one of the libraries I listed here.

Philipp Kyeck
  • 18,402
  • 15
  • 86
  • 123
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • ECMA-262 defines [timezone offset](http://ecma-international.org/ecma-262/5.1/#sec-15.9.5.26) as `UTC - localTime` expressed in minutes. So I think it's reasonable in a javascript forum to write `+240` where `-04:00` might otherwise be used. – RobG Dec 21 '13 at 12:02
  • 7
    `.getTimeZoneOffset()` should be `.getTimezoneOffset()` without the uppercase Z – Graham Walters Aug 21 '15 at 10:33
6

Following function can be used to return the UTC offset given a timezone:

const getTimezoneOffset = (timeZone, date = new Date()) => {
  const tz = date.toLocaleString("en", {timeZone, timeStyle: "long"}).split(" ").slice(-1)[0];
  const dateString = date.toString();
  const offset = Date.parse(`${dateString} UTC`) - Date.parse(`${dateString} ${tz}`);
  
  // return UTC offset in millis
  return offset;
}

It can be used like:

const offset = getTimezoneOffset("Europe/London");
console.log(offset);
// expected output => 3600000
ranjan_purbey
  • 421
  • 4
  • 11
  • 4
    This is slightly incorrect. The UTC offset should be positive for timezones that are behind it, and negative for timezones that are ahead of it. Therefore, the subtraction needs to be reversed: `Date.parse(\`${dateString} ${tz}\`) - Date.parse(\`${dateString} UTC\`);`. Otherwise, it won't align with the built-in `Date.prototype.getTimezoneOffset`. – fgblomqvist Sep 30 '20 at 17:38
  • 2
    Also, expected output for `"Europe/London"` should be `0` – Avraham Mar 04 '21 at 16:19
  • 2
    @Avraham the offset depends on date due to DST. That's why the function takes a date as the second argument – ranjan_purbey Mar 05 '21 at 05:17
  • 1
    timeStyle is invalid. It should be `timeZoneName` – dude Jul 07 '21 at 09:20
  • 1
    Results in `NaN` for me. – dude Jul 07 '21 at 09:37
  • @dude `timeStyle` is not [compatible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#browser_compatibility) with all browsers. `timeZoneName` is a good option, it's compatible with lower version of browser. – nnc1311 Feb 10 '22 at 08:16
  • @ranjan_purbey, apparently date.parse('date string') does not universally handle the format of date string you are passing it. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString Made a new solution based on your idea but reworked it to be based on a passed date (a future date can have a different offset then a current date) and not utilizing date.parse – Greggory Wiley May 31 '23 at 18:13
1

You can do this using moment.js

moment.tz('timezone name').utcOffset()

Although this involves using moment-timezone.js

Alexandru R
  • 8,560
  • 16
  • 64
  • 98
-1

The answer of @ranjan_purbey results in NaN for me and the answer of @Weihang Jian throws an exception in Chrome (but works in Firefox).

Therefore, based on all the answers I came up with the following function which is basically a combination of both answers working together successfully for me:

function getTimeZoneOffset(timeZone) {
    const date = new Date().toLocaleString('en', {timeZone, timeZoneName: 'short'}).split(' ');
    const timeZoneName = date[date.length - 1];
    const offset = timeZoneName.slice(3);
    if (!offset) {
      return 0;
    }
    const matchData = offset.match(/([+-])(\d+)(?::(\d+))?/);
    if (!matchData) {
      throw new Error(`Cannot parse timezone name: ${timeZoneName}`);
    }
    const [, sign, hour, minute] = matchData;
    let result = parseInt(hour, 10) * 60;
    if (sign === '+') {
      result *= -1;
    }
    if (minute) {
      result += parseInt(minute, 10);
    }
    return result;
}
dude
  • 5,678
  • 11
  • 54
  • 81