如何存储重复日期并考虑夏令时问题

48
我正在将事件存储在我的数据库中。我有“开始”和“结束”日期时间,“tickets_start”和“tickets_end”(用于票务销售实际开始/结束的时间-而不是活动的开始/结束时间)。
到目前为止,我已经构建了一些方法,可以在保存之前将日期/时间转换为GMT,并在显示时转换回它们各自的时区。
我使用varchar字段存储时区,其中值类似于“America / New_York”。
但是-现在我需要开始处理用户是否允许重复事件的情况。我以前做过这件事,这并不是什么大问题,但从未跨越多个时区。
起初,我认为这不是什么大问题,但后来意识到-如果最初的开始日期是7月(例如),并且在接下来的一年中每个月都重复一次,那么在某个时候,夏令时会导致从GMT转换时间的方式不同。一个月,当转换12:00时,它会将其更改为-5,而下一个月,由于DST,它会将其更改为-4。
我目前的想法是,我将存储一个'dst' tinyint(1)表示开始/结束日期是否在DST期间输入,并制作一个方法以在必要时按小时更改时间。
但是-我认为我应该在这里问一下,希望有一种“正常”的方法或我没有考虑到的简单方法。(cakephp 2.4.x)

可能是夏令时和时区最佳实践的重复问题。 - vascowhite
1个回答

144
首先,请注意,现代术语中应该使用UTC而不是GMT。它们大多数情况下是等效的,但UTC更加精确地定义了时间。保留GMT一词以指代英国在冬季月份生效的UTC+0偏移量的时区部分。
现在回到你的问题。UTC不一定是存储所有日期和时间值的最佳方式。它特别适用于过去的事件或未来的绝对事件,但对于未来的本地事件,特别是未来的重复事件,效果并不是很好。
我最近在另一个答案中写到了这个问题,并且这是少数几个例外情况之一,其中本地时间比UTC更有意义。主要的争论是“闹钟问题”。如果您按UTC设置闹钟,则在夏令时转换日当天,您将早或晚一个小时醒来。这就是为什么大多数人按当地时间设置闹钟的原因。
当然,如果您正在处理来自世界各地的数据,您不能存储本地时间。您应该存储一些不同的东西:
  • 重复事件的当地时间,例如“08:00”
  • 当地时间所表示的时区,例如“America/New_York”
  • 重复模式,以适合您的应用程序的任何格式表示,例如每天,每两周或每月第三个星期四等。
  • 最佳预测的下一个即时UTC日期和时间等效值。
  • 也许(但不总是)是未来一段预定义时间内(可能是一周,也可能是6个月,也可能是一两年,具体取决于您的需求)的活动UTC日期和时间列表。

对于最后两个问题,请理解任何本地日期/时间的UTC等效值都可能会更改,如果负责该时区的政府决定更改任何内容。由于每年有多次时区数据库更新,因此您将需要制定计划定期订阅更新公告更新您的时区数据库。每当您更新时区数据时,都需要重新计算所有未来事件的UTC等效时间。

若计划显示跨越多个时区的事件列表,则具有UTC等效项非常重要。 这些是您将查询以构建该列表的值。 另一个要考虑的问题是,如果某个事件安排在夏令时回退期间发生的当地时间,您必须决定事件是否发生在第一次实例(通常),第二次实例(有时)或两者都发生(很少),并构建应用程序机制确保除非您希望如此,否则事件不会触发两次。 如果您正在寻找简单的答案-抱歉,但没有。 在跨越时区安排未来事件是一项复杂的任务。
另一种方法是,将本地时间中的开始日期转换为UTC进行存储,并存储时区ID。 然后,在运行时,他们应用时区将原始UTC时间转换回本地时间,然后使用该本地时间计算其他重复事件,就好像它是上面存储的那个。 虽然这种技术有效,但缺点是:
如果有一个时区更新,改变了本地时间,那么在第一次运行之前,整个计划都会被打乱。可以通过选择过去的时间作为“第一个”实例来缓解这种情况,这样第二个实例就是真正的第一个实例。
如果时间确实是“浮动时间”,应该跟随用户(例如在手机上的闹钟),即使这不是您想要运行的区域,仍然必须存储它最初创建的区域的时区信息。
这增加了额外的复杂性,没有任何好处。我建议将此技术保留用于需要将UTC-only调度程序改装为支持时区的情况。

有没有好的方法来处理检查日期是否过期,或其他类似的简单“检查”?以前,如果将所有日期存储在UTC中,那很简单,因为我可以根据服务器时间进行检查。但现在,似乎需要额外的步骤...有什么建议吗? - Dave
1
如果您存储下一个即将到来的UTC时间,那么您可以使用它来查询事件是即将发生还是已经过去。如果已经过去,那么您可能想要在另一个表中编写过去事件的新记录,然后更新您的UTC时间以便于下一次重复实例。 - Matt Johnson-Pint
您可以将重复事件安排为本地时间,并将每个出现的过去实例转换为UTC时间。但是在您的领域中,请尝试将它们视为独立的实体。 - Matt Johnson-Pint
2
在这五个项目中,最后两个总是可以从前三个计算出来,因此如果你决定存储它们,我会将它们视为“缓存”值。 - musiphil
1
这是一篇非常信息丰富且结构良好的帖子。谢谢@MattJohnson-Pint。 - Deep
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接