Correct way to use moment.js to convert a series of non-UTC timestamps that cross the Fall DST boundary to UTC timestamps?
背景
我正在为一个项目调查不同的moment.js用例,但是在夏时制问题上遇到了困难,最终在秋季结束。在问我的问题之前,因为我想清楚地为其他有类似问题的人提供背景,让我解释一下我在做什么,以及在春季夏令时发现了什么。
首先,我正在使用UTC时间戳和美国/纽约时间戳。在美国,2017年的夏令时从3月12日凌晨2点开始(从凌晨2:00:00到凌晨3:00:00),到11月5日凌晨2点结束(从凌晨2:00:00恢复到凌晨1:00:00)。因为我也知道我需要转换到的目标时区(美国/纽约),所以我不会依赖moment.js来检测我的本地时区,而是显式指定我想要的时区。
在夏时制生效的春天,观察夏时制的时区,如美国/纽约,向前跳了一个小时。moment.js处理得很好。
例如,如果我在美国/纽约夏令时生效前一秒钟通过了一个UTC时间戳,则如下所示:
1 | moment('2017-03-12T06:59:59Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z') |
由于我的时间戳上的
或者,使用矩时区,我可以显式地将输入时区设置为UTC,并将输出设置为美国/纽约。
1 | moment.tz('2017-03-12T06:59:59', 'UTC').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z') |
不管怎样,结果都是
然后,我在一秒钟后运行相同的命令。我将使用上面第一个示例中给出的格式,其中我将时间指定为UTC,然后将其转换为美国/纽约时间:
1 | moment('2017-03-12T07:00:00Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z') |
我的结果和预期一样是正确的:
然后,我可以通过输入美国/纽约时间戳并将其转换为UTC,使用Moment时区返回到另一个方向。
1 | moment.tz('2017-03-12T01:59:59', 'America/New_York').utc().format('YYYY/MM/DD hh:mm:ss a z') |
这给了我1[4]
下一刻在美国/纽约时区,因为白天的节约开始发挥作用,是03:00:00,所以我把它转换成UTC…
1 | moment.tz('2017-03-12T03:00:00', 'America/New_York').utc().format('YYYY/MM/DD hh:mm:ss a z') |
…然后得到看起来正确的
总之,对于美国的春季夏令时变化,我可以将UTC时间戳传递到moment.js或moment time zone中,并在另一个时区中返回时间戳,同时应用正确的夏令时偏移量。然后我还可以将美国/纽约时间戳传递给Moment,并返回正确转换的UTC时间戳。
我的问题
太好了,所以我想在秋季夏令结束的时候做同样的事情,当然也不是那么简单。我的假设是,由于日光节约有效地导致了一个小时的"重复",暂时无法知道正确的时间UTC。换言之,当夏令时有一个小时的间隔,现在我们有一个小时的重叠。
问题(第1部分):是否有一种方法可以将相对时间戳和时区传递到Moment中并返回正确的UTC时间?当我在下面的例子中尝试这个时,moment跳过了UTC方面的一个小时。
1 2 3 4 | moment.tz('2017-11-05T01:00:00', 'America/Denver').tz('UTC').format('YYYY/MM/DD hh:mm:ss a z') =>"2017/11/05 07:00:00 am UTC" moment.tz('2017-11-05T01:59:59', 'America/Denver').tz('UTC').format('YYYY/MM/DD hh:mm:ss a z') =>"2017/11/05 07:59:59 am UTC" moment.tz('2017-11-05T02:00:00', 'America/Denver').tz('UTC').format('YYYY/MM/DD hh:mm:ss a z') =>"2017/11/05 09:00:00 am UTC" moment.tz('2017-11-05T02:59:59', 'America/Denver').tz('UTC').format('YYYY/MM/DD hh:mm:ss a z') =>"2017/11/05 09:59:59 am UTC" |
我假设这是因为时间是按时间顺序发生的,如下面的例子所示。未给出UTC偏移上下文,因此我假设时刻不能区分美国/纽约时间戳前面带星号:
1 2 3 4 5 6 | *2017/11/05 01:00:00 am America/New_York => 2017/11/05 07:00:00 am UTC *2017/11/05 01:59:59 am America/New_York => 2017/11/05 07:59:59 am UTC *2017/11/05 01:00:00 am America/New_York => ??? *2017/11/05 01:59:59 am America/New_York => ??? 2017/11/05 02:00:00 am America/New_York => 2017/11/05 09:00:00 am UTC 2017/11/05 02:59:59 am America/New_York => 2017/11/05 09:59:59 am UTC |
再说一次,我想知道是否有办法解决这个问题?当前我所拥有的数据中的时间戳不包含UTC偏移量。
问题(第2部分):如果我在美国/纽约时区展示数据,那么我是否认为我基本上将有两个小时的数据点,所有这些数据点都填充到(似乎)2017年11月5日01:00:00至01:59:59的一个小时周期中?
相关主题
还有其他一些与此相关的主题,但没有一个与此相关的主题是我发现的。我将在这里链接几个以供参考:
- moment.js将本地时间转换为UTC时间确实有效
- 用我创建的时区偏移量初始化一个时刻
看来你已经把这个问题想清楚了,做了一些基础研究。谢谢!
您所描述的内容包含在这里的即时时区文档中。如果数据在输入中不可用作UTC偏移量,则无法区分不明确本地时间的第一次或第二次出现之间的差异。因为时间是向前移动的,所以对于大多数场景来说,这通常是最明智的选择。
问题是模棱两可。即使只是一个普通人,如果我说"2017年11月5日上午1:00在纽约",你也不知道我在描述两个时间点中的哪一个。
也就是说,有时候你有外部知识可以帮助你。例如,如果您有一组有序的时间戳,其中包含向后跳过的时间,那么您就知道遇到了向后回退转换。假设我在本地时间每隔15分钟记录一次数据:
1 2 3 4 5 6 7 8 9 10 | 00:45 01:00 01:15 01:30 01:45 01:00 <--- this one comes next sequentially, but appears backwards, so infer transition 01:15 01:30 01:45 02:00 |
对于这个场景,您必须编写自己的检测逻辑来将一个值与另一个值进行比较。另外请注意,如果您没有任何时间出现顺序错误,那么您就不能确定描述的是哪个事件。在某些情况下,"心跳"信号可以帮助实现这一点。
现在,在不预先知道偏移量的情况下,如何选择瞬间的第二次出现?这样地:
首先,从这里获取
然后定义另一个函数:
1 2 3 4 5 | function adjustToLaterWhenAmbiguous(m) { if (hasAmbiguousWallTime(m)) { m.utcOffset(moment(m).add(1, 'hour').utcOffset(), true); } } |
现在您可以这样做:
1 2 3 4 5 6 7 8 9 | // start with the first occurrence var m = moment.tz("2017-11-05T01:00:00","America/New_York"); m.format(); //"2017-11-05T01:00:00-04:00" // now shift it to the second occurrence if (... your logic, such as wall time going backwards in sequence, etc. ...) { adjustToLaterWhenAmbiguous(m); m.format(); //"2017-11-05T01:00:00-05:00" } |
这两个功能可能需要加强并添加到Moment时区,但是对于您描述的场景来说,它们应该足够了。
其他几个要点:
- 代替
moment.tz(s, 'UTC') ,考虑使用moment.utc(s) 。 - 考虑使用
moment.tz(s, 'America/New_York').utc() 。 - 您可能需要查看DST标记wiki以可视化问题空间。
- 在你的问题的第二部分,是的——你最终会把两个小时的数据塞进一个小时的空间中。人们总是对图表有这个问题。他们用一个不变的当地时间来绘制一些图,然后在春天看到一个零效应,在秋天看到一个加倍效应。即使告诉Moment使用后一个事件,也不会避免这种情况,除非您实际以UTC而不是本地时间显示图表。