public function testLoadFromString() { $string = 'FREQ=YEARLY;'; $string .= 'COUNT=2;'; $string .= 'INTERVAL=2;'; $string .= 'BYSECOND=30;'; $string .= 'BYMINUTE=10;'; $string .= 'BYHOUR=5,15;'; $string .= 'BYDAY=SU,WE;'; $string .= 'BYMONTHDAY=16,22;'; $string .= 'BYYEARDAY=201,203;'; $string .= 'BYWEEKNO=29,32;'; $string .= 'BYMONTH=7,8;'; $string .= 'BYSETPOS=1,3;'; $string .= 'WKST=TU;'; $string .= 'EXDATE=20140607,20140620T010000,20140620T040000Z;'; $this->rule->loadFromString($string); $this->assertEquals(Frequency::YEARLY, $this->rule->getFreq()); $this->assertEquals(2, $this->rule->getCount()); $this->assertEquals(2, $this->rule->getInterval()); $this->assertEquals(array(30), $this->rule->getBySecond()); $this->assertEquals(array(10), $this->rule->getByMinute()); $this->assertEquals(array(5, 15), $this->rule->getByHour()); $this->assertEquals(array('SU', 'WE'), $this->rule->getByDay()); $this->assertEquals(array(16, 22), $this->rule->getByMonthDay()); $this->assertEquals(array(201, 203), $this->rule->getByYearDay()); $this->assertEquals(array(29, 32), $this->rule->getByWeekNumber()); $this->assertEquals(array(7, 8), $this->rule->getByMonth()); $this->assertEquals(array(1, 3), $this->rule->getBySetPosition()); $this->assertEquals('TU', $this->rule->getWeekStart()); $this->assertEquals(array(new DateExclusion(new \DateTime(20140607), false), new DateExclusion(new \DateTime('20140620T010000'), true), new DateExclusion(new \DateTime('20140620 04:00:00 UTC'), true, true)), $this->rule->getExDates()); }
public function testLoadFromArray() { $this->rule->loadFromArray(array('FREQ' => 'YEARLY', 'COUNT' => '2', 'INTERVAL' => '2', 'BYSECOND' => '30', 'BYMINUTE' => '10', 'BYHOUR' => '5,15', 'BYDAY' => 'SU,WE', 'BYMONTHDAY' => '16,22', 'BYYEARDAY' => '201,203', 'BYWEEKNO' => '29,32', 'BYMONTH' => '7,8', 'BYSETPOS' => '1,3', 'WKST' => 'TU', 'EXDATE' => '20140607,20140620T010000,20140620T160000Z')); $this->assertEquals(Frequency::YEARLY, $this->rule->getFreq()); $this->assertEquals(2, $this->rule->getCount()); $this->assertEquals(2, $this->rule->getInterval()); $this->assertEquals(array(30), $this->rule->getBySecond()); $this->assertEquals(array(10), $this->rule->getByMinute()); $this->assertEquals(array(5, 15), $this->rule->getByHour()); $this->assertEquals(array('SU', 'WE'), $this->rule->getByDay()); $this->assertEquals(array(16, 22), $this->rule->getByMonthDay()); $this->assertEquals(array(201, 203), $this->rule->getByYearDay()); $this->assertEquals(array(29, 32), $this->rule->getByWeekNumber()); $this->assertEquals(array(7, 8), $this->rule->getByMonth()); $this->assertEquals(array(1, 3), $this->rule->getBySetPosition()); $this->assertEquals('TU', $this->rule->getWeekStart()); $this->assertEquals(array(new DateExclusion(new \DateTime(20140607), false), new DateExclusion(new \DateTime('20140620T010000'), true), new DateExclusion(new \DateTime('20140620 16:00:00 UTC'), true, true)), $this->rule->getExDates()); }
protected function isFullyConvertible(Rule $rule) { if ($rule->getFreq() >= 4) { return false; } $until = $rule->getUntil(); $count = $rule->getCount(); if (!empty($until) && !empty($count)) { return false; } $bySecond = $rule->getBySecond(); $byMinute = $rule->getByMinute(); $byHour = $rule->getByHour(); if (!empty($bySecond) || !empty($byMinute) || !empty($byHour)) { return false; } $byWeekNum = $rule->getByWeekNumber(); $byYearDay = $rule->getByYearDay(); if ($rule->getFreq() != 0 && (!empty($byWeekNum) || !empty($byYearDay))) { return false; } return true; }
/** * @param Rule $rule * @param \DateTime $dt * * @return array */ public static function getTimeSet(Rule $rule, \DateTime $dt) { $set = array(); if (null === $rule || $rule->getFreq() >= Frequency::HOURLY) { return $set; } $byHour = $rule->getByHour(); $byMinute = $rule->getByMinute(); $bySecond = $rule->getBySecond(); if (empty($byHour)) { $byHour = array($dt->format('G')); } if (empty($byMinute)) { $byMinute = array($dt->format('i')); } if (empty($bySecond)) { $bySecond = array($dt->format('s')); } foreach ($byHour as $hour) { foreach ($byMinute as $minute) { foreach ($bySecond as $second) { $set[] = new Time($hour, $minute, $second); } } } return $set; }
/** * Transform a Rule in to an array of \DateTimes * * @param Rule $rule the Rule * @param int|null $virtualLimit imposed upon infinitely recurring events. * @param ConstraintInterface|null $constraint Potential recurrences must pass the constraint, else * they will not be included in the returned collection. * * @return RecurrenceCollection * @throws MissingData */ public function transform($rule, $virtualLimit = null, ConstraintInterface $constraint = null) { if (null === $rule) { throw new MissingData('Rule has not been set'); } $start = $rule->getStartDate(); $end = $rule->getEndDate(); $until = $rule->getUntil(); if (null === $start) { $start = new \DateTime('now', $until instanceof \DateTime ? $until->getTimezone() : null); } if (null === $end) { $end = $start; } $durationInterval = $start->diff($end); $startDay = $start->format('j'); $startMonthLength = $start->format('t'); $fixLastDayOfMonth = false; $dt = clone $start; $maxCount = $rule->getCount(); $vLimit = !empty($virtualLimit) && is_int($virtualLimit) ? $virtualLimit : $this->getVirtualLimit(); $freq = $rule->getFreq(); $weekStart = $rule->getWeekStartAsNum(); $bySecond = $rule->getBySecond(); $byMinute = $rule->getByMinute(); $byHour = $rule->getByHour(); $byMonth = $rule->getByMonth(); $byWeekNum = $rule->getByWeekNumber(); $byYearDay = $rule->getByYearDay(); $byMonthDay = $rule->getByMonthDay(); $byMonthDayNeg = array(); $byWeekDay = $rule->getByDayTransformedToWeekdays(); $byWeekDayRel = array(); $bySetPos = $rule->getBySetPosition(); $implicitByMonthDay = false; if (!(!empty($byWeekNum) || !empty($byYearDay) || !empty($byMonthDay) || !empty($byWeekDay))) { switch ($freq) { case Frequency::YEARLY: if (empty($byMonth)) { $byMonth = array($start->format('n')); } if ($startDay > 28) { $fixLastDayOfMonth = true; } $implicitByMonthDay = true; $byMonthDay = array($startDay); break; case Frequency::MONTHLY: if ($startDay > 28) { $fixLastDayOfMonth = true; } $implicitByMonthDay = true; $byMonthDay = array($startDay); break; case Frequency::WEEKLY: $byWeekDay = array(new Weekday(DateUtil::getDayOfWeek($start), null)); break; } } if (!$this->config->isLastDayOfMonthFixEnabled()) { $fixLastDayOfMonth = false; } if (is_array($byMonthDay) && count($byMonthDay)) { foreach ($byMonthDay as $idx => $day) { if ($day < 0) { unset($byMonthDay[$idx]); $byMonthDayNeg[] = $day; } } } if (!empty($byWeekDay)) { foreach ($byWeekDay as $idx => $day) { /** @var $day Weekday */ if (!empty($day->num)) { $byWeekDayRel[] = $day; unset($byWeekDay[$idx]); } else { $byWeekDay[$idx] = $day->weekday; } } } if (empty($byYearDay)) { $byYearDay = null; } if (empty($byMonthDay)) { $byMonthDay = null; } if (empty($byMonthDayNeg)) { $byMonthDayNeg = null; } if (empty($byWeekDay)) { $byWeekDay = null; } if (!count($byWeekDayRel)) { $byWeekDayRel = null; } $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); $hour = $dt->format('G'); $minute = $dt->format('i'); $second = $dt->format('s'); $dates = array(); $total = 1; $count = $maxCount; $continue = true; while ($continue) { $dtInfo = DateUtil::getDateInfo($dt); $tmp = DateUtil::getDaySet($rule, $dt, $dtInfo, $start); $daySet = $tmp->set; $daySetStart = $tmp->start; $daySetEnd = $tmp->end; $wNoMask = array(); $wDayMaskRel = array(); $timeSet = DateUtil::getTimeSet($rule, $dt); if ($freq >= Frequency::HOURLY) { if ($freq >= Frequency::HOURLY && !empty($byHour) && !in_array($hour, $byHour) || $freq >= Frequency::MINUTELY && !empty($byMinute) && !in_array($minute, $byMinute) || $freq >= Frequency::SECONDLY && !empty($bySecond) && !in_array($second, $bySecond)) { $timeSet = array(); } else { switch ($freq) { case Frequency::HOURLY: $timeSet = DateUtil::getTimeSetOfHour($rule, $dt); break; case Frequency::MINUTELY: $timeSet = DateUtil::getTimeSetOfMinute($rule, $dt); break; case Frequency::SECONDLY: $timeSet = DateUtil::getTimeSetOfSecond($dt); break; } } } // Handle byWeekNum if (!empty($byWeekNum)) { $no1WeekStart = $firstWeekStart = DateUtil::pymod(7 - $dtInfo->dayOfWeekYearDay1 + $weekStart, 7); if ($no1WeekStart >= 4) { $no1WeekStart = 0; $wYearLength = $dtInfo->yearLength + DateUtil::pymod($dtInfo->dayOfWeekYearDay1 - $weekStart, 7); } else { $wYearLength = $dtInfo->yearLength - $no1WeekStart; } $div = floor($wYearLength / 7); $mod = DateUtil::pymod($wYearLength, 7); $numWeeks = floor($div + $mod / 4); foreach ($byWeekNum as $weekNum) { if ($weekNum < 0) { $weekNum += $numWeeks + 1; } if (!(0 < $weekNum && $weekNum <= $numWeeks)) { continue; } if ($weekNum > 1) { $offset = $no1WeekStart + ($weekNum - 1) * 7; if ($no1WeekStart != $firstWeekStart) { $offset -= 7 - $firstWeekStart; } } else { $offset = $no1WeekStart; } for ($i = 0; $i < 7; $i++) { $wNoMask[] = $offset; $offset++; if ($dtInfo->wDayMask[$offset] == $weekStart) { break; } } } // Check week number 1 of next year as well if (in_array(1, $byWeekNum)) { $offset = $no1WeekStart + $numWeeks * 7; if ($no1WeekStart != $firstWeekStart) { $offset -= 7 - $firstWeekStart; } // If week starts in next year, we don't care about it. if ($offset < $dtInfo->yearLength) { for ($k = 0; $k < 7; $k++) { $wNoMask[] = $offset; $offset += 1; if ($dtInfo->wDayMask[$offset] == $weekStart) { break; } } } } if ($no1WeekStart) { // Check last week number of last year as well. // If $no1WeekStart is 0, either the year started on week start, // or week number 1 got days from last year, so there are no // days from last year's last week number in this year. if (!in_array(-1, $byWeekNum)) { $dtTmp = new \DateTime(); $dtTmp->setDate($year - 1, 1, 1); $lastYearWeekDay = DateUtil::getDayOfWeek($dtTmp); $lastYearNo1WeekStart = DateUtil::pymod(7 - $lastYearWeekDay + $weekStart, 7); $lastYearLength = DateUtil::getYearLength($dtTmp); if ($lastYearNo1WeekStart >= 4) { $lastYearNo1WeekStart = 0; $lastYearNumWeeks = floor(52 + DateUtil::pymod($lastYearLength + DateUtil::pymod($lastYearWeekDay - $weekStart, 7), 7) / 4); } else { $lastYearNumWeeks = floor(52 + DateUtil::pymod($dtInfo->yearLength - $no1WeekStart, 7) / 4); } } else { $lastYearNumWeeks = -1; } if (in_array($lastYearNumWeeks, $byWeekNum)) { for ($i = 0; $i < $no1WeekStart; $i++) { $wNoMask[] = $i; } } } } // Handle relative weekdays (e.g. 3rd Friday of month) if (!empty($byWeekDayRel)) { $ranges = array(); if (Frequency::YEARLY == $freq) { if (!empty($byMonth)) { foreach ($byMonth as $mo) { $ranges[] = array_slice($dtInfo->mRanges, $mo - 1, 2); } } else { $ranges[] = array(0, $dtInfo->yearLength); } } elseif (Frequency::MONTHLY == $freq) { $ranges[] = array_slice($dtInfo->mRanges, $month - 1, 2); } if (!empty($ranges)) { foreach ($ranges as $range) { $rangeStart = $range[0]; $rangeEnd = $range[1]; --$rangeEnd; reset($byWeekDayRel); foreach ($byWeekDayRel as $weekday) { /** @var Weekday $weekday */ if ($weekday->num < 0) { $i = $rangeEnd + ($weekday->num + 1) * 7; $i -= DateUtil::pymod($dtInfo->wDayMask[$i] - $weekday->weekday, 7); } else { $i = $rangeStart + ($weekday->num - 1) * 7; $i += DateUtil::pymod(7 - $dtInfo->wDayMask[$i] + $weekday->weekday, 7); } if ($rangeStart <= $i && $i <= $rangeEnd) { $wDayMaskRel[] = $i; } } } } } $numMatched = 0; foreach ($daySet as $i => $dayOfYear) { $dayOfMonth = $dtInfo->mDayMask[$dayOfYear]; $ifByMonth = $byMonth !== null && !in_array($dtInfo->mMask[$dayOfYear], $byMonth); $ifByWeekNum = $byWeekNum !== null && !in_array($i, $wNoMask); $ifByYearDay = $byYearDay !== null && ($i < $dtInfo->yearLength && !in_array($i + 1, $byYearDay) && !in_array(-$dtInfo->yearLength + $i, $byYearDay) || $i >= $dtInfo->yearLength && !in_array($i + 1 - $dtInfo->yearLength, $byYearDay) && !in_array(-$dtInfo->nextYearLength + $i - $dtInfo->yearLength, $byYearDay)); $ifByMonthDay = $byMonthDay !== null && !in_array($dtInfo->mDayMask[$dayOfYear], $byMonthDay); // Handle "last day of next month" problem. if ($fixLastDayOfMonth && $ifByMonthDay && $implicitByMonthDay && $startMonthLength > $dtInfo->monthLength && $dayOfMonth == $dtInfo->monthLength && $dayOfMonth < $startMonthLength && !$numMatched) { $ifByMonthDay = false; } $ifByMonthDayNeg = $byMonthDayNeg !== null && !in_array($dtInfo->mDayMaskNeg[$dayOfYear], $byMonthDayNeg); $ifByDay = $byWeekDay !== null && count($byWeekDay) && !in_array($dtInfo->wDayMask[$dayOfYear], $byWeekDay); $ifWDayMaskRel = $byWeekDayRel !== null && !in_array($dayOfYear, $wDayMaskRel); if ($byMonthDay !== null && $byMonthDayNeg !== null) { if ($ifByMonthDay && $ifByMonthDayNeg) { unset($daySet[$i]); } } elseif ($ifByMonth || $ifByWeekNum || $ifByYearDay || $ifByMonthDay || $ifByMonthDayNeg || $ifByDay || $ifWDayMaskRel) { unset($daySet[$i]); } else { ++$numMatched; } } if (!empty($bySetPos)) { $datesAdj = array(); $tmpDaySet = array_combine($daySet, $daySet); foreach ($bySetPos as $setPos) { if ($setPos < 0) { $dayPos = floor($setPos / count($timeSet)); $timePos = DateUtil::pymod($setPos, count($timeSet)); } else { $dayPos = floor(($setPos - 1) / count($timeSet)); $timePos = DateUtil::pymod($setPos - 1, count($timeSet)); } $tmp = array(); for ($k = $daySetStart; $k <= $daySetEnd; $k++) { if (!array_key_exists($k, $tmpDaySet)) { continue; } $tmp[] = $tmpDaySet[$k]; } if ($dayPos < 0) { $nextInSet = array_slice($tmp, $dayPos, 1); $nextInSet = $nextInSet[0]; } else { $nextInSet = $tmp[$dayPos]; } /** @var Time $time */ $time = $timeSet[$timePos]; $dtTmp = DateUtil::getDateTimeByDayOfYear($nextInSet, $dt->format('Y'), $start->getTimezone()); $dtTmp->setTime($time->hour, $time->minute, $time->second); $datesAdj[] = $dtTmp; } foreach ($datesAdj as $dtTmp) { if (null !== $until && $dtTmp > $until) { $continue = false; break; } if ($dtTmp < $start) { continue; } if ($constraint instanceof ConstraintInterface && !$constraint->test($dtTmp)) { if ($constraint->stopsTransformer()) { $continue = false; break; } else { continue; } } $dates[] = $dtTmp; if (null !== $count) { --$count; if ($count <= 0) { $continue = false; break; } } ++$total; if ($total > $vLimit) { $continue = false; break; } } } else { foreach ($daySet as $dayOfYear) { $dtTmp = DateUtil::getDateTimeByDayOfYear($dayOfYear, $dt->format('Y'), $start->getTimezone()); foreach ($timeSet as $time) { /** @var Time $time */ $dtTmp->setTime($time->hour, $time->minute, $time->second); if (null !== $until && $dtTmp > $until) { $continue = false; break; } if ($dtTmp < $start) { continue; } if ($constraint instanceof ConstraintInterface && !$constraint->test($dtTmp)) { if ($constraint->stopsTransformer()) { $continue = false; break; } else { continue; } } $dates[] = clone $dtTmp; if (null !== $count) { --$count; if ($count <= 0) { $continue = false; break; } } ++$total; if ($total > $vLimit) { $continue = false; break; } } if (!$continue) { break; } } if ($total > $vLimit) { $continue = false; break; } } switch ($freq) { case Frequency::YEARLY: $year += $rule->getInterval(); $month = $dt->format('n'); $day = $dt->format('j'); $dt->setDate($year, $month, 1); break; case Frequency::MONTHLY: $month += $rule->getInterval(); if ($month > 12) { $delta = floor($month / 12); $mod = DateUtil::pymod($month, 12); $month = $mod; $year += $delta; if ($month == 0) { $month = 12; --$year; } } $dt->setDate($year, $month, 1); break; case Frequency::WEEKLY: if ($weekStart > $dtInfo->dayOfWeek) { $delta = ($dtInfo->dayOfWeek + 1 + (6 - $weekStart)) * -1 + $rule->getInterval() * 7; } else { $delta = ($dtInfo->dayOfWeek - $weekStart) * -1 + $rule->getInterval() * 7; } $dt->modify("+{$delta} day"); $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); break; case Frequency::DAILY: $dt->modify('+' . $rule->getInterval() . ' day'); $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); break; case Frequency::HOURLY: $dt->modify('+' . $rule->getInterval() . ' hours'); $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); $hour = $dt->format('G'); break; case Frequency::MINUTELY: $dt->modify('+' . $rule->getInterval() . ' minutes'); $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); $hour = $dt->format('G'); $minute = $dt->format('i'); break; case Frequency::SECONDLY: $dt->modify('+' . $rule->getInterval() . ' seconds'); $year = $dt->format('Y'); $month = $dt->format('n'); $day = $dt->format('j'); $hour = $dt->format('G'); $minute = $dt->format('i'); $second = $dt->format('s'); break; } } /** @var Recurrence[] $recurrences */ $recurrences = array(); foreach ($dates as $start) { /** @var \DateTime $end */ $end = clone $start; $recurrences[] = new Recurrence($start, $end->add($durationInterval)); } $recurrences = $this->handleExclusions($rule->getExDates(), $recurrences); return new RecurrenceCollection($recurrences); }