当构建一个日历应用时,我应该在数据库中存储日期还是重复规则?

40

我正在构建一个日历网站(ASP.NET MVC)应用程序(类似于简化版的Outlook),我想开始支持定期发生的日历事件(例如每月、每年等)。

目前,我在我的数据库中存储实际日期,但我想弄清楚,在有重复事件的情况下,是否继续存储日期(超过某些明显的截止日期),或者应该存储重复选项并根据需要生成日期。

这让我想到了Outlook、Google Mail等如何处理这个问题,还有其他支持重复日历项目的服务。

对此有什么建议吗?

7个回答

66

将数据分为两部分:"规范"数据(重复规则)和 "服务" 数据(生成的日期;除再次生成以外只读)。如果规范数据有更改,就在那时重新生成 "服务" 数据。对于无限期循环,保留一些实例并在用完时生成更多(例如,如果用户查看其 2030 年的日历)。

如果您拥有无限处理器速度,您只需要规范数据 - 但实际上,在每个页面视图中进行所有重复规则的日期/时间处理可能太费时间了…因此,您会在存储方面做一些权衡(和复杂性),以节省重复计算。与所需计算量相比,存储通常非常便宜。如果您仅需要存储事件日期,那么这真的非常便宜 - 您可以轻松使用 4 字节整数来表示日期,然后从中生成完整日期/时间,假设您的重复都是基于日期的。对于基于时间的重复(例如“每三小时”),您可以使用完整的 UTC 瞬间-8 字节将其表示到您可能需要的非常好的分辨率。

但是,您需要小心维护有效性-如果定期会议今天有变化,那不会改变它过去曾经发生的时间...因此,您可能还想拥有关于重复实际发生时间的规范只读数据。显然,您不希望一直保留过去,因此根据您的存储限制,您可能需要“垃圾回收”几年前的事件。

您可能还需要按发生情况添加注释和异常信息(例如“由于公共假期,会议今天不会发生”或“已推迟到下午4点”)。当您更改重复设置时,这变得非常有趣 - 如果您将“每周一”更改为“每周二”,您是否保留异常信息?当您从“每天”更改为“每周”时,如何匹配异常信息?这些问题与存储直接无关,但存储决策将影响您决定实施任何政策的难易程度。


3
太棒了,这是我第一次为自己的目的阅读/注意到Jon Skeet的答案 :-) 请原谅我的离题。 - PascalVKooten
当在回答中使用2020作为未来参考时,我已经跨越了这一点。我正在开发一个日历应用程序,这对我非常有帮助! - Tanner Priest

14

您需要分别处理事件和发生情况。

关于事件: 对于事件,您将需要存储重复规则(可以是类似于rfc5545中指定的rrule,也可以是rfc5545中的rdate中的一组明确日期),还需要存储异常情况(参见rfc5545中的exdate以及可能的rfc2445中的exrule)。您还需要跟踪这些规则的更改: 当rdate、exdate发生在未来且过去的日期应该忽略时,更改没有问题。当rrule更改时会更棘手,因为会影响先前的出现情况。我个人倾向于添加一个特定的属性,用于旧的和新的rrule,以指定它们各自的有效开始和结束日期。 如果事件有限定时间段(比如存在COUNT或UNTIL属性),您应该在表格中存储其开始和结束时间,以便更轻松地查询事件(特别是在寻找预先计算时间窗口之外的发生情况时,它可以帮助减少需要重新计算的事件数)。

关于发生情况: 对于发生情况,您应该在预定义的窗口范围内存储实例(例如+/-6个月或12个月,并定期计算),并记录此内容以允许重新计算,如果您的用户想要查看更远的未来(出于性能问题)。您还应该考虑计算索引(RECURRENCE-ID),以便更轻松地找到下一个发生情况。 在后端方面,您还应该跟踪tzid更改,询问用户是否需要更新安排在给定tzid上的事件(如果它是保持当前时区还是需要更新),同样,您可以问一下夏令时期间发生的事件是“永远不会发生”还是“发生两次”的情况(关于此主题的更多信息可在这里)。

注意: 您可能希望在重复规则方面支持超出rfc5545定义的内容,并添加对宗教重复规则的支持(请参见USNO日历介绍或在打印版中,请参见E. Reingol和N. Dershowitz的《日历计算》(第三版))。

