public function testIsValid() { // test invalid by normal validation actions $rrule = new Calendar_Model_Rrule(array('interval' => 'NotAnInt'), true); $rrule->bypassFilters = false; $this->assertFalse($rrule->isValid(false)); // count and until are not allowed together $rrule = new Calendar_Model_Rrule(array('count' => '10', 'until' => '2012-01-09 09:33:00'), true); $this->assertFalse($rrule->isValid(false), 'until & count'); // test invalid by setFromString $this->setExpectedException('Tinebase_Exception_Record_Validation'); $rruleString = "FREQ=WEEKLY;INTERVAL=NotAnInt"; $rrule = new Calendar_Model_Rrule(array()); $rrule->setFromString($rruleString); }
/** * Computes the Recurrence set of the given event leaving out $_event->exdate and $_exceptions * * @todo respect rrule_until! * * @param Calendar_Model_Event $_event * @param Tinebase_Record_RecordSet $_exceptions * @param Tinebase_DateTime $_from * @param Tinebase_DateTime $_until * @return Tinebase_Record_RecordSet * @throws Tinebase_Exception_UnexpectedValue */ public static function computeRecurrenceSet($_event, $_exceptions, $_from, $_until) { if (!$_event->dtstart instanceof Tinebase_DateTime) { throw new Tinebase_Exception_UnexpectedValue('Event needs DateTime dtstart: ' . print_r($_event->toArray(), TRUE)); } $rrule = new Calendar_Model_Rrule(NULL, TRUE); $rrule->setFromString($_event->rrule); $exceptionRecurIds = self::getExceptionsRecurIds($_event, $_exceptions); $recurSet = new Tinebase_Record_RecordSet('Calendar_Model_Event'); switch ($rrule->freq) { case self::FREQ_DAILY: self::_computeRecurDaily($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); break; case self::FREQ_WEEKLY: // default BYDAY clause if (!$rrule->byday) { $rrule->byday = array_search($_event->dtstart->format('w'), self::$WEEKDAY_DIGIT_MAP); } if (!$rrule->wkst) { $rrule->wkst = self::getWeekStart(); } $weekDays = array_keys(self::$WEEKDAY_DIGIT_MAP); array_splice($weekDays, 0, 0, array_splice($weekDays, array_search($rrule->wkst, $weekDays))); $dailyrrule = clone $rrule; $dailyrrule->freq = self::FREQ_DAILY; $dailyrrule->interval = 7 * $rrule->interval; $eventLength = $_event->dtstart->diff($_event->dtend); foreach (explode(',', $rrule->byday) as $recurWeekDay) { // NOTE: in weekly computation, each wdays base event is a recur instance itself $baseEvent = clone $_event; // NOTE: skipping must be done in organizer_tz $baseEvent->dtstart->setTimezone($_event->originator_tz); $direction = array_search($recurWeekDay, $weekDays) >= array_search(array_search($baseEvent->dtstart->format('w'), self::$WEEKDAY_DIGIT_MAP), $weekDays) ? +1 : -1; self::skipWday($baseEvent->dtstart, $recurWeekDay, $direction, TRUE); $baseEvent->dtstart->setTimezone('UTC'); $baseEvent->dtend = clone $baseEvent->dtstart; $baseEvent->dtend->add($eventLength); self::_computeRecurDaily($baseEvent, $dailyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); // check if base event (recur instance) needs to be added to the set if ($baseEvent->dtstart > $_event->dtstart && $baseEvent->dtstart >= $_from && $baseEvent->dtstart < $_until) { if (!in_array($baseEvent->setRecurId($baseEvent->getId()), $exceptionRecurIds)) { self::addRecurrence($baseEvent, $recurSet); } } } break; case self::FREQ_MONTHLY: if ($rrule->byday) { self::_computeRecurMonthlyByDay($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); } else { self::_computeRecurMonthlyByMonthDay($_event, $rrule, $exceptionRecurIds, $_from, $_until, $recurSet); } break; case self::FREQ_YEARLY: $yearlyrrule = clone $rrule; $yearlyrrule->freq = self::FREQ_MONTHLY; $yearlyrrule->interval = 12; $baseEvent = clone $_event; $originatorsDtstart = clone $baseEvent->dtstart; $originatorsDtstart->setTimezone($_event->originator_tz); // @TODO respect BYMONTH if ($rrule->bymonth && $rrule->bymonth != $originatorsDtstart->format('n')) { // adopt $diff = (12 + $rrule->bymonth - $originatorsDtstart->format('n')) % 12; // NOTE: skipping must be done in organizer_tz $baseEvent->dtstart->setTimezone($_event->originator_tz); $baseEvent->dtend->setTimezone($_event->originator_tz); $baseEvent->dtstart->addMonth($diff); $baseEvent->dtend->addMonth($diff); $baseEvent->dtstart->setTimezone('UTC'); $baseEvent->dtend->setTimezone('UTC'); // check if base event (recur instance) needs to be added to the set if ($baseEvent->dtstart->isLater($_from) && $baseEvent->dtstart->isEarlier($_until)) { if (!in_array($baseEvent->setRecurId($baseEvent->getId()), $exceptionRecurIds)) { self::addRecurrence($baseEvent, $recurSet); } } } if ($rrule->byday) { self::_computeRecurMonthlyByDay($baseEvent, $yearlyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); } else { self::_computeRecurMonthlyByMonthDay($baseEvent, $yearlyrrule, $exceptionRecurIds, $_from, $_until, $recurSet); } break; } return $recurSet; }
/** * sets rrule until helper field * * @return void */ public function setRruleUntil() { if (empty($this->rrule)) { $this->rrule_until = NULL; } else { $rrule = $this->rrule; if (!$this->rrule instanceof Calendar_Model_Rrule) { $rrule = new Calendar_Model_Rrule(array()); $rrule->setFromString($this->rrule); $this->rrule = $rrule; } if (isset($rrule->count)) { $this->rrule_until = NULL; $exdates = $this->exdate; $this->exdate = NULL; $lastOccurrence = Calendar_Model_Rrule::computeNextOccurrence($this, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $this->dtend, $rrule->count - 1); $this->rrule_until = $lastOccurrence->dtend; $this->exdate = $exdates; } else { // set until to end of day in organizers timezone. // NOTE: this is in contrast to the iCal spec which says until should be the // dtstart of the last occurence. But as the client with the name of the // spec sets it to the end of the day, we do it also. if ($rrule->until instanceof Tinebase_DateTime && !$this->is_all_day_event) { $rrule->until->setTimezone($this->originator_tz); // NOTE: subSecond cause some clients send 00:00:00 for midnight $rrule->until->subSecond(1)->setTime(23, 59, 59); $rrule->until->setTimezone('UTC'); } $this->rrule_until = $rrule->until; } } if ($this->rrule_until && $this->rrule_until->getTimeStamp() - $this->dtstart->getTimeStamp() < -1) { throw new Tinebase_Exception_Record_Validation('rrule until must not be before dtstart'); } }
/** * sets rrule until helper field * * @return void */ public function setRruleUntil() { if (empty($this->rrule)) { $this->rrule_until = NULL; } else { $rrule = $this->rrule; if (!$this->rrule instanceof Calendar_Model_Rrule) { $rrule = new Calendar_Model_Rrule(array()); $rrule->setFromString($this->rrule); } if (isset($rrule->count)) { $this->rrule_until = NULL; $exdates = $this->exdate; $this->exdate = NULL; $lastOccurrence = Calendar_Model_Rrule::computeNextOccurrence($this, new Tinebase_Record_RecordSet('Calendar_Model_Event'), $this->dtend, $rrule->count - 1); $this->rrule_until = $lastOccurrence->dtend; $this->exdate = $exdates; } else { $this->rrule_until = $rrule->until; } } }