/** * Constructor */ public function __construct($start, $end) { if (!$start instanceof qCal_DateTime) { $start = qCal_DateTime::factory($start); } if (!$end instanceof qCal_DateTime) { $end = qCal_DateTime::factory($end); } $this->start = $start; $this->end = $end; if ($this->getSeconds() < 0) { throw new qCal_DateTime_Exception_InvalidPeriod("The start date must come before the end date."); } }
/** * Cast a string value into a qCal_DateTime_Period object */ protected function doCast($value) { $parts = explode("/", $value); if (count($parts) !== 2) { throw new qCal_DateTime_Exception_InvalidPeriod("A period must contain a start date and either an end date, or a duration of time."); } $start = qCal_DateTime::factory($parts[0]); try { $end = qCal_DateTime::factory($parts[1]); } catch (qCal_DateTime_Exception $e) { // @todo This should probably be a more specific exception // invalid date, so try duration // @todo: I might want to create a qCal_Date object to represent a duration (not tied to any points in time) // using a qCal_Value object here is sort of inconsistent. Plus, I can see value in having that functionality // within the qCal_Date subcomponent // also, there is a difference in a period and a duration in that if you say start on feb 26 and end on march 2 // that will be a different "duration" depending on the year. that goes for months with alternate amounts of days too $duration = new qCal_DateTime_Duration($parts[1]); $end = qCal_DateTime::factory($start->getUnixTimestamp() + $duration->getSeconds()); // @todo This needs to be updated once qCal_DateTime accepts timestamps } return new qCal_DateTime_Period($start, $end); }
/** * Fetches instances of the recurrence rule in the given time period. Because recurrences * could potentially go on forever, there is no way to fetch ALL instances of a recurrence rule * other than providing a date range that spans the entire length of the recurrence. * * The way this will need to work is, depending on the frequency, I will find all possible * occurrence of the rule. For instance, if this is a "monthly" rule, I'll find out which month * to start in, then find all occurrence possible. Then narrow down by the other rules I guess. * * @idea Maybe I should build classes for each of the frequency types. That way I could loop over * the object and get methods like qCal_DateTime_Recur_Monthly::isNthDay('SU') to find out what sunday * of the month it is... stuff like that... I dunno... ? * * @throws qCal_DateTime_Exception_InvalidRecur * @todo The giant switch in this method is a glaring code smell, but it works for now. I will refactor * after version 0.1 and remove the switch (probably will implement qCal_DateTime_Recur_Yearly, qCal_DateTime_Recur_Monthly, etc.) */ public function getRecurrences($start, $end) { $start = qCal_DateTime::factory($start); $end = qCal_DateTime::factory($end); if ($start->getUnixTimestamp() > $end->getUnixTimestamp()) { throw new qCal_DateTime_Exception_InvalidRecur('Start date must come before end date'); } if (!$this->interval) { throw new qCal_DateTime_Exception_InvalidRecur('You must specify an interval'); } $rules = array('bymonth' => array(), 'byweekno' => array(), 'byyearday' => array(), 'byday' => array()); // byMonth rules if (is_array($this->bymonth)) { foreach ($this->bymonth as $bymonth) { $rules['bymonth'][] = new qCal_DateTime_Recur_Rule_ByMonth($bymonth); } } // byWeekNo rules if (is_array($this->byweekno)) { foreach ($this->byweekno as $byweekno) { $rules['byweekno'][] = new qCal_DateTime_Recur_Rule_ByWeekNo($byweekno); } } // byYearDay rules if (is_array($this->byyearday)) { foreach ($this->byyearday as $byyearday) { $rules['byyearday'][] = new qCal_DateTime_Recur_Rule_ByYearDay($byyearday); } } // byMonthDay rules (these get applied to bymonth rules) if (is_array($this->bymonthday)) { foreach ($this->bymonthday as $bymonthday) { $bmdrule = new qCal_DateTime_Recur_Rule_ByMonthDay($bymonthday); foreach ($rules['bymonth'] as $bymonth) { $bymonth->attach($bmdrule); } } } // byDay rules (these get applied to bymonth rules if they exist, otherwise simply to year) if (is_array($this->byday)) { foreach ($this->byday as $byday) { $bdrule = new qCal_DateTime_Recur_Rule_ByDay($byday); if (is_array($rules['bymonth']) && !empty($rules['bymonth'])) { foreach ($rules['bymonth'] as $bymonth) { $bymonth->attach($bdrule); } } else { $rules['byday'][] = $bdrule; } } } // byHour rules (these get applied to each rule above) if (is_array($this->byhour)) { foreach ($this->byhour as $byhour) { $bhrule = new qCal_DateTime_Recur_Rule_ByHour($byhour); foreach ($rules as $type => $ruleset) { foreach ($ruleset as $rule) { $rule->attach($bhrule); } } } } // byMinute rules (these get applied to each rule above) if (is_array($this->byminute)) { foreach ($this->byminute as $byminute) { $bmrule = new qCal_DateTime_Recur_Rule_ByMinute($byminute); foreach ($rules as $type => $ruleset) { foreach ($ruleset as $rule) { $rule->attach($bmrule); } } } } // bySecond rules (these get applied to each rule above) if (is_array($this->bysecond)) { foreach ($this->bysecond as $bysecond) { $bsrule = new qCal_DateTime_Recur_Rule_BySecond($bysecond); foreach ($rules as $type => $ruleset) { foreach ($ruleset as $rule) { $rule->attach($bsrule); } } } } return $this->doGetRecurrences($rules, $start, $end); }
/** * Timezones should be accessible individually by getTimezone() */ public function testGetTimezone() { $cal = new qCal_Component_Vcalendar(); $useastern = new qCal_Component_Vtimezone(array('tzid' => 'US-Eastern')); // fake us eastern timezone $useastern->attach(new qCal_Component_Standard(array('dtstart' => qCal_DateTime::factory('20090913T000000Z'), 'offsetto' => new qCal_Property_Tzoffsetto('0200'), 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0400')))); $uswestern = new qCal_Component_Vtimezone(array('tzid' => 'US-Western')); // fake us western timezone $uswestern->attach(new qCal_Component_Standard(array('dtstart' => qCal_DateTime::factory('20090913T000000Z'), 'offsetto' => new qCal_Property_Tzoffsetto('0100'), 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0300')))); $cal->attach($useastern); $cal->attach($uswestern); $this->assertIdentical($cal->getTimezone('us-eastern'), $useastern); }
/** * Test conversion to UTC * @todo The entire process for UTC conversion is hacky at best. Fix it up in the next release. */ public function testUTCConversion() { $datetime = qCal_DateTime::factory("2/22/1988 5:52am", "America/Denver"); // February 22, 1988 at 5:52am Mountain Standard Time (-7 hours) // UTC is GMT time, which means that the result of this should be the time specified plus seven hours $this->assertEqual($datetime->getUtc(), "19880222T125200Z"); }
/** * Test that all of the right characters are escaped when rendered * @todo Need to make sure that when parsing the escape characters are removed. */ public function testCharactersAreEscaped() { $journal = new qCal_Component_Vjournal(array('summary' => 'The most interesting, but non-interesting journal entry ever.', 'description' => 'This is a sentence that ends with a semi-colon, which I\'m not sure needs to be escaped; I will read the RFC a bit and find out what, exactly, needs to escaped. I know commas do though, and this entry has plenty of those.', 'dtstart' => qCal_DateTime::factory('20090809T113500'))); $this->assertEqual($journal->render(), "BEGIN:VJOURNAL\r\nSUMMARY:The most interesting\\, but non-interesting journal entry ever.\r\nDESCRIPTION:This is a sentence that ends with a semi-colon\\, which I'm not \r\n sure needs to be escaped; I will read the RFC a bit and find out what\\, exa\r\n ctly\\, needs to escaped. I know commas do though\\, and this entry has plent\r\n y of those.\r\nDTSTART:20090809T113500\r\nEND:VJOURNAL\r\n"); }
/** * Test that date-time data is handled right */ public function testRawDateTime() { $value = new qCal_Value_DateTime('2009-04-23 6:00'); $this->assertEqual($value->getValue(), qCal_DateTime::factory('2009-04-23 6:00')); }
public function eventToIcal($_event) { if ($_event instanceof Tinebase_Record_RecordSet) { foreach ($_event as $event) { $this->eventToIcal($event); } return $this->_vcalendar; } // NOTE: we deliver events in originators tz $_event->setTimezone($_event->originator_tz); if (!in_array($_event->originator_tz, $this->_attachedTimezones)) { $this->_vcalendar->attach(self::getVtimezone($_event->originator_tz)); $this->_attachedTimezones[] = $_event->originator_tz; } if ($_event->is_all_day_event) { $dtstart = new qCal_Property_Dtstart($_event->dtstart->format('Ymd'), array('VALUE' => 'DATE')); $dtend = new qCal_Property_Dtend($_event->dtend->format('Ymd'), array('VALUE' => 'DATE')); } else { $dtstart = new qCal_Property_Dtstart(qCal_DateTime::factory($_event->dtstart->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); $dtend = new qCal_Property_Dtend(qCal_DateTime::factory($_event->dtend->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); } $vevent = new qCal_Component_Vevent(array('uid' => $_event->uid, 'sequence' => $_event->seq, 'summary' => $_event->summary, 'dtstart' => $dtstart, 'dtend' => $dtend)); foreach (self::$veventMap as $icalProp => $tineField) { if (isset($_event[$tineField])) { $vevent->addProperty($icalProp, $_event->{$tineField}); } } // rrule if ($_event->rrule) { $vevent->addProperty('rrule', preg_replace('/(UNTIL=)(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})/', '$1$2$3$4T$5$6$7Z', $_event->rrule)); if ($exdateArray = $_event->exdate) { // use multiple EXDATE for the moment, as apple ical uses them foreach ($_event->exdate as $exdate) { $exdates = new qCal_Property_Exdate(qCal_DateTime::factory($exdate->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); $vevent->addProperty($exdates); } // $exdates = new qCal_Property_Exdate(qCal_DateTime::factory(array_shift($exdateArray)->format('Ymd\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz)); // foreach($exdateArray as $exdate) { // $exdates->addValue(qCal_DateTime::factory($exdate->format('Ymd\THis'), $_event->originator_tz)); // } // // $vevent->addProperty($exdates); } } // recurid if ($_event->isRecurException()) { $originalDtStart = $_event->getOriginalDtStart(); $originalDtStart->setTimezone($_event->originator_tz); $vevent->addProperty(new qCal_Property_RecurrenceId(qCal_DateTime::factory($originalDtStart->format('Ymd\\THis'), $_event->originator_tz), array('TZID' => $_event->originator_tz))); } // organizer $organizerId = $_event->organizer instanceof Addressbook_Model_Contact ? array($_event->organizer->getId()) : array($_event->organizer); $organizer = Addressbook_Controller_Contact::getInstance()->getMultiple($organizerId, TRUE)->getFirstRecord(); if ($organizer && ($organizerEmail = $organizer->getPreferedEmailAddress())) { $vevent->addProperty(new qCal_Property_Organizer("mailto:{$organizerEmail}", array('CN' => $organizer->n_fileas))); } // attendee if ($_event->attendee) { Calendar_Model_Attender::resolveAttendee($_event->attendee, FALSE); foreach ($_event->attendee as $attender) { $attenderEmail = $attender->getEmail(); if ($attenderEmail) { $vevent->addProperty(new qCal_Property_Attendee("mailto:{$attenderEmail}", array('CN' => $attender->getName(), 'CUTYPE' => self::$cutypeMap[$attender->user_type], 'EMAIL' => $attenderEmail, 'PARTSTAT' => $attender->status, 'ROLE' => "{$attender->role}-PARTICIPANT", 'RSVP' => 'FALSE'))); } } } // alarms if ($_event->alarms) { foreach ($_event->alarms as $alarm) { $valarm = new qCal_Component_Valarm(array('ACTION' => 'DISPLAY', 'DESCRIPTION' => $_event->summary)); // qCal only support DURATION ;-( $diffSeconds = $_event->dtstart->php52compat_diff($alarm->alarm_time); $valarm->addProperty(new qCal_Property_Trigger($diffSeconds)); // if (is_numeric($alarm->minutes_before)) { // $valarm->addProperty(new qCal_Property_Trigger("-PT{$alarm->minutes_before}M")); // } else { // $valarm->addProperty(new qCal_Property_Trigger(qCal_DateTime::factory($alarm->alarm_time->format('Ymd\THis'), $_event->originator_tz)), array('TZID' => $_event->originator_tz)); // } $vevent->attach($valarm); } } // @todo status $this->_vcalendar->attach($vevent); return $this->_vcalendar; }
/** * Provided a date/time object, use this recurrence's rules to determine * all of the recurrence times for the date and return them in an array. * @param qCal_Date The date object to find time recurrences for * @return array A list of time recurrences for the specified date/time * @access protected * @todo I don't really like the way this is done. Definitely a code smell here. * Each of the rules should do their own logic. Something like: * $seconds = $bySecond->getTimeInstances(); * $minutes = $byMinute->getTimeInstances($seconds); * $hours = $byHour->getTimeInstances($minutes); */ protected function findTimeRecurrences(qCal_Date $date) { // find all of the bySeconds $seconds = array(); if ($this->hasRule('qCal_DateTime_Recur_Rule_BySecond')) { $seconds = $this->getRule('qCal_DateTime_Recur_Rule_BySecond')->getValues(); sort($seconds); } else { $seconds = array($this->getStart()->getTime()->getSecond()); } // find all of the byMinutes $minutes = array(); if ($this->hasRule('qCal_DateTime_Recur_Rule_ByMinute')) { $minutesRules = $this->getRule('qCal_DateTime_Recur_Rule_ByMinute')->getValues(); sort($minutesRules); } else { $minutesRules = array($this->getStart()->getTime()->getMinute()); } foreach ($minutesRules as $minute) { $minutes[$minute] = $seconds; } // find all of the byHours $hours = array(); if ($this->hasRule('qCal_DateTime_Recur_Rule_ByHour')) { $hoursRules = $this->getRule('qCal_DateTime_Recur_Rule_ByHour')->getValues(); sort($hoursRules); } else { $hoursRules = array($this->getStart()->getTime()->getHour()); } foreach ($hoursRules as $hour) { $hours[$hour] = $minutes; } // create an array to store times $times = array(); foreach ($hours as $hour => $minutes) { foreach ($minutes as $minute => $seconds) { foreach ($seconds as $second) { try { // try to build a date/time object $datetime = new qCal_DateTime($date->getYear(), $date->getMonth(), $date->getDay(), $hour, $minute, $second); $times[$datetime->format('YmdHis')] = $datetime; } catch (qCal_DateTime_Exception_InvalidTime $e) { // if the date/time object instantiation fails, this exception will be thrown // @todo Recover from this error and report it. Maybe catch the error and pass it to a log or something? // qCal_Log::logException($e, get_class($this)); throw $e; } } } } return $times; }
/** * This converts to a qCal_Date for internal storage */ protected function doCast($value) { // @todo This may be the wrong place to do this... if ($value instanceof qCal_DateTime) { return $value; } $date = qCal_DateTime::factory($value); return $date; }
/** * Set the end date/time for the recurrence set. No recurrences will be returned * beyond this date/time * @param mixed Either a qCal_DateTime object or a string representing one * @return $this * @access public */ public function setUntil($datetime) { $this->until = $datetime instanceof qCal_DateTime ? $datetime : qCal_DateTime::factory($datetime); return $this; }
public function testGetUtc() { $datetime = new qCal_DateTime(2009, 10, 31, 10, 30, 0, "America/Los_Angeles"); $this->assertEqual($datetime->getUtc(), "20091031T183000Z"); $this->assertEqual($datetime->getUtc(true), "2009-10-31T18:30:00Z"); }