【问题标题】:Calculate the UTC offset given a TimeZone string in JavaScript在 JavaScript 中计算给定 TimeZone 字符串的 UTC 偏移量
【发布时间】:2016-07-06 21:12:35
【问题描述】:

使用标准 JS 库 (ECMA5),不使用 momentjs 或外部库,如何计算给定 TimeZone 字符串(例如“Europe/Rome”或“America/Los_Angeles”)的 UTC 偏移量?

UTC 偏移量可能取决于它是否是 DST,因此如果解决方案需要将本地客户端日期转换为指定的时区字符串,这将是有意义的。目标只是知道与 UTC 的偏移量。

function getUtcOffset(timezone) {
  // return int value. 
  // > 0 if +GMT 
  // < 0 if -GMT.
}

【问题讨论】:

标签: javascript datetime timezone locale offset


【解决方案1】:

ECMAScript (ECMA-262) 中没有可以执行您请求的操作的函数。这仅仅是因为标准的 ECMAScript 除了本地计算机和 UTC 之外,对时区一无所知。

但是,在支持 ECMAScript 国际化 API (ECMA-402) 并完全支持 IANA 时区数据库标识符的浏览器中,您可以将这样的函数组合在一起:

function getTimezoneOffset(d, tz) {
  const a = d.toLocaleString("ja", {timeZone: tz}).split(/[/\s:]/);
  a[1]--;
  const t1 = Date.UTC.apply(null, a);
  const t2 = new Date(d).setMilliseconds(0);
  return (t2 - t1) / 60 / 1000;
}

这将适用于当前版本的 Chrome,也许在其他一些地方。但肯定不能保证在任何地方都能工作。特别是,它不能在任何版本的 Internet Explorer 浏览器中运行。

示例用法(在 Chrome 中):

getTimezoneOffset(new Date(2016, 0, 1), "America/New_York") // 300
getTimezoneOffset(new Date(2016, 6, 1), "America/New_York") // 240
getTimezoneOffset(new Date(2016, 0, 1), "Europe/Paris") // -60
getTimezoneOffset(new Date(2016, 6, 1), "Europe/Paris") // -120

关于这个特定功能的一些注意事项:

  • 就像我提到的,它不会在任何地方都有效。最终,所有浏览器都会赶上现代标准,但目前还不会。

  • 你传入的日期确实会影响结果。这是由于夏令时和其他时区异常。您可以只使用new Date() 传递当前 日期,但结果会根据您调用函数的时间而改变。请参阅the timezone tag wiki 中的“时区!= 偏移量”。

  • 此函数的结果与 Date.getTimezoneOffset 相同 - 以分钟为单位,正值是 UTC 的 West。如果您使用 ISO8601 偏移量,则需要转换为小时数并反转符号。

  • 该函数依赖于toLocaleString 函数的时区格式化功能。我选择了'ja' 文化,因为日期部分已经按照数组的正确顺序排列。这确实是一个黑客。理想情况下,会有一个 API 可以让您访问时区信息,而无需在格式化时将其绑定到语言环境。不幸的是,这个特定 API 的设计者犯了将时区与语言环境相关联的错误。这是在其他一些不同语言的 API 中犯的错误,不幸的是在这里被带到了 JavaScript 中。

    明确重申:ECMA-402 中唯一的时区功能是在格式化字符串时应用时区,恕我直言,这是一个设计缺陷。

  • 在我上面的示例用法部分中有一个错误,它举例说明了为什么这个 API 是一个错误的部分原因。具体来说,当您创建 Date 对象时,无法指定时区。我传入的 1 月 1 日和 7 月 1 日日期是在 local 时区创建的,而不是在指定的时区。因此,输出可能与您在转换附近的预期不完全一致。这可能会被更多地破解来解决这个问题,但我会把它作为练习留给你。

再次 - 虽然这个答案满足要求的标准,因为没有涉及外部库,我强烈建议 反对 在任何生产代码中使用它。如果您打算对此做任何重要的事情,我会使用one of the libraries I listed here

