daily($anchor, $reference, $inclusive); case 'weekly': return $this->weekly($anchor, $reference, $inclusive); case 'monthly_day': return $this->monthlyByDay($anchor, $lastDay, $reference, $inclusive); case 'monthly_dow': return $this->monthlyByWeekday($anchor, $lastDay, $reference, $inclusive); case 'yearly': return $this->yearly($anchor, $lastDay, $reference, $inclusive); default: return null; } } private function daily($anchor, $reference, $inclusive) { $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); $d = (int) date('j', $reference); for ($k = 0; $k < 3; $k++) { $o = mktime($h, $i, $s, $m, $d + $k, $y); if ($this->matches($o, $reference, $inclusive)) { return $o; } } return null; } private function weekly($anchor, $reference, $inclusive) { $h = (int) date('G', $anchor); $i = (int) date('i', $anchor); $s = (int) date('s', $anchor); $w = (int) date('w', $anchor); $y = (int) date('Y', $reference); $m = (int) date('n', $reference); $d = (int) date('j', $reference); for ($k = 0; $k < 8; $k++) { $o = mktime($h, $i, $s, $m, $d + $k, $y); if ((int) date('w', $o) === $w && $this->matches($o, $reference, $inclusive)) { return $o; } } 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); $o = mktime($h, $i, $s, $m, $day, $y); if ($this->matches($o, $reference, $inclusive)) { return $o; } $this->nextMonth($m, $y); } return null; } private function monthlyByWeekday($anchor, $lastDay, $reference, $inclusive) { $h = (int) date('G', $anchor); $i = (int) date('i', $anchor); $s = (int) date('s', $anchor); $w = (int) date('w', $anchor); $k = intdiv((int) date('j', $anchor) - 1, 7) + 1; // 1..5: which occurrence of $w in the month $y = (int) date('Y', $reference); $m = (int) date('n', $reference); for ($j = 0; $j < self::MAX_MONTHS; $j++) { $day = $lastDay ? $this->lastWeekdayOfMonth($y, $m, $w) : $this->nthWeekdayOfMonth($y, $m, $w, $k); if ($day !== null) { $o = mktime($h, $i, $s, $m, $day, $y); if ($this->matches($o, $reference, $inclusive)) { return $o; } } $this->nextMonth($m, $y); } return null; } private function yearly($anchor, $lastDay, $reference, $inclusive) { $month = (int) date('n', $anchor); $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); for ($j = 0; $j < self::MAX_YEARS; $j++) { $lastDom = (int) date('t', mktime(0, 0, 0, $month, 1, $y)); $day = $lastDay ? $lastDom : min($anchorDay, $lastDom); $o = mktime($h, $i, $s, $month, $day, $y); if ($this->matches($o, $reference, $inclusive)) { return $o; } $y++; } return null; } // The day-of-month of the k-th weekday $w in month $m/$y, or null if that month has no k-th one. private function nthWeekdayOfMonth($y, $m, $w, $k) { $firstDow = (int) date('w', mktime(0, 0, 0, $m, 1, $y)); $firstW = 1 + (($w - $firstDow + 7) % 7); $day = $firstW + ($k - 1) * 7; $lastDom = (int) date('t', mktime(0, 0, 0, $m, 1, $y)); return $day <= $lastDom ? $day : null; } // The day-of-month of the last weekday $w in month $m/$y. private function lastWeekdayOfMonth($y, $m, $w) { $lastDom = (int) date('t', mktime(0, 0, 0, $m, 1, $y)); $lastDow = (int) date('w', mktime(0, 0, 0, $m, $lastDom, $y)); return $lastDom - (($lastDow - $w + 7) % 7); } private function matches($occurrence, $reference, $inclusive) { return $inclusive ? $occurrence >= $reference : $occurrence > $reference; } private function nextMonth(&$m, &$y) { $m++; if ($m > 12) { $m = 1; $y++; } } }