How Do I calculate the difference of 2 time zones in JavaScript?
Asked Answered
G

4

17

For example, the difference in Eastern and Central is 1. My solution below feels hacky. Is there a easier / better way?

var diff = (parseInt(moment().tz("America/New_York").format("ZZ")) - parseInt(moment().tz("America/Chicago").format("ZZ"))) / 100;

My example is using the Momentjs library.

Gillis answered 25/3, 2015 at 20:0 Comment(4)
What about subtracting the offsets?Festa
so something like (moment().tz("America/New_York").zone() - moment().zone()) / 60 ? I guess that's a little better.Gillis
What exactly do you plan to do with it? Do you really need to use timezone by name?Lysol
In this case we do. The web server is in the Eastern time zone; we're in Central. I need the difference in the user's timezone and the server.Gillis
G
46

It's impossible to calculate the difference between two arbitrary time zones. You can only calculate a difference for a specific moment in time.

  • It's currently 4 hours difference between London and New York (writing this on Mar 25, 2015).
  • But it was 5 hours difference a few weeks ago, and it will be 5 a few weeks from now.
  • Each time zone switches offsets for daylight saving time at a different point in time.

This is true in the general case between two time zones. However some time zones either switch exactly at the same time, or don't switch at all.

  • There is always one hour between London and Paris, because they switch at the same moment in time.
  • There are always 3 hours between Arizona and Hawaii, because neither have DST.

Keep in mind that in the United States, each time zone that uses DST actually switches at a different moment in time. They all switch at 2:00 AM in their local time, but not at the same universal moment in time.

  • So between Chicago and New York, there is usually 1 hour apart
  • But for two brief periods each year they are either 2 hours apart, or have the same exact time.

See also "Time Zone != Offset" in the timezone tag wiki.

Now with regard to moment-timezone, you said in comments:

The web server is in the Eastern time zone; we're in Central. I need the difference in the user's timezone and the server.

The time zone of the web server is irrelevant. You should be able to host from anywhere in the world without affecting your application. If you can't, then you're doing it wrong.

You can get the current time difference between your time zone (US Central time) and the user's. You don't even need to know the user's exact time zone for this, if the code is running in the browser:

var now = moment();
var localOffset = now.utcOffset();
now.tz("America/Chicago"); // your time zone, not necessarily the server's
var centralOffset = now.utcOffset();
var diffInMinutes = localOffset - centralOffset;

If instead the code was running on the server (in a node.js app), then you would need to know the user's time zone. Just change the first line like this:

var now = moment.tz("America/New_York"); // their time zone

Updated answer:

This can be done without Moment, in environments that support the ECMAScript Internationalization API and have fully implemented IANA time zone support. This is most browsers these days.

function getTimeZoneOffset(date, timeZone) {

  // Abuse the Intl API to get a local ISO 8601 string for a given time zone.
  let iso = date.toLocaleString('en-CA', { timeZone, hour12: false }).replace(', ', 'T');
  
  // Include the milliseconds from the original timestamp
  iso += '.' + date.getMilliseconds().toString().padStart(3, '0');
  
  // Lie to the Date object constructor that it's a UTC time.
  const lie = new Date(iso + 'Z');

  // Return the difference in timestamps, as minutes
  // Positive values are West of GMT, opposite of ISO 8601
  // this matches the output of `Date.getTimeZoneOffset`
  return -(lie - date) / 60 / 1000;
}

Example usage:

getTimeZoneOffset(new Date(2020, 3, 13), 'America/New_York') //=> 240
getTimeZoneOffset(new Date(2020, 3, 13), 'Asia/Shanghai') //=> -480

If you want the difference between them, you can simply subtract the results.

Node.js

The above function works in Node.js where the full-icu internationalization support is installed (which is the default for Node 13 and newer). If you have an older version with either system-icu or small-icu, you can use this modified function. It will work in browsers and full-icu environments also, but is a bit larger. (I have tested this on Node 8.17.0 on Linux, and Node 12.13.1 on Windows.)

