在 JavaScript 中给定 TimeZone 字符串计算 UTC 偏移量

Calculate the UTC offset given a TimeZone string in JavaScript

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

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

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

你检查时刻时区了吗?

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

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时区创建的,而不是指定的时区。因此,输出可能与您在转换附近所期望的不完全相同。这可能会被黑客攻击更多以解决这个问题,但我会把它留给你作为练习。

再次 - 虽然这个答案满足所要求的标准,但由于不涉及外部库,我强烈建议 against 在任何生产代码中使用它。如果你打算用这个做任何重要的事情,我会使用 one of the libraries I listed here.

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

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

你必须:

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

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

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

所选答案并未真正回答问题。作者想要的是一个可以输入时区名称然后 return 偏移量的功能。

经过一些调查和 digging the source code of icu4c,发现以下代码片段可以满足您的需求:

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 相同的模式,可以轻松解析。

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

希望对你也有帮助:)