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

GM *_*cid 8 javascript timezone datetime locale offset

使用标准的JS库(ECMA5),而无需使用momentjs或外部库,你如何计算UTC偏移给出一个TimeZone字符串,如"欧洲/罗马"或"美国/洛杉矶"?

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

function getUtcOffset(timezone) {
  // return int value. 
  // > 0 if +GMT 
  // < 0 if -GMT.
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*int 10

ECMAScript(ECMA-262)中没有可以执行您请求的操作的功能。这仅仅是因为标准ECMAScript除了本地计算机和UTC之外都不了解其他时区。

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

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

这将在当前版本的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
Run Code Online (Sandbox Code Playgroud)

有关此特定功能的一些注意事项:

  • 就像我提到的那样,它不会在所有地方都起作用。最终,随着所有浏览器都赶上现代标准,它会,但是目前不会。

  • 您传递的日期确实会影响结果。这是由于夏时制和其他时区异常。您可以使用来传递当前日期new Date(),但是结果将根据调用函数的时间而改变。请参阅时区标记Wiki “时区!=偏移量” 。

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

  • 该功能依赖于该功能的时区格式化toLocaleString功能。我选择了'ja'区域性,因为日期部分已经按照数组的正确顺序排列了。这确实是一个hack。理想情况下,将有一个API使您可以在格式化时访问时区信息,而不必将其绑定到语言环境。不幸的是,该特定API的设计者犯了将时区与语言环境相关联的错误。这是其他几种来自多种语言的API所犯的错误,不幸的是,此处已将其带入JavaScript。

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

  • 上面的示例用法部分中有一个错误,该错误说明了此API错误的部分原因。具体而言,有没有办法当你指定一个时间区创建一个Date对象。我传递的1月1日和7月1日是在本地时区中创建的,而不是在指定的时区中创建的。因此,输出可能不是您在过渡附近所期望的。为了解决此问题,可能会更多地利用它,但是我将作为练习留给您。

再说一次-尽管此答案满足了所要求的标准,但由于不涉及外部库,因此我强烈建议不要在任何生产代码使用此答案。如果您打算对此做任何重要的事情,那么我将使用这里列出的库之一。我个人比较喜欢带有moment-timezone插件的moment.js,因为我可以帮助维护该库。青年汽车


小智 8

你查看了时刻时区吗?

moment.tz("America/Los_Angeles").utcOffset();
Run Code Online (Sandbox Code Playgroud)


eke*_*ner 5

你必须:

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

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

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');
Run Code Online (Sandbox Code Playgroud)

哪个输出像

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
Run Code Online (Sandbox Code Playgroud)


Jia*_*ang 5

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

经过一番调查并挖掘 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"));
Run Code Online (Sandbox Code Playgroud)

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

使用Interlingua ( ia),格式始终是相同的模式GMT+NN:NN,可以轻松解析。

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

希望它也能帮助你:)