function getTimeZoneOffset(date, timeZone) {

  // Abuse the Intl API to get a local ISO 8601 string for a given time zone.
  const options = {timeZone, calendar: 'iso8601', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false};
  const dateTimeFormat = new Intl.DateTimeFormat(undefined, options);
  const parts = dateTimeFormat.formatToParts(date);
  const map = new Map(parts.map(x => [x.type, x.value]));
  const year = map.get('year');
  const month = map.get('month');
  const day = map.get('day');
  const hour = `${map.get('hour') % 24}`.padStart(2, "0"); // Sometimes hour value comes as 24
  const minute = map.get('minute');
  const second = map.get('second');
  const ms = date.getMilliseconds().toString().padStart(3, '0');
  const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}`;

  // Lie to the Date object constructor that it's a UTC time.
  const lie = new Date(iso + 'Z');

  // Return the difference in timestamps, as minutes
  // Positive values are West of GMT, opposite of ISO 8601
  // this matches the output of `Date.getTimeZoneOffset`
  return -(lie - date) / 60 / 1000;
}

Note that either way, we must go through Intl to have the time zone applied properly.

Gratulation answered 25/3, 2015 at 23:35 Comment(8)
Tried it with node.js and it's not working - this line doesn't work const lie = new Date(iso + 'Z') when it parses this date string 4/15/2020T08:43:57.710Z the result is NaN. But maybe my node is too old v8.3.0Continual
@AlexeyPetrushin - Thanks for pointing that out. The problem is that Node.js pre v13 comes with small-icu by default, which doesn't have locale support - so you get a system-locale format instead (en-CA y-m-d format isn't honored). I've expanded my answer to include an alternative way, using the formatToParts API. That should work for you without updating Node.Gratulation
@ErKKChopra - IE11 does support toLocaleString, but it doesn't support IANA time zones. If you require both time zone support and support for IE11 and other older browsers, you'll need to use an older library that brings its own time zone data instead of relying on Intl. For example, momentjs.com/timezoneGratulation
Nice work! One note: const hour = map.get('hour'); sometimes returns "24", which makes an invalid iso string. It needs a hour==="24" ? "00" : hour; check put in somewhere to fix it. This is with Node v12+ BTW. Also, it doesn't hurt to round the result. I got some annoying unit test fails because -0 !== 0.Protectorate
Interesting. Though, 24 means 0 of the following day, so there would be a bit more math to do somwhere. Probably easiest to adjust the lie. Alternatively, I wonder if there's an Intl setting to avoid such a result. Perhaps it would work better to pass some specific locale rather than undefined. (BTW, Temporal will solve this properly.)Gratulation
I did not quite understand. This function gets the offset at the time new Date(2020, 3, 13), 'America/New_York' or new Date(2020, 3, 13) + 240*60*1000, 'America/New_York'?Sontich
It looks to me like the last example is described either incorrectly or unintuitively. It returns the difference between the passed time zone and UTC, not between the passed time zone the user'sHill
I hit the same case as Trevedhek. So suggesting an edit to replace const hour = map.get('hour') with const hour = ${map.get('hour') % 24}.padStart(2, "0") @MattJohnson-PintTrotyl
M
1

The difference between two timezones can only be measured w.r.t particular time because of things like Daylight saving time (dst).

But, for a particular date, below code should work.

function getOffsetBetweenTimezonesForDate(date, timezone1, timezone2) {
  const timezone1Date = convertDateToAnotherTimeZone(date, timezone1);
  const timezone2Date = convertDateToAnotherTimeZone(date, timezone2);
  return timezone1Date.getTime() - timezone2Date.getTime();
}

function convertDateToAnotherTimeZone(date, timezone) {
  const dateString = date.toLocaleString('en-US', {
    timeZone: timezone
  });
  return new Date(dateString);
}

The offset/difference is in milli sec

Then all you have to do is:

const offset = getOffsetBetweenTimezonesForDate(date, 'America/New_York', 'America/Chicago');
Maus answered 25/4, 2020 at 11:50 Comment(1)
please look at https://mcmap.net/q/689376/-how-do-i-calculate-the-difference-of-2-time-zones-in-javascript - your answer does not provide any new information.Komsomolsk
K
0

This is the working Typescript code based on the answer of https://mcmap.net/q/689376/-how-do-i-calculate-the-difference-of-2-time-zones-in-javascript

export function getTimeZoneOffset(date: Date, timeZone: string) {
  // Abuse the Intl API to get a local ISO 8601 string for a given time zone.
  const isoStrRaw: string = date
    .toLocaleString("en-CA", { timeZone, hour12: false })
    .replace(", ", "T");

  const toks: string[] = isoStrRaw.split("T");
  if (toks.length < 2) {
    throw Error(
      "getTimeZoneOffset: isoStrRaw(" +
        isoStrRaw +
        ") split with T failed. result toks(" +
        String(toks) +
        ")"
    );
  }

  // On some systems toLocalString() is ISO format YYYY-MM-DD, on some other systems, it's MM/DD/YYYY.
  let dateStr: string = toks[0];
  const toksD: string[] = dateStr.split("/");
  if (toksD.length == 3) {
    // This means dateStr is in MM/DD/YYYY format convert to ISO.
    dateStr =
      toksD[2] +
      "-" +
      toksD[0].padStart(2, "0") +
      "-" +
      toksD[1].padStart(2, "0");
  }
  const iso =
    dateStr +
    "T" +
    toks[1] +
    "." +
    date.getMilliseconds().toString().padStart(3, "0");

  // Lie to the Date object constructor that it's a UTC time.
  const lie = new Date(iso + "Z");
  if (lie == undefined) {
    throw Error(
      "getTimeZoneOffset: result lie is undefined. Check input string(" +
        iso +
        "Z)"
    );
  }

  // Return the difference in timestamps, as minutes
  // Positive values are West of GMT, opposite of ISO 8601
  // this matches the output of `Date.getTimeZoneOffset`
  return -(lie.getTime() - date.getTime()) / 60 / 1000;
}
Karmenkarna answered 22/8, 2023 at 18:17 Comment(0)
R
-1

An easy way using toLocalString to get the difference with GMT of the timezones. Then you only need to substract.

var d = new Date();
    
var a = d.toLocaleString("en-US", { timeZone: 'Asia/Tokyo', timeZoneName: "short" }).split(/GMT/g)[1];
var b = d.toLocaleString("en-US", { timeZone: 'America/Montreal', timeZoneName: "short"  }).split(/GMT/g)[1];
var diff = a - b;
Roxanaroxane answered 7/1, 2022 at 22:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.