我正在构建的系统有一个事件组件,其中一部分是创建重复事件的能力。在我的数据库中,我将所有事件存储在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转换为用户的本地时区。
谢谢!
答案 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-fns和date-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 });