在 JavaScript 中给定时区字符串的情况下计算 UTC 偏移量

Calculate the UTC offset given a TimeZone string in JavaScript

本文关键字:情况下 计算 UTC 偏移量 字符串 JavaScript 定时区      更新时间:2023-09-26

使用标准JS库(ECMA5),不使用momentjs或外部库,如何在给定时区字符串(例如"欧洲/罗马"或"美国/Los_Angeles")的情况下计算UTC偏移量?

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

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

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() 传递当前日期,但结果将根据您调用函数的时间而变化。 请参阅时区标签维基中的"时区!=偏移量"。

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

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

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

  • 在上面的示例用法部分中有一个错误,它说明了为什么这个 API 是一个错误。 具体而言,创建Date对象时无法指定时区。 我传入的 1 月 1 日和 7 月 1 日日期是在本地时区创建的,而不是指定的时区。 因此,输出可能不完全是您在过渡附近的预期。 这可以被黑客入侵以解决此问题,但我会将其作为练习留给您。

再次 - 尽管此答案满足要求的标准,但由于不涉及外部库,我强烈建议不要在任何生产代码中使用它。 如果你打算用这个做任何重要的事情,我会使用我在这里列出的一个库。

你检查过时刻时区吗?

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

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

经过一些调查和挖掘 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"));

请注意,此处使用的区域设置ia是国际语言。原因是根据 icu4c 的源代码,时区名称因语言环境而异。即使您使用相同的区域设置,时区名称的格式仍可能因不同的时区而异。

使用国际语(ia),格式总是与GMT+NN:NN相同的模式,可以很容易地解析。

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

希望它对您有所帮助:)

你必须:

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

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

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

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

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