71 lines
2.3 KiB
PHP
71 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace Kanboard\Plugin\RecoReco\Model;
|
|
|
|
/**
|
|
* Pure calendar math for RecoReco.
|
|
*
|
|
* Given an anchor due date (the drift-free pattern: day-of-month + time), a frequency, and the
|
|
* "last day" flag, it computes occurrence datetimes. It has NO container dependencies, so it is
|
|
* unit-testable in isolation (see Test/OccurrenceCalculatorTest.php).
|
|
*
|
|
* Occurrences are always computed from the anchor's day, never by marching a clamped date forward,
|
|
* so a "30th" schedule goes 30, 28/29, 30, 30... and never drifts to the 28th.
|
|
*
|
|
* v0.3 implements monthly-by-day; the other frequencies arrive in v1.1 (occurrenceFrom returns null
|
|
* for them until then).
|
|
*/
|
|
class OccurrenceCalculator
|
|
{
|
|
const MAX_MONTHS = 120;
|
|
|
|
/**
|
|
* The earliest occurrence at/after $reference (when $inclusive) or strictly after it.
|
|
*
|
|
* @param int $anchor Unix timestamp: the pattern (day-of-month + time).
|
|
* @param string $frequency
|
|
* @param bool $lastDay
|
|
* @param int $reference Unix timestamp.
|
|
* @param bool $inclusive
|
|
* @return int|null Occurrence timestamp, or null if the frequency is not yet supported.
|
|
*/
|
|
public function occurrenceFrom($anchor, $frequency, $lastDay, $reference, $inclusive = false)
|
|
{
|
|
switch ($frequency) {
|
|
case 'monthly_day':
|
|
return $this->monthlyByDay((int) $anchor, (bool) $lastDay, (int) $reference, (bool) $inclusive);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function monthlyByDay($anchor, $lastDay, $reference, $inclusive)
|
|
{
|
|
$anchorDay = (int) date('j', $anchor);
|
|
$h = (int) date('G', $anchor);
|
|
$i = (int) date('i', $anchor);
|
|
$s = (int) date('s', $anchor);
|
|
|
|
$y = (int) date('Y', $reference);
|
|
$m = (int) date('n', $reference);
|
|
|
|
for ($k = 0; $k < self::MAX_MONTHS; $k++) {
|
|
$lastDom = (int) date('t', mktime(0, 0, 0, $m, 1, $y));
|
|
$day = $lastDay ? $lastDom : min($anchorDay, $lastDom);
|
|
$occurrence = mktime($h, $i, $s, $m, $day, $y);
|
|
|
|
if ($inclusive ? $occurrence >= $reference : $occurrence > $reference) {
|
|
return $occurrence;
|
|
}
|
|
|
|
$m++;
|
|
if ($m > 12) {
|
|
$m = 1;
|
|
$y++;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|