既然您询问现有的实现,您可以轻松检查sunbird(sqlite)或Apple开源日历和联系人服务器的数据库模式,存在于caldav服务器的现有开源项目的更完整列表(这可能是您要寻找的子集)在此处可用。


11

我需要构建一个与调度相关的系统,而我们已经完成了。以下是我们所拥有的:

  • 一系列跟踪日程安排的表格。
  • 一张记录日程安排先前实例(发生时间)的表格。
  • 一张记录最后和下一个实例的表格(根据上次发生时间确定下一个项目应该何时发生)。您不需要这张表格,但我们使用它,否则您将不断计算一个项目是否应该现在发生。

对于日程安排来说,情况可能非常棘手,因为您必须记住,在任何时间点,日程安排都可能会改变。此外,某个项目可能会在您的应用程序未运行时到期,当它再次启动时,您需要知道如何识别过期的项目。

另外,我们确保保留跟踪实际日程安排的表格。原因是那些是系统中最复杂的表格集合,我们希望能够重复使用它们,以便用于需要调度的不同事项,例如发送管理电子邮件、发送通知以及服务器维护(如清理日志文件)。


3

我肯定会选择您的第二个选项。使用不同的重复选项,将其单独存储并在运行时计算。存储所有那些日期将是一大堆不必要的数据。

这里有一个很好的答案可以补充您的问题。
用于存储重复事件的数据结构?

另外,顺便说一下。我已经开始将所有内容存储为UTC时间,这样如果您需要使用多个时区,就有了一个共同的基准线。


如果为每个有重复事件的用户存储每次重复事件的单独日期,那么数据量会迅速增加。 - Chase Florell
+1 倾向于存储重复事件。考虑这样一个用例,用户移动重复事件的实例:她可以选择将此移动应用于所有事件、所有后续事件、仅此事件等。 - Fuhrmanator

2
请注意,大多数回复都倾向于保存生成的数据。但请确保考虑到您的使用情况。
过去,我的服务器受限于io,有很多cpu无所事事。现在你有ssd(如果你能负担得起这些,否则你只能用旧的旋转硬盘),但请注意核心数量也增加了。
这种计算的好处在于,你可以轻松地将它们分割并分配给你的许多核心,甚至是几个廉价的服务器在本地网络中。通常比设置nosql集群或走完整个数据库集群的方式更便宜。
另一种选择可能是缓存,只需缓存您的日历视图,当没有改变时就不需要每次都进行所有计算。
但正如前面所说,这取决于您的使用情况。不要只是跟随上面的答案,如果您有时间,请自己进行计算,然后做出决定。

1

我在几年前制作的一个Web应用程序中遇到了类似的问题(现在可能有更好的方法:))。 我想包括一个调度程序,具有所有重复事件的功能,处理时间、天数、周数、月数、年数和异常情况,以便我可以有如下规则:

1)每天上午10点,除了星期三

2)每2小时最多进行4次迭代

3)每个月的第一个星期一

等等。

存储重复日期/时间是可能的,但不灵活。 每个事件的每次迭代都会改变“最大值”。 你要往前看多远?

最后,我编写了一个自定义调度类,可以从字符串中读取和写入。 这是存储在数据库中的字符串,然后可以调用简单的函数来查找下一个发生的时间。


但我需要的不仅仅是下一个出现。 - leora
@ooo,但是一个写得好的日程类可以做到这一点。根据当前日期,它可以计算下一个事件,以及无限期之后的事件。您的when()函数的输入应该是“现在”,它可以返回下一个事件日期。鉴于此,您可以通过将给定日期反馈到when()中或者让该函数返回整个序列来计算尽可能多的事件。 - Moo-Juice

1

你需要确保存储其中一些。用户可能会编辑其中一个事件,而不触及其他事件(你可能在某些日历中遇到过这个问题:“您想编辑所有重复事件还是仅此事件?”例如 Windows Mobile)。

你也可能希望存储过去的事件,并在用户删除重复事件时不将其删除。

存储所有其他事件或生成它们是实现细节。如果可能的话,我更喜欢生成它们。

无论如何,你都需要在每个事件中存储一些重复事件的ID,以及一些标志告诉你该事件是否稍后被修改。或者在更复杂的方法中,为每个事件属性设置一个标志,告诉你它是默认值(来自重复事件)还是为此特定实例修改的。当用户决定编辑重复事件时,你将需要这个。


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