如何将JavaScript Date初始化为特定的时区
如何将JavaScript Date初始化为特定的时区
我有一个表示特定时区的日期时间字符串,想将其转换为本地时间。但是我不知道如何在 Date 对象中设置时区。
例如,我有一个 Feb 28 2013 7:00 PM ET
,我可以
var mydate = new Date(); mydate.setFullYear(2013); mydate.setMonth(02); mydate.setDate(28); mydate.setHours(7); mydate.setMinutes(00);
据我所知,我可以设置 UTC 时间或本地时间。但是,我如何设置其他时区的时间?
我尝试使用从 UTC 中添加/减去偏移量,但是我不知道如何解决夏令时。我不确定我是否朝着正确的方向走。
如何在 JavaScript 中将来自不同时区的时间转换为本地时间?
正如Matt Johnson所说的:
如果你能将你的使用限制在现代的Web浏览器中,你现在可以使用以下方法而无需任何特殊库:
new Date().toLocaleString("en-US", {timeZone: "America/New_York"})
这不是一个全面的解决方案,但它适用于许多情况,其需要仅进行输出转换(从UTC或本地时间到特定时区,但不是反向的情况)。
所以,尽管浏览器不能在创建日期时读取IANA时区,或者没有任何方法在现有的Date对象上更改时区,但似乎有一个变通的方法:
function changeTimezone(date, ianatz) { // suppose the date is 12:00 UTC var invdate = new Date(date.toLocaleString('en-US', { timeZone: ianatz })); // then invdate will be 07:00 in Toronto // and the diff is 5 hours var diff = date.getTime() - invdate.getTime(); // so 12:00 in Toronto is 17:00 UTC return new Date(date.getTime() - diff); // needs to substract } // E.g. var here = new Date(); var there = changeTimezone(here, "America/Toronto"); console.log(`Here: ${here.toString()}\nToronto: ${there.toString()}`);
背景
JavaScript的Date
对象在内部跟踪的是UTC时间,但通常接受输入和产生输出则是根据计算机运行的本地时间。它几乎没有处理其他时区时间的工具。
Date
对象的内部表示是一个数字,表示从1970-01-01 00:00:00 UTC
以来经过的毫秒数,不考虑闰秒。
Date
对象本身没有存储任何时区或字符串格式。
当运用Date
对象的各种函数时,计算机的本地时区将应用于内部表示。如果函数产生字符串,则计算机的本地信息可能被考虑在内,以确定如何产生该字符串。详细信息因函数而异,并且有些是特定于实现的。
Date
对象可以处理非本地时区的唯一操作是:
-
它可以解析包含来自任何时区的数值UTC偏移量的字符串。它使用此信息来调整被解析的值,并存储UTC等效值。在生成的
Date
对象中,原始的本地时间和偏移量不会保留。例如:var d = new Date("2020-04-13T00:00:00.000+08:00"); d.toISOString() //=> "2020-04-12T16:00:00.000Z" d.valueOf() //=> 1586707200000 (this is what is actually stored in the object)
-
在实现了ECMA国际化API(又名“Intl”)的环境中,
Date
对象可以产生针对给定时区标识符调整的地方特定字符串。这是通过toLocaleString
及其变体的timeZone
选项实现的。大多数实现将支持IANA时区标识符,如'America/New_York'
。例如:var d = new Date("2020-04-13T00:00:00.000+08:00"); d.toLocaleString('en-US', { timeZone: 'America/New_York' }) //=> "4/12/2020, 12:00:00 PM" // (midnight in China on April 13th is noon in New York on April 12th)
大多数现代环境都支持完整的IANA时区标识符集合(可以在这里查看兼容性表)。但要注意,Intl要求支持的唯一标识符是
'UTC'
,因此如果需要支持旧版本浏览器或非典型环境(例如轻量级IoT设备),请仔细检查。
库
有几个库可用于处理时区。虽然它们仍然无法使Date
对象表现不同,但它们通常实现了标准的IANA时区数据库,并提供了在JavaScript中使用它的函数。现代库使用Intl API提供的时区数据,但旧的库通常有开销,特别是如果您在Web浏览器中运行,因为数据库可能会变得有点大。这些库中的一些还允许您有选择地减少数据集,无论是根据支持的时区还是可用的日期范围。
以下是需要考虑的库:
基于Intl的库
新开发应从以下实现之一中选择,它们依赖于Intl API来获取它们的时区数据:
- Luxon(Moment.js的继任者)
- date-fns-tz(date-fns的扩展)
- Day.js(使用其 Timezone plugin )
非Intl的库
这些库是维护的,但是需要打包自己的时区数据,这可能会非常大。
- js-joda/timezone(js-joda的扩展)
- moment-timezone*(Moment.js的扩展)
- date-fns-timezone(date-fns 1.x的旧版扩展)
- BigEasy/TimeZone
- tz.js
*虽然以前建议使用Moment和Moment-Timezone,但现在Moment团队更喜欢用户选择新开发用Luxon。
停用的库
这些库已正式停用,不应再使用。
未来提议
TC39 Temporal Proposal旨在为JavaScript语言本身提供一组新的标准对象,用于处理日期和时间。这将包括支持一个时区感知对象。
常见错误
经常尝试的几种方法是错误的,通常应该避免使用。
重新解析
new Date(new Date().toLocaleString('en', {timeZone: 'America/New_York'}))
上述方法正确地使用Intl API在特定时区创建字符串,但然后错误地将该字符串传递回Date
构造函数。 在这种情况下,解析将是特定于实现的,并且可能完全失败。 如果成功,计算机的本地时区将在解析期间应用,结果可能是Date
对象现在表示错误的时间点。
时间位移
var d = new Date(); d.setTime(d.getTime() + someOffset * 60000);
上面的方法试图通过将Unix时间戳位移来操作Date
对象的时区。然而,由于Date
对象仅以UTC跟踪时间,它实际上只是使Date
对象代表不同的时间点。
有时会直接在构造函数上使用相同的方法,但这也是无效的。
时间位移有时在日期库内部被用作快捷方式,以避免编写日历日算法。在这样做时,必须避免访问非UTC属性。例如,一旦位移,调用getUTCHours
是可以接受的,但调用getHours
是无效的,因为它使用本地时区。
它被称为“时间位移”,因为正确使用时,Unix纪元(1970-01-01T00:00:00.000Z
)已不再与时间戳0
相关联,而是通过偏移量的量移动到不同的时间戳。
如果您不是编写日期库,您不应该进行时间位移。
有关更多关于时间位移的详情,请观看Greg Miller在CppCon 2015的此视频。该视频是关于C++中的time_t
,但解释和问题是相同的。(对于JavaScript的开发者来说,每当您听到Greg提到time_t
,只需想到“Date
对象”。)
尝试创造一个“UTC日期”
var d = new Date(); var utcDate = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()));
在这个例子中,d
和utcDate
是相同的。构建utcDate
的工作是多余的,因为d
已经以UTC为单位。检查toISOString
、getTime
或valueOf
函数的输出将显示两个变量的相同值。
另一种类似的方法是:
var d = new Date(); var utcDate = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
这种方法将UTC值传递到期望本地时间值的Date
构造函数中。生成的Date
对象现在表示完全不同的时间点。这本质上是之前描述的时间位移相同的结果,因此应该避免使用。
获得基于UTC的Date
对象的正确方法是使用new Date()
。如果需要一个基于UTC的字符串表示形式,则使用new Date().toISOString()
。