moves/fowler-recurring-events

此包已废弃,不再维护。未建议替代包。

4.0.0-beta.10 2023-08-01 15:57 UTC

README

简介

此库是 Martin Fowler 的循环日历事件算法的实现。更多信息请参见 https://martinfowler.com.cn/apsupp/recurring.pdf

此库分为几个 "时间表达式" 类,每个类都可以用来根据指定的模式判断事件是否发生在特定的目标日期。

包括了几种不同的模式,据我们所知,这些模式是主流日历提供程序(如 Google 日历)中最常见的。

安装

要将此库添加到您的项目中,运行

composer require moves/fowler-recurring-events

可用模式

注意,所有模式都包括一个 frequency 选项,允许您指定模式应用的频率。例如,对于 星期 模式,您可能希望指定每两周发生一次,而不是每周一次。在这种情况下,您可以将 frequency 设置为 2,以指定模式每两周应用一次。

  • 月份中的某天 - 在每个月的特定编号日重复
    • 1 = 每月的第一天
    • 10 = 每月的第十天
    • -1 = 每月的最后一天
    • -2 = 每月的倒数第二天
  • 月份中的星期几 - 在每个月编号周的特定星期几重复
    • (3, 1) = 每月的第一个星期三
    • (1, 2) = 每月的第二个星期一
    • (6, -1) = 每月的最后一个星期六
    • (7, -2) = 每月的倒数第二个星期日
  • 一年中的某天 - 每年特定的日历日期重复
    • (25, 12) = 每年的12月25日
    • (1, 4) = 每年的4月1日
    • (29, 2) = 每个闰年的2月29日,非闰年的3月1日
  • 天数 - 每隔一定天数重复
    • 1 = 每天
    • 2 = 每隔一天
    • 10 = 每隔10天
  • 星期中的某天 - 在星期的特定天数重复
    • [1, 2, 3, 4, 5] = 每个工作日(星期一至星期五)
    • [6, 7] = 每个周末(星期六至星期日)
    • [1, 5] = 每个星期一和星期五

用法

要确定重复事件是否发生在特定的目标日期,首先使用您的重复模式详情实例化相应的时间表达式类,然后调用 includes() 函数。

请注意,所有时间表达式都需要一个模式开始的日期。

例如,对于一个每年12月25日重复的事件

use Moves\FowlerRecurringEvents\TemporalExpressions\TEDayOfYear;

$patternStart = new DateTime('336-12-25'); //First recorded Christmas celebration
$eventPattern = new TEDayOfYear($patternStart, 25, 12);

$targetDate = new DateTime('2021-12-25');

$eventOccursOnTargetDate = $eventPattern->includes($targetDate);
// Expected Result: true

$otherTargetDate = new DateTime('2022-01-01');

$eventOccursOnOtherTargetDate = $eventPattern->includes($otherTargetDate);
// Expected Result: false

频率和模式结束日期

当构建时间表达式时,频率和模式结束日期属性是可选的。要设置值,请使用设置函数来设置这些可选属性。

$eventPattern->setFrequency(2);
$eventPattern->setEndDate(new DateTime('2022-01-01'));

构建者模式

您可以使用构建器模式和setter方法链,以获得更多便利来构建您的临时表达式。

use Moves\FowlerRecurringEvents\TemporalExpressions\TEDaysOfWeek;

$eventStart = new DateTime('2021-01-04');
$eventEnd = new DateTime('2021-12-27');
$eventPattern = TEDaysOfWeek::build($eventStart, 1)
  ->setEndDate($eventEnd)
  ->setFrequency(2);

实际应用和建议

"临时表达式"算法并不打算独立存在。在几乎每一种情况下,您都希望存储一个单个的“主”实例以表示重复的模式,通常是第一次出现,以及重复模式或临时表达式的类型和该特定模式的所需细节。

对于不熟悉日历系统的开发者来说,简单预先计算并存储每个可能的重复日历事件实例可能非常有吸引力。然而,这样做会带来巨大的低效,因为创建、编辑和删除一系列事件的运行时间和数据存储复杂度会随着您打算投射的距离线性增长。此外,使用这种方法,您的预测必须停止在某处,否则您将陷入无限创建实例的困境,这意味着您必须对用户的模式施加一个结束日期。

或者,您可能认为通过存储单个“主”实例来提高数据存储复杂度并规避“结束日期”限制。虽然这是一个正确的思考过程,但这是不熟悉开发者在实践中常常走偏的地方。诱惑往往是简单地每天、每周、每月或每年从起始日期迭代到目标预测日期。当然,这种方法下,运行时间会随着您打算投射的距离线性增长,因此对于生产日历系统来说并不理想。

Fowler方法描述了投影日历事件的O(1)时间算法,这意味着无论您打算投射多远,运行时间始终相同。如果您打算投射在指定时间段内发生的日历事件列表,例如在日历GUI中一次渲染一个月,您可以查询“主”事件列表,然后迭代您想要创建预测的每一天,检查每个主事件在每一天的临时表达式,以动态构建您的列表。

当然,您可以在投射特定时间段的事件列表时应用许多优化来减少需要检查和计算的情况,但这些超出了这个库的范围。