/** * computes monthly (bymonthday) recurring events and inserts them into given $_recurSet * * @param Calendar_Model_Event $_event * @param Calendar_Model_Rrule $_rrule * @param array $_exceptionRecurIds * @param Tinebase_DateTime $_from * @param Tinebase_DateTime $_until * @param Tinebase_Record_RecordSet $_recurSet * @return void */ protected static function _computeRecurMonthlyByMonthDay($_event, $_rrule, $_exceptionRecurIds, $_from, $_until, $_recurSet) { $eventInOrganizerTZ = clone $_event; $eventInOrganizerTZ->setTimezone($_event->originator_tz); // some clients skip the monthday e.g. for yearly rrules if (!$_rrule->bymonthday) { $_rrule->bymonthday = $eventInOrganizerTZ->dtstart->format('j'); } // NOTE: non existing dates will be discarded (e.g. 31. Feb.) // for correct computations we deal with virtual dates, represented as arrays $computationStartDateArray = self::date2array($eventInOrganizerTZ->dtstart); // adopt startdate if rrule monthday != dtstart monthday // in this case, the first instance is not the base event! if ($_rrule->bymonthday != $computationStartDateArray['day']) { $computationStartDateArray['day'] = $_rrule->bymonthday; $computationStartDateArray = self::addMonthIgnoringDay($computationStartDateArray, -1 * $_rrule->interval); } $computationEndDate = $_event->rrule_until instanceof DateTime && $_until->isLater($_event->rrule_until) ? $_event->rrule_until : $_until; // if dtstart is before $_from, we compute the offset where to start our calculations if ($eventInOrganizerTZ->dtstart->isEarlier($_from)) { $computationOffsetMonth = self::getMonthDiff($eventInOrganizerTZ->dtend, $_from); // NOTE: $computationOffsetMonth must be multiple of interval! $computationOffsetMonth = floor($computationOffsetMonth / $_rrule->interval) * $_rrule->interval; $computationStartDateArray = self::addMonthIgnoringDay($computationStartDateArray, $computationOffsetMonth - $_rrule->interval); } $eventLength = $eventInOrganizerTZ->dtstart->diff($eventInOrganizerTZ->dtend); $originatorsOriginalDtstart = clone $eventInOrganizerTZ->dtstart; while (true) { $computationStartDateArray = self::addMonthIgnoringDay($computationStartDateArray, $_rrule->interval); $recurEvent = self::cloneEvent($eventInOrganizerTZ); $recurEvent->dtstart = self::array2date($computationStartDateArray, $eventInOrganizerTZ->originator_tz); // we calculate dtend from the event length, as events during a dst boundary could get dtend less than dtstart otherwise $recurEvent->dtend = clone $recurEvent->dtstart; $recurEvent->dtend->add($eventLength); $recurEvent->setTimezone('UTC'); if ($computationEndDate->isEarlier($recurEvent->dtstart)) { break; } // skip non existing dates if (!Tinebase_DateTime::isDate(self::array2string($computationStartDateArray))) { continue; } // skip events ending before our period. // NOTE: such events could be included, cause our offset only calcs months and not seconds if ($_from->compare($recurEvent->dtend) >= 0) { continue; } $recurEvent->setRecurId($_event->getId()); if (!in_array($recurEvent->recurid, $_exceptionRecurIds)) { self::addRecurrence($recurEvent, $_recurSet); } } }
/** * returns the original dtstart of a recur series exception event * -> when the event should have started with no exception * * @return Tinebase_DateTime */ public function getOriginalDtStart() { $origianlDtStart = $this->dtstart instanceof stdClass ? clone $this->dtstart : $this->dtstart; if ($this->isRecurException()) { if ($this->recurid instanceof DateTime) { $origianlDtStart = clone $this->recurid; } else { if (is_string($this->recurid)) { $origianlDtStartString = substr($this->recurid, -19); if (!Tinebase_DateTime::isDate($origianlDtStartString)) { throw new Tinebase_Exception_InvalidArgument('recurid does not contain a valid original start date'); } $origianlDtStart = new Tinebase_DateTime($origianlDtStartString, 'UTC'); } } } return $origianlDtStart; }