【讨论】:

    【解决方案2】:

    你必须:

    • 获取当前日期时间(将为您提供当前设备当前时区的日期时间)
    • 获取当前时区偏移量
    • 将日期时间转换为新的/所需的时区
    • 获取当前日期时间和转换后的日期时间之间的差异
    • 将所述差异添加到当前时区偏移量

    例如以小时为单位返回时区:

    function getTimezoneOffset(tz, hereDate) {
        hereDate = new Date(hereDate || Date.now());
        hereDate.setMilliseconds(0); // for nice rounding
        
        const
        hereOffsetHrs = hereDate.getTimezoneOffset() / 60 * -1,
        thereLocaleStr = hereDate.toLocaleString('en-US', {timeZone: tz}),
        thereDate = new Date(thereLocaleStr),
        diffHrs = (thereDate.getTime() - hereDate.getTime()) / 1000 / 60 / 60,
        thereOffsetHrs = hereOffsetHrs + diffHrs;
    
        console.log(tz, thereDate, 'UTC'+(thereOffsetHrs < 0 ? '' : '+')+thereOffsetHrs);
        return thereOffsetHrs;
    }
    
    
    getTimezoneOffset('America/New_York', new Date(2016, 0, 1));
    getTimezoneOffset('America/New_York', new Date(2016, 6, 1));
    getTimezoneOffset('Europe/Paris', new Date(2016, 0, 1));
    getTimezoneOffset('Europe/Paris', new Date(2016, 6, 1));
    getTimezoneOffset('Australia/Sydney', new Date(2016, 0, 1));
    getTimezoneOffset('Australia/Sydney', new Date(2016, 6, 1));
    getTimezoneOffset('Australia/Sydney');
    getTimezoneOffset('Australia/Adelaide');
    

    哪些输出像

    America/New_York 2015-12-30T22:00:00.000Z UTC-5
    America/New_York 2016-06-30T01:00:00.000Z UTC-4
    Europe/Paris 2015-12-31T04:00:00.000Z UTC+1
    Europe/Paris 2016-06-30T07:00:00.000Z UTC+2
    Australia/Sydney 2015-12-31T14:00:00.000Z UTC+11
    Australia/Sydney 2016-06-30T15:00:00.000Z UTC+10
    Australia/Sydney 2019-08-14T03:04:21.000Z UTC+10
    Australia/Adelaide 2019-08-14T02:34:21.000Z UTC+9.5
    

    【讨论】:

      【解决方案3】:

      选择的答案并没有真正回答问题。作者想要的是一个可以输入时区名称,然后返回偏移量的函数。

      经过一番调查和digging the source code of icu4c,原来follow sn -p可以做你需要的:

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

      请注意,此处使用的语言环境iaInterlingua。原因是根据 icu4c 的源代码,时区名称因地区而异。即使您使用相同的语言环境,时区名称的格式仍然会根据不同的时区而有所不同。

      使用 Interlingua (ia),格式始终与 GMT+NN:NN 相同,易于解析。

      这有点麻烦,但在我自己的产品中效果很好。

      希望对你也有帮助:)

      【讨论】:

      • 在 Chrome 中不起作用。例如,timeZoneName 最终成为 MDT
      【解决方案4】:

      对于可能遇到此问题的其他人,JavaScript 中的标准 Date 函数有一种方法可用于从 UTC 获取偏移量,称为 getTimezoneOffset。

      用法很简单(例如):new Date().getTimezoneOffset()。这将返回当前时区距 UTC 的分钟数。注意:如果当前时区早于 UTC,则返回负数。例如,如果用户的时区是 UTC+1,则返回 -60。

      【讨论】:

      • 这不是在回答问题。 getTimezoneOffset 是客户端本地的。他询问是否提供特定时区参数,而getTimezoneOffset 没有该功能。
      【解决方案5】:

      你检查过时刻-时区吗?

      moment.tz("America/Los_Angeles").utcOffset();
      

      【讨论】:

      • 这是一个很好的答案,但事实证明我毕竟不能使用momentjs。我被困在一个具有 parseZone 的非常旧的版本上,但 not 有 .tz。旧的 momentjs 根本没有时区全名的查找表。因此,如果可能的话,我需要一种方法在本机 JS 中执行此操作。
      • .tz 和数据在moment-timezone 插件中单独提供。
      • 感谢@MattJohnson。但是,我宁愿不必添加时刻时区。我并不完全拥有该软件,因此更改库并非易事。
      • 谢谢,但还有更多。您需要将utcOffset 的返回值除以60 以实现+ 或- GMT,here you can check
      猜你喜欢
      • 2021-02-09
      • 1970-01-01
      • 1970-01-01
      • 2014-03-06
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多