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

时间:2016-03-20 10:46:18

标签: javascript datetime timezone locale offset

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

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

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

5 个答案:

答案 0 :(得分:6)

你看过时刻时区吗?

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

答案 1 :(得分:3)

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;
}

这适用于当前版本的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相同 - 以分钟为单位,正值为 West UTC。如果您正在处理ISO8601偏移,则需要转换为小时并反转符号。

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

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

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

再次 - 虽然这个答案满足要求的标准,因为没有涉及外部库,我强烈建议反对在任何生产代码中使用它。如果您计划对此做一些重要的事情,我会使用one of the libraries I listed here。我的个人偏好是moment.jsmoment-timezone插件,因为我帮助维护该库。 YMMV

答案 2 :(得分:1)

您必须:

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

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

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 :(得分:1)

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

经过调查和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相同,可以很容易地对其进行解析。

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

希望它也对您有帮助:)

答案 4 :(得分:0)

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

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