/** * This method will try to find out the correct timezone for an iCalendar * date-time value. * * You must pass the contents of the TZID parameter, as well as the full * calendar. * * If the lookup fails, this method will return UTC. * * @param string $tzid * @param Sabre\VObject\Component $vcalendar * @return DateTimeZone */ public static function getTimeZone($tzid, Component $vcalendar = null) { // First we will just see if the tzid is a support timezone identifier. try { return new \DateTimeZone($tzid); } catch (\Exception $e) { } // Next, we check if the tzid is somewhere in our tzid map. if (isset(self::$map[$tzid])) { return new \DateTimeZone(self::$map[$tzid]); } if ($vcalendar) { // If that didn't work, we will scan VTIMEZONE objects foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { if ((string) $vtimezone->TZID === $tzid) { // Some clients add 'X-LIC-LOCATION' with the olson name. if (isset($vtimezone->{'X-LIC-LOCATION'})) { try { return new \DateTimeZone($vtimezone->{'X-LIC-LOCATION'}); } catch (\Exception $e) { } } // Microsoft may add a magic number, which we also have an // answer for. if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { if (isset(self::$microsoftExchangeMap[(int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->value])) { return new \DateTimeZone(self::$microsoftExchangeMap[(int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->value]); } } } } } // If we got all the way here, we default to UTC. return new \DateTimeZone(date_default_timezone_get()); }
/** * This method will try to find out the correct timezone for an iCalendar * date-time value. * * You must pass the contents of the TZID parameter, as well as the full * calendar. * * If the lookup fails, this method will return the default PHP timezone * (as configured using date_default_timezone_set, or the date.timezone ini * setting). * * Alternatively, if $failIfUncertain is set to true, it will throw an * exception if we cannot accurately determine the timezone. * * @param string $tzid * @param SabreForRainLoop\VObject\Component $vcalendar * @return DateTimeZone */ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { // First we will just see if the tzid is a support timezone identifier. try { return new \DateTimeZone($tzid); } catch (\Exception $e) { } // Next, we check if the tzid is somewhere in our tzid map. if (isset(self::$map[$tzid])) { return new \DateTimeZone(self::$map[$tzid]); } // Maybe the author was hyper-lazy and just included an offset. We // support it, but we aren't happy about it. if (preg_match('/^GMT(\\+|-)([0-9]{4})$/', $tzid, $matches)) { return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2], 0, 2), '0')); } if ($vcalendar) { // If that didn't work, we will scan VTIMEZONE objects foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { if ((string) $vtimezone->TZID === $tzid) { // Some clients add 'X-LIC-LOCATION' with the olson name. if (isset($vtimezone->{'X-LIC-LOCATION'})) { $lic = (string) $vtimezone->{'X-LIC-LOCATION'}; // Libical generators may specify strings like // "SystemV/EST5EDT". For those we must remove the // SystemV part. if (substr($lic, 0, 8) === 'SystemV/') { $lic = substr($lic, 8); } try { return new \DateTimeZone($lic); } catch (\Exception $e) { } } // Microsoft may add a magic number, which we also have an // answer for. if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); // 2 can mean both Europe/Lisbon and Europe/Sarajevo. if ($cdoId === 2 && strpos((string) $vtimezone->TZID, 'Sarajevo') !== false) { return new \DateTimeZone('Europe/Sarajevo'); } if (isset(self::$microsoftExchangeMap[$cdoId])) { return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); } } } } } if ($failIfUncertain) { throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); } // If we got all the way here, we default to UTC. return new \DateTimeZone(date_default_timezone_get()); }
/** * This method will try to find out the correct timezone for an iCalendar * date-time value. * * You must pass the contents of the TZID parameter, as well as the full * calendar. * * If the lookup fails, this method will return the default PHP timezone * (as configured using date_default_timezone_set, or the date.timezone ini * setting). * * Alternatively, if $failIfUncertain is set to true, it will throw an * exception if we cannot accurately determine the timezone. * * @param string $tzid * @param Sabre\VObject\Component $vcalendar * @return DateTimeZone */ public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) { // First we will just see if the tzid is a support timezone identifier. // // The only exception is if the timezone starts with (. This is to // handle cases where certain microsoft products generate timezone // identifiers that for instance look like: // // (GMT+01.00) Sarajevo/Warsaw/Zagreb // // Since PHP 5.5.10, the first bit will be used as the timezone and // this method will return just GMT+01:00. This is wrong, because it // doesn't take DST into account. if ($tzid[0] !== '(') { // PHP has a bug that logs PHP warnings even it shouldn't: // https://bugs.php.net/bug.php?id=67881 // // That's why we're checking if we'll be able to successfull instantiate // \DateTimeZone() before doing so. Otherwise we could simply instantiate // and catch the exception. $tzIdentifiers = \DateTimeZone::listIdentifiers(); try { if (in_array($tzid, $tzIdentifiers) || preg_match('/^GMT(\\+|-)([0-9]{4})$/', $tzid, $matches) || in_array($tzid, self::getIdentifiersBC())) { return new \DateTimeZone($tzid); } } catch (\Exception $e) { } } self::loadTzMaps(); // Next, we check if the tzid is somewhere in our tzid map. if (isset(self::$map[$tzid])) { return new \DateTimeZone(self::$map[$tzid]); } // Maybe the author was hyper-lazy and just included an offset. We // support it, but we aren't happy about it. if (preg_match('/^GMT(\\+|-)([0-9]{4})$/', $tzid, $matches)) { // Note that the path in the source will never be taken from PHP 5.5.10 // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it // already gets returned early in this function. Once we drop support // for versions under PHP 5.5.10, this bit can be taken out of the // source. // @codeCoverageIgnoreStart return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2], 0, 2), '0')); // @codeCoverageIgnoreEnd } if ($vcalendar) { // If that didn't work, we will scan VTIMEZONE objects foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { if ((string) $vtimezone->TZID === $tzid) { // Some clients add 'X-LIC-LOCATION' with the olson name. if (isset($vtimezone->{'X-LIC-LOCATION'})) { $lic = (string) $vtimezone->{'X-LIC-LOCATION'}; // Libical generators may specify strings like // "SystemV/EST5EDT". For those we must remove the // SystemV part. if (substr($lic, 0, 8) === 'SystemV/') { $lic = substr($lic, 8); } return self::getTimeZone($lic, null, $failIfUncertain); } // Microsoft may add a magic number, which we also have an // answer for. if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) { $cdoId = (int) $vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue(); // 2 can mean both Europe/Lisbon and Europe/Sarajevo. if ($cdoId === 2 && strpos((string) $vtimezone->TZID, 'Sarajevo') !== false) { return new \DateTimeZone('Europe/Sarajevo'); } if (isset(self::$microsoftExchangeMap[$cdoId])) { return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]); } } } } } if ($failIfUncertain) { throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid); } // If we got all the way here, we default to UTC. return new \DateTimeZone(date_default_timezone_get()); }
/** * Creates the iterator * * You should pass a VCALENDAR component, as well as the UID of the event * we're going to traverse. * * @param Component $vcal * @param string|null $uid */ public function __construct(Component $vcal, $uid = null) { if (is_null($uid)) { if ($vcal instanceof Component\VCalendar) { throw new \InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well'); } $components = array($vcal); $uid = (string) $vcal->uid; } else { $components = $vcal->select('VEVENT'); } foreach ($components as $component) { if ((string) $component->uid == $uid) { if (isset($component->{'RECURRENCE-ID'})) { $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component; $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime(); } else { $this->baseEvent = $component; } } } if (!$this->baseEvent) { // No base event was found. CalDAV does allow cases where only // overridden instances are stored. // // In this barticular case, we're just going to grab the first // event and use that instead. This may not always give the // desired result. if (!count($this->overriddenEvents)) { throw new \InvalidArgumentException('Could not find an event with uid: ' . $uid); } ksort($this->overriddenEvents, SORT_NUMERIC); $this->baseEvent = array_shift($this->overriddenEvents); } $this->startDate = clone $this->baseEvent->DTSTART->getDateTime(); $this->endDate = null; if (isset($this->baseEvent->DTEND)) { $this->endDate = clone $this->baseEvent->DTEND->getDateTime(); } else { $this->endDate = clone $this->startDate; if (isset($this->baseEvent->DURATION)) { $this->endDate->add(DateTimeParser::parse((string) $this->baseEvent->DURATION)); } elseif (!$this->baseEvent->DTSTART->hasTime()) { $this->endDate->modify('+1 day'); } } $this->currentDate = clone $this->startDate; $rrule = $this->baseEvent->RRULE; // If no rrule was specified, we create a default setting if (!$rrule) { $this->frequency = 'daily'; $this->count = 1; } else { foreach ($rrule->getParts() as $key => $value) { switch ($key) { case 'FREQ': if (!in_array(strtolower($value), array('secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'))) { throw new \InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); } $this->frequency = strtolower($value); break; case 'UNTIL': $this->until = DateTimeParser::parse($value); // In some cases events are generated with an UNTIL= // parameter before the actual start of the event. // // Not sure why this is happening. We assume that the // intention was that the event only recurs once. // // So we are modifying the parameter so our code doesn't // break. if ($this->until < $this->baseEvent->DTSTART->getDateTime()) { $this->until = $this->baseEvent->DTSTART->getDateTime(); } break; case 'COUNT': $this->count = (int) $value; break; case 'INTERVAL': $this->interval = (int) $value; if ($this->interval < 1) { throw new \InvalidArgumentException('INTERVAL in RRULE must be a positive integer!'); } break; case 'BYSECOND': $this->bySecond = (array) $value; break; case 'BYMINUTE': $this->byMinute = (array) $value; break; case 'BYHOUR': $this->byHour = (array) $value; break; case 'BYDAY': $this->byDay = (array) $value; break; case 'BYMONTHDAY': $this->byMonthDay = (array) $value; break; case 'BYYEARDAY': $this->byYearDay = (array) $value; break; case 'BYWEEKNO': $this->byWeekNo = (array) $value; break; case 'BYMONTH': $this->byMonth = (array) $value; break; case 'BYSETPOS': $this->bySetPos = (array) $value; break; case 'WKST': $this->weekStart = strtoupper($value); break; } } } // Parsing exception dates if (isset($this->baseEvent->EXDATE)) { foreach ($this->baseEvent->EXDATE as $exDate) { foreach (explode(',', (string) $exDate) as $exceptionDate) { $this->exceptionDates[] = DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone()); } } } }
/** * Creates the iterator * * You should pass a VCALENDAR component, as well as the UID of the event * we're going to traverse. * * @param Component $vcal * @param string|null $uid */ public function __construct(Component $vcal, $uid = null) { if (is_null($uid)) { if ($vcal->name === 'VCALENDAR') { throw new \InvalidArgumentException('If you pass a VCALENDAR object, you must pass a uid argument as well'); } $components = array($vcal); $uid = (string) $vcal->uid; } else { $components = $vcal->select('VEVENT'); } foreach ($components as $component) { if ((string) $component->uid == $uid) { if (isset($component->{'RECURRENCE-ID'})) { $this->overriddenEvents[$component->DTSTART->getDateTime()->getTimeStamp()] = $component; $this->overriddenDates[] = $component->{'RECURRENCE-ID'}->getDateTime(); } else { $this->baseEvent = $component; } } } if (!$this->baseEvent) { throw new \InvalidArgumentException('Could not find a base event with uid: ' . $uid); } $this->startDate = clone $this->baseEvent->DTSTART->getDateTime(); $this->endDate = null; if (isset($this->baseEvent->DTEND)) { $this->endDate = clone $this->baseEvent->DTEND->getDateTime(); } else { $this->endDate = clone $this->startDate; if (isset($this->baseEvent->DURATION)) { $this->endDate->add(DateTimeParser::parse($this->baseEvent->DURATION->value)); } elseif ($this->baseEvent->DTSTART->getDateType() === Property\DateTime::DATE) { $this->endDate->modify('+1 day'); } } $this->currentDate = clone $this->startDate; $rrule = (string) $this->baseEvent->RRULE; $parts = explode(';', $rrule); // If no rrule was specified, we create a default setting if (!$rrule) { $this->frequency = 'daily'; $this->count = 1; } else { foreach ($parts as $part) { list($key, $value) = explode('=', $part, 2); switch (strtoupper($key)) { case 'FREQ': if (!in_array(strtolower($value), array('secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly'))) { throw new \InvalidArgumentException('Unknown value for FREQ=' . strtoupper($value)); } $this->frequency = strtolower($value); break; case 'UNTIL': $this->until = DateTimeParser::parse($value); // In some cases events are generated with an UNTIL= // parameter before the actual start of the event. // // Not sure why this is happening. We assume that the // intention was that the event only recurs once. // // So we are modifying the parameter so our code doesn't // break. if ($this->until < $this->baseEvent->DTSTART->getDateTime()) { $this->until = $this->baseEvent->DTSTART->getDateTime(); } break; case 'COUNT': $this->count = (int) $value; break; case 'INTERVAL': $this->interval = (int) $value; if ($this->interval < 1) { throw new \InvalidArgumentException('INTERVAL in RRULE must be a positive integer!'); } break; case 'BYSECOND': $this->bySecond = explode(',', $value); break; case 'BYMINUTE': $this->byMinute = explode(',', $value); break; case 'BYHOUR': $this->byHour = explode(',', $value); break; case 'BYDAY': $this->byDay = explode(',', strtoupper($value)); break; case 'BYMONTHDAY': $this->byMonthDay = explode(',', $value); break; case 'BYYEARDAY': $this->byYearDay = explode(',', $value); break; case 'BYWEEKNO': $this->byWeekNo = explode(',', $value); break; case 'BYMONTH': $this->byMonth = explode(',', $value); break; case 'BYSETPOS': $this->bySetPos = explode(',', $value); break; case 'WKST': $this->weekStart = strtoupper($value); break; } } } // Parsing exception dates if (isset($this->baseEvent->EXDATE)) { foreach ($this->baseEvent->EXDATE as $exDate) { foreach (explode(',', (string) $exDate) as $exceptionDate) { $this->exceptionDates[] = DateTimeParser::parse($exceptionDate, $this->startDate->getTimeZone()); } } } }