使用moment.js将一系列跨越Fall DST边界的非UTC时间戳转换为UTC时间戳的正确方法是什么?

时间:2017-04-12 04:23:18

标签: timezone momentjs utc dst

背景

我正在调查一个项目的moment.js的不同用例,但我对在秋季结束的夏令时问题感到困惑。在提出我的问题之前,由于我想要清楚并为有类似问题的其他人提供背景,让我解释一下我正在做什么以及春天夏令时发现了什么。

首先,我正在使用UTC时间戳和America / New_York时间戳。在美国,2017年的夏令时从3月12日凌晨2点开始(从凌晨2:00:00到凌晨3:00:00跳过),并于11月5日凌晨2点结束(从凌晨2:00:00恢复到凌晨1:00:00 )。由于我也总是知道我需要转换的目标时区(America / New_York),因此我不会依赖moment.js来检测我的本地时区,而是明确指定我想要的时区。

在夏令时实施夏令时,观察夏令时的时区,如America / New_York,会向前跳一小时。 moment.js处理这个很好。

例如,如果我将moment.js的UTC时间戳传递给美国/纽约的夏令时生效一秒钟,那么它看起来像这样:

moment('2017-03-12T06:59:59Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z')

上面的输入因为我的时间戳上的Z而被视为UTC,我明确地使用.tz('America/New_York')设置目标时区,以便它不使用本地系统时间。

或者使用moment-timezone我可以明确地将输入时区设置为UTC并将输出设置为America / New_York。

moment.tz('2017-03-12T06:59:59', 'UTC').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z')

无论哪种方式,结果都是2017/03/12 01:59:59 am EST

然后,我在一秒钟后运行相同的命令。我将使用上面第一个示例中给出的格式,我将时间指定为UTC,然后将其转换为America / New_York时间:

moment('2017-03-12T07:00:00Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z')

我的结果与预期的一样正确:2017/03/12 03:00:00 am EDT - 由于夏令时,提前一小时跳过了。

然后,我可以使用moment-timezone通过传入America / New_York时间戳并将其转换为UTC来返回其他方式。

moment.tz('2017-03-12T01:59:59', 'America/New_York').utc().format('YYYY/MM/DD hh:mm:ss a z')

这给了我2017/03/12 06:59:59 am UTC

美国/纽约时区的下一刻,因为夏令时发挥作用,现在是03:00:00,所以我将其转换为UTC ......

    moment.tz('2017-03-12T03:00:00', 'America/New_York').utc().format('YYYY/MM/DD hh:mm:ss a z')

...并获得看似正确的2017/03/12 07:00:00 am UTC。 在America / New_York时间跳过一小时(“丢失”),但是时刻可以检测到并将其转换为UTC。

总结一下,对于美国的春季夏令时变化,我可以将UTC时间戳传递到moment.js或moment-timezone,并在另一个时区中获取时间戳,同时应用正确的夏令时偏移量。然后我也可以将America / New_York时间戳传递给片刻,并获得正确转换的UTC时间戳。

我的问题

很好,所以当秋天的白天节省结束时我想做同样的事情当然不是那么简单。我的假设是,由于夏令时有效地导致一小时“重复”,因此暂时无法知道UTC的正确时间。换句话说,当夏令时开始时有一小时的间隙,现在我们有一个小时的重叠。

问题(第1部分):有没有办法将相对时间戳和时区传递给片刻并恢复正确的UTC时间?当我在下面的例子中尝试这个时,UTC时刻会跳过一个小时。

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偏移上下文,因此我假设当前不能区分以星号开头的America / New_York时间戳:

*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部分):如果我在America / New_York时区提交数据,那么认为我将基本上有两个小时的数据点全部填入(看似)单一小时的01小时是正确的:2017年11月5日00:00到01:59:59?

相关主题

还有一些关于SO的其他主题与此相关,但我找不到任何主题或回答同一个问题。我将在此处链接一些以供参考:

1 个答案:

答案 0 :(得分:3)

看起来你已经通过思考并完成了一些基础研究。谢谢!

您所描述的是covered in the moment-timezone docs here。如果数据在输入中不能作为UTC偏移量使用,则无法区分出第一次或第二次出现模糊的本地时间。片刻选择第一次出现,因为时间向前移动,所以这通常是大多数情景下最明智的选择。

问题在于含糊不清。即使只是一个人,如果我在2017年11月5日凌晨1点在纽约说"你不知道我在描述的两个时间点中的哪一个。

那就是说,有时候你有外在的知识可以提供帮助。例如,如果您有一组有序的时间戳,其中包含向后跳过的时间,那么您就知道遇到了后退转换。假设我在当地时间间隔15分钟记录数据:

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

您必须编写自己的检测逻辑,以便将该方案的一个值与下一个值进行比较。另请注意,如果您没有任何时间似乎不按顺序排列,那么无法确定正在描述的事件。 A&#34;心跳&#34;在某些情况下,信号可以提供帮助。

现在如何在不事先知道偏移的情况下选择Moment中的第二次出现?像这样:

首先,抓住hasAmbiguousWallTime函数from here

然后定义另一个函数:

function adjustToLaterWhenAmbiguous(m) {
    if (hasAmbiguousWallTime(m)) {
        m.utcOffset(moment(m).add(1, 'hour').utcOffset(), true);
    }
}

现在你可以这样做:

// 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.tz(s, 'UTC')
  • ,而不是moment.utc(s)
  • 考虑到,而不是moment.tz(s, 'America/New_York').tz('UTC') 使用moment.tz(s, 'America/New_York').utc()
  • 您可能需要查看DST tag wiki以查看问题空间。
  • 在你的问题的第二部分,是的 - 你最终会将两小时的数据填入可能被视为一小时空间的数据中。人们一直有图表和图表的问题。他们在当地时间绘制一个具有恒定值的东西,然后在春天看到归零效果,在秋天看到倍增效应。即使你告诉时间使用后来的事件,除非你实际以UTC而不是当地时间显示图形,否则你不会避免这种情况。