【问题标题】:How Do I calculate the difference of 2 time zones in JavaScript?如何在 JavaScript 中计算 2 个时区的差异?
【发布时间】:2015-05-29 16:44:52
【问题描述】:

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

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

我的示例是使用 Momentjs 库。

【问题讨论】:

  • 减去offsets怎么样?
  • 类似(moment().tz("America/New_York").zone() - moment().zone()) / 60 ?我想这样会好一点。
  • 你打算用它做什么?您真的需要按名称使用时区吗?
  • 在这种情况下我们会这样做。网络服务器位于东部时区;我们在中环。我需要用户时区和服务器的差异。

标签: javascript timezone momentjs


【解决方案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;

【讨论】:

    【解决方案2】:

    由于夏令时 (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');
    

    【讨论】:

    【解决方案3】:

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

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

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

    • 伦敦和巴黎之间总是一小时,因为它们在同一时间切换。
    • 亚利桑那州和夏威夷之间总是 3 小时,因为两者都没有夏令时。

    请注意,在美国,使用 DST 的每个时区实际上会在不同的时间切换。它们都在其本地时间的凌晨 2:00 切换,但不是在同一时间通用

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

    另请参阅the timezone tag wiki 中的“时区!= 偏移量”。

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

    网络服务器位于东部时区;我们在中环。我需要用户时区和服务器的差异。

    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 时区支持的环境中完成。这是当今大多数浏览器。

    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

    上述函数在安装了full-icu internationalization 支持的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');
      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 正确应用时区。

    【讨论】:

    • 用 node.js 尝试过,但它不起作用 - 当它解析这个日期字符串 4/15/2020T08:43:57.710Z 时,这条线不起作用4/15/2020T08:43:57.710Z 结果是NaN。但也许我的节点太旧了v8.3.0
    • @AlexeyPetrushin - 感谢您指出这一点。问题是 Node.js pre v13 默认带有 small-icu,它不支持语言环境 - 所以你得到一个系统语言环境格式(en-CA y-m-d 格式不被尊重)。我使用formatToParts API 扩展了我的答案以包含另一种方法。这应该对您有用,而无需更新 Node。
    • @ErKKChopra - IE11 支持 toLocaleString,但不支持 IANA 时区。如果您既需要时区支持又需要支持 IE11 和其他旧版浏览器,则需要使用带有自己的时区数据的旧版库,而不是依赖 Intl。例如,momentjs.com/timezone
    • 干得好!注意:const hour = map.get('hour'); 有时会返回“24”,这会产生无效的 iso 字符串。它需要在某处放置hour==="24" ? "00" : hour; 检查来修复它。这是 Node v12+ BTW。此外,对结果进行四舍五入也没有什么坏处。我有一些烦人的单元测试失败了,因为-0 !== 0
    • 有趣。不过,24 表示 下一天 的 0,因此在某处需要做更多的数学运算。可能最容易调整谎言。或者,我想知道是否有一个 Intl 设置来避免这种结果。也许通过某些特定的语言环境而不是undefined 会更好。 (顺便说一句,Temporal 会妥善解决这个问题。)
    猜你喜欢
    • 2014-03-22
    • 1970-01-01
    • 2013-05-21
    • 2021-08-26
    • 1970-01-01
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 2015-04-02
    相关资源
    最近更新 更多