如何在JavaScript中计算两个时区之间的差异?

17
例如,东部和中部的差异是1。我下面的解决方案感觉有点瑕疵。是否有更简单/更好的方法?

例如,东部和中部之间的差别为1。我的解决方案可能不太优雅。是否有更简单或更好的方法?

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

我使用Momentjs库作为示例。


1
减去偏移量怎么样? - CodingIntrigue
那就像 (moment().tz("America/New_York").zone() - moment().zone()) / 60 这样?我想这样好些。 - Homer
你计划用它做什么?你真的需要使用时区名称吗? - Salman A
在这种情况下,我们需要考虑。网络服务器位于东部时区;而我们在中部时区。我需要知道用户所在的时区与服务器之间的差异。 - Homer
4个回答

45
无法计算任意两个时区之间的差异。您只能为特定时间计算差异。
  • 目前伦敦和纽约之间相差4个小时(此内容编写于2015年3月25日)。
  • 几周前它们之间相差5个小时,几周后也将是5个小时。
  • 每个时区在不同时刻转换夏令时偏移量。
这在两个时区之间的一般情况下是正确的。但是,有些时区要么完全同时切换,要么根本不切换。
  • 伦敦和巴黎之间始终相差1小时,因为它们同时切换。
  • 亚利桑那州和夏威夷之间始终相差3个小时,因为两者都没有夏令时。
请记住,在美国,每个使用夏令时的时区实际上在不同的时间切换。它们都在当地时间早上2点切换,但不是在同一个世界标准时间切换。
  • 在芝加哥和纽约之间通常相差1小时。
  • 但是每年有两个短暂的时期,它们要么相差2小时,要么时间完全相同。

请参见时区标签百科中的“时区!=偏移量”。

现在关于moment-timezone,您在评论中说:

Web服务器位于东部时区;我们在中部。我需要用户时区与服务器之间的差异。

Web服务器的时区无关紧要。您应该能够在世界上任何地方托管而不影响您的应用程序。如果不能,则做错了。

您可以获取当前时间差,以及用户所在时区(美国中部时间)与您的时区之间的差异。如果代码在浏览器中运行,则甚至不需要知道用户的确切时区:

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;

如果代码是在服务器上运行(在node.js应用程序中),那么你需要知道用户的时区。只需像这样更改第一行即可:
var now = moment.tz("America/New_York"); // their time zone

更新的答案:

在支持ECMAScript国际化API并完全实现IANA时区支持的环境中,可以在不使用Moment的情况下完成。这在大多数现代浏览器中都是可行的。

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;
}

使用示例:

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

如果您想知道它们之间的差异,只需简单地减去结果即可。

Node.js

上述函数适用于安装了国际化支持的Node.js(对于Node 13及更高版本,这是默认设置)。如果您使用旧版本的system-icusmall-icu,则可以使用此修改后的函数。它也适用于浏览器和full-icu环境,但稍微大一些。(我已在Linux上的Node 8.17.0和Windows上的Node 12.13.1上测试过此功能。)
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;
}

请注意,无论哪种方式,我们都必须通过Intl来正确应用时区。

1
@AlexeyPetrushin - 感谢您指出这一点。问题在于,Node.js v13之前的版本默认使用small-icu,它不支持区域设置 - 因此您会得到系统区域设置格式(不支持en-CA y-m-d格式)。我扩展了我的答案,包括另一种方法,使用formatToParts API。这应该可以在不更新Node的情况下为您工作。 - Matt Johnson-Pint
2
干得好! 需要注意的是:const hour = map.get('hour'); 有时会返回“24”,这会导致无效的ISO字符串。需要在某个地方放置一个 hour==="24" ? "00" : hour; 检查来修复它。顺便说一下,这是使用Node v12+的情况。 此外,将结果四舍五入也没有坏处。我因为 -0 !== 0 而遇到了一些烦人的单元测试失败。 - Trevedhek
有趣。虽然,24表示后续一天的0点,所以可能需要进行更多的数学计算。最简单的方法可能是调整代码。或者,我想知道是否有一个Intl设置可以避免这样的结果。也许传递某些特定的语言环境会更好,而不是使用undefined。(顺便说一下,Temporal将正确解决这个问题。) - Matt Johnson-Pint
在我看来,最后一个例子的描述可能不正确或者不够直观。它返回的是传递的时区与UTC之间的差异,而不是传递的时区与用户所在时区之间的差异。 - limeandcoconut
我遇到了与Trevedhek相同的情况。因此建议进行编辑,将const hour = map.get('hour')替换为const hour = ${map.get('hour') % 24}.padStart(2, "0") @MattJohnson-Pint - Mehmet Kaplan
显示剩余3条评论

1

由于夏令时(dst)等因素的影响,两个时区之间的差异只能通过特定时间来衡量。

但是,对于特定日期,下面的代码应该有效。

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);
}

偏移/差异以毫秒为单位

然后你需要做的就是:

const offset = getOffsetBetweenTimezonesForDate(date, 'America/New_York', 'America/Chicago');

请查看https://dev59.com/NV4b5IYBdhLWcg3wXAV1#29268535 - 您的答案没有提供任何新信息。 - xuesheng

0
这是基于https://dev59.com/NV4b5IYBdhLWcg3wXAV1#29268535答案的工作中的TypeScript代码。
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;
}

-1
使用 toLocalString 轻松获取时区与 GMT 的差异,然后只需要进行减法运算即可。
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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接