使用 moment.js 将一系列跨越秋季 DST 边界的非 UTC 时间戳转换为 UTC 时间戳的正确方法?
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 的不同用例,但我对夏令时在秋季结束的问题感到困惑。在问我的问题之前,因为我想说清楚并为有类似问题的其他人提供背景知识,所以让我解释一下我在做什么以及 spring 夏令时的发现。
首先,我正在使用 UTC 时间戳和 America/New_York 时间戳。在美国,2017 年夏令时从 3 月 12 日凌晨 2 点开始(从 2:00:00AM 跳到 3:00:00AM),到 11 月 5 日凌晨 2 点结束(从 2:00:00AM 跳到 1:00:00AM ).因为我也总是知道我需要转换到的目标时区 (America/New_York),所以我不会依赖 moment.js 来检测我的本地时区,而是明确指定我想要的时区。
在spring夏令时生效期间,实行夏令时的时区,如America/New_York,向前跳一个小时。 moment.js 处理得很好。
例如,如果我在 America/New_York 夏令时生效前一秒传递 moment.js UTC 时间戳,它看起来像这样:
moment('2017-03-12T06:59:59Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z')
上面的输入被认为是 UTC,因为我的时间戳上有 Z
,我明确地将目标时区设置为 .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 time:
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
America/New_York 时区的下一刻,由于开始实行夏令时,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 时间内跳过了一个小时 ("lost"),但 moment 可以检测到并将其转换为 UTC。
总而言之,对于美国的 spring 夏令时更改,我可以将 UTC 时间戳传递到 moment.js 或 moment-timezone 并返回另一个时区的时间戳还应用了正确的夏令时偏移量。然后我还可以将 America/New_York 时间戳传递给 moment 并取回正确转换的 UTC 时间戳。
我的问题
太好了,所以我想在秋季夏令时结束时做同样的事情,当然这不是那么简单。我的假设是,由于夏令时有效地导致一个小时 "repeat",暂时无法知道正确的 UTC 时间。换句话说,夏令时开始时有一个小时的间隔,现在我们有一个小时的重叠。
问题(第 1 部分):有没有办法将相对时间戳和时区传递给 moment 并取回正确的 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 时区显示数据,那么认为我基本上将两个小时的数据点全部塞入一个(看似)单个数据点是否正确2017 年 11 月 5 日从 01:00:00 到 01:59:59 的小时数?
相关主题
关于 SO 的其他一些主题与此相关,但 none 我发现提出或回答了同样的问题。我将link这里的几个供参考:
- Moment.js Convert Local time to UTC time does work
看来你已经想通了问题,做了一些基础研究。谢谢!
您描述的是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
您必须编写自己的检测逻辑,以针对该场景将一个值与下一个值进行比较。另请注意,如果您没有任何时间出现乱序,那么您无法 确信所描述的是哪个事件。在某些情况下,"heartbeat" 信号可以提供帮助。
现在如何在事先不知道偏移量的情况下选择Moment中的第二次出现?像这样:
首先,获取hasAmbiguousWallTime
函数。
然后再定义一个函数:
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-timezone 中,但它们应该足以满足您描述的场景。
其他几个小问题:
- 而不是
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 以可视化问题 space。
- 关于您问题的第二部分,是的 - 您最终会将两个小时的数据塞入可能可视化为一小时的数据中 space。人们一直对图形和图表有这个问题。他们绘制了一些在本地时间具有恒定值的图形,然后在 spring 中看到了归零效应,在秋季看到了加倍效应。即使您告诉 moment 使用较晚出现的时间,您也不会避免这种情况,除非您实际以 UTC 而不是本地时间显示图形。
背景
我正在为一个项目调查 moment.js 的不同用例,但我对夏令时在秋季结束的问题感到困惑。在问我的问题之前,因为我想说清楚并为有类似问题的其他人提供背景知识,所以让我解释一下我在做什么以及 spring 夏令时的发现。
首先,我正在使用 UTC 时间戳和 America/New_York 时间戳。在美国,2017 年夏令时从 3 月 12 日凌晨 2 点开始(从 2:00:00AM 跳到 3:00:00AM),到 11 月 5 日凌晨 2 点结束(从 2:00:00AM 跳到 1:00:00AM ).因为我也总是知道我需要转换到的目标时区 (America/New_York),所以我不会依赖 moment.js 来检测我的本地时区,而是明确指定我想要的时区。
在spring夏令时生效期间,实行夏令时的时区,如America/New_York,向前跳一个小时。 moment.js 处理得很好。
例如,如果我在 America/New_York 夏令时生效前一秒传递 moment.js UTC 时间戳,它看起来像这样:
moment('2017-03-12T06:59:59Z').tz('America/New_York').format('YYYY/MM/DD hh:mm:ss a z')
上面的输入被认为是 UTC,因为我的时间戳上有 Z
,我明确地将目标时区设置为 .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 time:
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
America/New_York 时区的下一刻,由于开始实行夏令时,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 时间内跳过了一个小时 ("lost"),但 moment 可以检测到并将其转换为 UTC。
总而言之,对于美国的 spring 夏令时更改,我可以将 UTC 时间戳传递到 moment.js 或 moment-timezone 并返回另一个时区的时间戳还应用了正确的夏令时偏移量。然后我还可以将 America/New_York 时间戳传递给 moment 并取回正确转换的 UTC 时间戳。
我的问题
太好了,所以我想在秋季夏令时结束时做同样的事情,当然这不是那么简单。我的假设是,由于夏令时有效地导致一个小时 "repeat",暂时无法知道正确的 UTC 时间。换句话说,夏令时开始时有一个小时的间隔,现在我们有一个小时的重叠。
问题(第 1 部分):有没有办法将相对时间戳和时区传递给 moment 并取回正确的 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 时区显示数据,那么认为我基本上将两个小时的数据点全部塞入一个(看似)单个数据点是否正确2017 年 11 月 5 日从 01:00:00 到 01:59:59 的小时数?
相关主题
关于 SO 的其他一些主题与此相关,但 none 我发现提出或回答了同样的问题。我将link这里的几个供参考:
- Moment.js Convert Local time to UTC time does work
看来你已经想通了问题,做了一些基础研究。谢谢!
您描述的是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
您必须编写自己的检测逻辑,以针对该场景将一个值与下一个值进行比较。另请注意,如果您没有任何时间出现乱序,那么您无法 确信所描述的是哪个事件。在某些情况下,"heartbeat" 信号可以提供帮助。
现在如何在事先不知道偏移量的情况下选择Moment中的第二次出现?像这样:
首先,获取hasAmbiguousWallTime
函数
然后再定义一个函数:
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-timezone 中,但它们应该足以满足您描述的场景。
其他几个小问题:
- 而不是
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 以可视化问题 space。
- 关于您问题的第二部分,是的 - 您最终会将两个小时的数据塞入可能可视化为一小时的数据中 space。人们一直对图形和图表有这个问题。他们绘制了一些在本地时间具有恒定值的图形,然后在 spring 中看到了归零效应,在秋季看到了加倍效应。即使您告诉 moment 使用较晚出现的时间,您也不会避免这种情况,除非您实际以 UTC 而不是本地时间显示图形。