如何计算 JavaScript 中 2 个时区的差异?

IT技术 javascript timezone momentjs
2021-03-07 08:04:49

例如,东部和中部的差异是1。我下面的解决方案感觉很糟糕。有没有更简单/更好的方法?

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

我的例子是使用Momentjs库。

2个回答

计算任意两个时区之间差异是不可能的您只能计算特定时刻的差异

  • 这是目前伦敦和纽约4之间的小时差(于2015年3月25日写这个)。
  • 但几周前是 5 小时的差异,而几周后将是 5 小时。
  • 每个时区在不同的时间点切换夏令时的偏移量。

这在两个时区之间的一般情况下是正确的。然而,有些时区要么完全同时切换,要么根本不切换。

  • 始终伦敦和巴黎之间的一个小时后,因为他们在同一时刻切换。
  • 总是亚利桑那州和夏威夷之间的3个小时,因为也没有DST。

请记住,在美国,使用 DST 的每个时区实际上在不同的时间切换。他们都在各自的上午2:00切换本地时间,但不能在同一通用的时间的时刻。

  • 所以芝加哥和纽约之间,通常相隔1小时
  • 但是每年有两个短暂的时期,它们要么相隔 2 小时,要么具有相同的确切时间。

另请参阅时区标记 wiki“时区 != 偏移量”

现在关于时刻时区,您在评论中说:

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

如果你想要它们之间的差异,你可以简单地减去结果。

节点.js

上述函数适用于安装full-icu 国际化支持的Node.js (这是 Node 13 和更新版本的默认设置)。如果您有带有system-icu的旧版本small-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');
  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正确应用时区。

@AlexeyPetrushin - 感谢您指出这一点。问题是 Node.js pre v13small-icu默认附带,它没有语言环境支持 - 所以你会得到系统语言环境格式(不支持en-CAymd 格式)。我已经扩展了我的答案,包括使用formatToPartsAPI的替代方法这应该对你有用,而无需更新 Node.js。
2021-04-24 08:04:49
有趣的。虽然,24种手段的0以下的一天,所以会有更多的数学来somwhere做。可能最容易调整谎言。或者,我想知道是否有 Intl 设置来避免这种结果。也许通过一些特定的语言环境而不是undefined. (顺便说一句,Temporal会妥善解决这个问题。)
2021-04-27 08:04:49
用 node.js 试过它,但它不起作用 -const lie = new Date(iso + 'Z')当它解析这个日期字符串时,这条线不起作用4/15/2020T08:43:57.710Z,结果是NaN但也许我的节点太旧了v8.3.0
2021-05-03 08:04:49
@ErKKChopra - IE11 确实支持toLocaleString,但不支持 IANA 时区。如果您同时需要时区支持和对 IE11 和其他旧浏览器的支持,您将需要使用一个旧库,它带来自己的时区数据,而不是依赖Intl. 例如,momentjs.com/timezone
2021-05-09 08:04:49
干得好!一个注意事项:const hour = map.get('hour');有时会返回“24”,这会导致无效的 iso 字符串。它需要hour==="24" ? "00" : hour;在某个地方进行检查才能修复它。这是 Node v12+ BTW。此外,舍入结果也无妨。我有一些烦人的单元测试失败,因为-0 !== 0.
2021-05-14 08:04:49

由于夏令时(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');
请查看stackoverflow.com/a/29268535/2381172 - 您的回答没有提供任何新信息。
2021-05-08 08:04:49