使用moment.js防止DST转换为重复发生的事件

时间:2016-05-17 18:36:10

标签: javascript node.js momentjs

我正在构建的系统有一个事件组件,其中一部分是创建重复事件的能力。在我的数据库中,我将所有事件存储在UTC中。当用户日历上显示定期事件时,应始终以“待机时间”显示。例如,如果我每周三下午1:00创建一个定期事件,无论夏令时如何,都应该在下午1:00。

我遇到的问题是,每当我尝试使用Moment.js格式化此日期时,Moment总是会考虑DST转换并相应地更新事件。以我在前面的例子为例,在2016年,DST转变发生在3月13日,所以如果我的预约在2016年2月被预订,那么每次预约到3月13日都会正确输出为下午1:00。 3月13日之后,DST转换已经应用,我现在所有的活动都提前了一小时。

有没有推荐的方法来处理这个问题?我似乎找不到让Moment“忽略”DST的方法,也没有在搜索时找到合适的解决方案。

不确定这有多重要,但我在输出日期时使用Moment Timezone转换为用户的本地时区。

谢谢!

2 个答案:

答案 0 :(得分:5)

作为最佳做法,未来日期应存储在当地时间。这是因为,正如评论中所指出的,时区规则可以而且确实会发生变化。因此,您无法提前正确计算当地时间的正确UTC日期。

当您存储未来日期时,您应该将其存储在用户期望事件时间所在的IANA时区。然后您实际上会执行与您现在正在进行的转换相反的操作。如果您需要了解事件在全球时间轴上的确切位置,您可以从当地时间转换为UTC。

请注意,IANA时区数据库经常更新。它现在是2016d版本,这意味着它今年已经更新了四次。如果您有一个将在多个国家/地区使用的应用程序,您需要非常勤奋地不断更新时刻时区以跟上变化。

至于为什么您现在看到您的UTC日期没有正确更改 - 我认为您正在计划您的预定日期,转换为UTC,然后计算您的重复发生的时间。当您使用UTC进行日期数学运算时,它将无法正确解释DST的变化,因为它不了解它们。因此,当时刻将该日期转换回本地时,它将关闭一小时。如果您要在当地时间进行日期数学运算,然后转换为UTC进行存储,那么您的转换将是正确的,并且提示时区规则没有更改。不要这样做 - 将日期存储在当地时间。

答案 1 :(得分:0)

替代(部分)解决方案

有另一种解决方案,可以解决DST移位问题。 注意: 当将来某个时区的时区偏移量发生变化时,这将无法正常工作!它不使用moment.js,但我认为它可能仍会有所帮助某人。

说明

窍门是在创建实体时通过使用当前UTC到本地时间的偏移来调整UTC日期,然后存储更新的UTC日期。

假设本地时区是'Europe/Berlin',我们正在创建一个从'2020-03-28 10:00:00Z'(UTC)开始的周期性事件。这是我们的参考日期。此时时区的偏移量为+01:00,因此用户的意图是让事件从11:00开始。

现在将重复此事件,直到'2020-03-29'(该时区DST偏移后的第二天,该时区的偏移量为+02:00)。因此,如果我们不进行任何更改,则该事件将在当天的12:00上显示(而不是用户期望的11:00)。

因此,如果我们将日期存储为当天的'2020-03-29 09:00:00Z'(UTC),则会在当地时区的11:00正确显示该日期。

代码

这有点古怪,可以肯定地改善了我们应用时区偏移的部分,但是对于我们的用例来说已经足够了。它在Node.js环境中使用date-fnsdate-fns-timezone

  • referenceDate:用作要应用的偏移量的参考的UTC日期(例如,系列的第一个日期;在上面的示例中,'2020-03-28 10:00:00Z'
  • date:当前重复的UTC日期;以上示例中的'2020-03-29 10:00:00Z'
  • timeZone:本地时区的IANA time zone name;以上示例中的'Europe/Berlin'
  • adjustedUTCDate:调整后的UTC日期

转换代码:

// offset == '+01:00' in the example
let offset = dateFnsTimezone.formatToTimeZone(referenceDate, 'Z', { timeZone });
if (offset[0] === '+') {
    offset = offset.replace('+', '-');
} else {
    offset = offset.replace('-', '+');
}

const dateString = dateFns.format(date, 'YYYY-MM-DDTHH:mm:ss[Z]');

// applies the offset to change the time (intended local time); '2020-03-29 10:00:00+01:00' in the example
const localDate = dateString.replace('Z', offset);

// convert the date back to UTC, from the intended local time; '2020-03-29 09:00:00' in the example
const adjustedUTCDate = dateFnsTimezone.parseFromTimeZone(localDate, { timeZone });