diff --git a/examples/MyCal.ics b/examples/MyCal.ics index b2527a6..0df49f3 100644 --- a/examples/MyCal.ics +++ b/examples/MyCal.ics @@ -196,4 +196,32 @@ STATUS:CONFIRMED SUMMARY;LANGUAGE=en-gb:BYDAY Test 2 TRANSP:TRANSPARENT END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170111 +DTEND;VALUE=DATE:20170111 +DTSTAMP;TZID="GMT Standard Time":20170121T195741Z +RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=5;BYMONTH=1,2,3 +UID:f50e8b89a4a3b0070e0b687d03@google.com +CREATED:20170119T142040Z +DESCRIPTION;LANGUAGE=en-gb:BYMONTH Multiple Test 1 +LAST-MODIFIED:20170409T150000Z +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY;LANGUAGE=en-gb:BYMONTH Multiple Test 1 +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170405 +DTEND;VALUE=DATE:20170405 +DTSTAMP;TZID="GMT Standard Time":20170121T195741Z +RRULE:FREQ=YEARLY;BYMONTH=4,5,6;BYDAY=WE;COUNT=5 +UID:675f06aa795665ae50904ebf0e@google.com +CREATED:20170119T142040Z +DESCRIPTION;LANGUAGE=en-gb:BYMONTH Multiple Test 2 +LAST-MODIFIED:20170409T150000Z +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY;LANGUAGE=en-gb:BYMONTH Multiple Test 2 +TRANSP:TRANSPARENT +END:VEVENT END:VCALENDAR \ No newline at end of file diff --git a/src/ICal/ICal.php b/src/ICal/ICal.php index 27ad7a6..727e9d4 100644 --- a/src/ICal/ICal.php +++ b/src/ICal/ICal.php @@ -1077,6 +1077,11 @@ public function processRecurrences() $recurringTimestamp = $startTimestamp; $offset = "+{$interval} year"; + // Deal with BYMONTH + if (isset($rrules['BYMONTH']) && $rrules['BYMONTH'] !== '') { + $bymonths = explode(',', $rrules['BYMONTH']); + } + // Check if BYDAY rule exists if (isset($rrules['BYDAY']) && $rrules['BYDAY'] !== '') { while ($recurringTimestamp <= $until) { @@ -1087,23 +1092,93 @@ public function processRecurrences() $timezoneOffset = ($this->useTimeZoneWithRRules) ? $initialStart->getTimezone()->getOffset($recurringTimeZone) : 0; $yearRecurringTimestamp += ($timezoneOffset !== $initialStartOffset) ? $initialStartOffset - $timezoneOffset : 0; - $eventStartDesc = "{$this->dayOrdinals[$dayNumber]} {$this->weekdays[$weekDay]}" - . " of {$this->monthNames[$rrules['BYMONTH']]} " - . gmdate('Y H:i:s', $yearRecurringTimestamp); - $eventStartTimestamp = strtotime($eventStartDesc); - - if (intval($rrules['BYDAY']) === 0) { - $lastDayDesc = "last {$this->weekdays[$weekDay]}" - . " of {$this->monthNames[$rrules['BYMONTH']]} " + foreach ($bymonths as $bymonth) { + $eventStartDesc = "{$this->dayOrdinals[$dayNumber]} {$this->weekdays[$weekDay]}" + . " of {$this->monthNames[$bymonth]} " . gmdate('Y H:i:s', $yearRecurringTimestamp); + $eventStartTimestamp = strtotime($eventStartDesc); + + if (intval($rrules['BYDAY']) === 0) { + $lastDayDesc = "last {$this->weekdays[$weekDay]}" + . " of {$this->monthNames[$bymonth]} " + . gmdate('Y H:i:s', $yearRecurringTimestamp); + } else { + $lastDayDesc = "{$this->dayOrdinals[$dayNumber]} {$this->weekdays[$weekDay]}" + . " of {$this->monthNames[$bymonth]} " + . gmdate('Y H:i:s', $yearRecurringTimestamp); + } + $lastDayTimestamp = strtotime($lastDayDesc); + + do { + if ($eventStartTimestamp > $startTimestamp && $eventStartTimestamp < $until) { + $anEvent['DTSTART'] = date(self::DATE_TIME_FORMAT, $eventStartTimestamp) . ($isAllDayEvent || $initialStartTimeZoneName === 'Z' ? 'Z' : ''); + $anEvent['DTSTART_array'][1] = $anEvent['DTSTART']; + $anEvent['DTSTART_array'][2] = $eventStartTimestamp; + $anEvent['DTEND_array'] = $anEvent['DTSTART_array']; + $anEvent['DTEND_array'][2] += $eventTimestampOffset; + $anEvent['DTEND'] = date( + self::DATE_TIME_FORMAT, + $anEvent['DTEND_array'][2] + ) . ($isAllDayEvent || $initialEndTimeZoneName === 'Z' ? 'Z' : ''); + $anEvent['DTEND_array'][1] = $anEvent['DTEND']; + + $searchDate = $anEvent['DTSTART']; + $isExcluded = array_filter($anEvent['EXDATE_array'][1], function ($val) use ($searchDate) { + return $this->iCalDateToUnixTimestamp($searchDate) === $this->iCalDateToUnixTimestamp($val); + }); + + if (isset($anEvent['UID'])) { + if (isset($this->alteredRecurrenceInstances[$anEvent['UID']]) && in_array($yearRecurringTimestamp, $this->alteredRecurrenceInstances[$anEvent['UID']])) { + $isExcluded = true; + } + } + + if (!$isExcluded) { + $events[] = $anEvent; + $this->eventCount++; + + // If RRULE[COUNT] is reached then break + if (isset($rrules['COUNT'])) { + $countNb++; + + if ($countNb >= $countOrig) { + break 3; + } + } + } + } + + $eventStartTimestamp += 7 * 86400; + } while ($eventStartTimestamp <= $lastDayTimestamp); + } + + // Move forwards + $recurringTimestamp = strtotime($offset, $recurringTimestamp); + } + } else { + $day = gmdate('d', $startTimestamp); + + // Step through years + while ($recurringTimestamp <= $until) { + $yearRecurringTimestamp = $recurringTimestamp; + + // Adjust timezone from initial event + $recurringTimeZone = \DateTime::createFromFormat('U', $yearRecurringTimestamp); + $timezoneOffset = ($this->useTimeZoneWithRRules) ? $initialStart->getTimezone()->getOffset($recurringTimeZone) : 0; + $yearRecurringTimestamp += ($timezoneOffset !== $initialStartOffset) ? $initialStartOffset - $timezoneOffset : 0; + + $eventStartDescs = array(); + if (isset($rrules['BYMONTH']) && $rrules['BYMONTH'] !== '') { + foreach ($bymonths as $bymonth) { + array_push($eventStartDescs, "$day {$this->monthNames[$bymonth]} " . gmdate('Y H:i:s', $yearRecurringTimestamp)); + } } else { - $lastDayDesc = "{$this->dayOrdinals[$dayNumber]} {$this->weekdays[$weekDay]}" - . " of {$this->monthNames[$rrules['BYMONTH']]} " - . gmdate('Y H:i:s', $yearRecurringTimestamp); + array_push($eventStartDescs, $day . gmdate('F Y H:i:s', $yearRecurringTimestamp)); } - $lastDayTimestamp = strtotime($lastDayDesc); - do { + foreach ($eventStartDescs as $eventStartDesc) { + $eventStartTimestamp = strtotime($eventStartDesc); + if ($eventStartTimestamp > $startTimestamp && $eventStartTimestamp < $until) { $anEvent['DTSTART'] = date(self::DATE_TIME_FORMAT, $eventStartTimestamp) . ($isAllDayEvent || $initialStartTimeZoneName === 'Z' ? 'Z' : ''); $anEvent['DTSTART_array'][1] = $anEvent['DTSTART']; @@ -1141,70 +1216,6 @@ public function processRecurrences() } } } - - $eventStartTimestamp += 7 * 86400; - } while ($eventStartTimestamp <= $lastDayTimestamp); - - // Move forwards - $recurringTimestamp = strtotime($offset, $recurringTimestamp); - } - } else { - $day = gmdate('d', $startTimestamp); - - // Step through years - while ($recurringTimestamp <= $until) { - $yearRecurringTimestamp = $recurringTimestamp; - - // Adjust timezone from initial event - $recurringTimeZone = \DateTime::createFromFormat('U', $yearRecurringTimestamp); - $timezoneOffset = ($this->useTimeZoneWithRRules) ? $initialStart->getTimezone()->getOffset($recurringTimeZone) : 0; - $yearRecurringTimestamp += ($timezoneOffset !== $initialStartOffset) ? $initialStartOffset - $timezoneOffset : 0; - - // Add specific month dates - if (isset($rrules['BYMONTH']) && $rrules['BYMONTH'] !== '') { - $eventStartDesc = "$day {$this->monthNames[$rrules['BYMONTH']]} " . gmdate('Y H:i:s', $yearRecurringTimestamp); - } else { - $eventStartDesc = $day . gmdate('F Y H:i:s', $yearRecurringTimestamp); - } - - $eventStartTimestamp = strtotime($eventStartDesc); - - if ($eventStartTimestamp > $startTimestamp && $eventStartTimestamp < $until) { - $anEvent['DTSTART'] = date(self::DATE_TIME_FORMAT, $eventStartTimestamp) . ($isAllDayEvent || $initialStartTimeZoneName === 'Z' ? 'Z' : ''); - $anEvent['DTSTART_array'][1] = $anEvent['DTSTART']; - $anEvent['DTSTART_array'][2] = $eventStartTimestamp; - $anEvent['DTEND_array'] = $anEvent['DTSTART_array']; - $anEvent['DTEND_array'][2] += $eventTimestampOffset; - $anEvent['DTEND'] = date( - self::DATE_TIME_FORMAT, - $anEvent['DTEND_array'][2] - ) . ($isAllDayEvent || $initialEndTimeZoneName === 'Z' ? 'Z' : ''); - $anEvent['DTEND_array'][1] = $anEvent['DTEND']; - - $searchDate = $anEvent['DTSTART']; - $isExcluded = array_filter($anEvent['EXDATE_array'][1], function ($val) use ($searchDate) { - return $this->iCalDateToUnixTimestamp($searchDate) === $this->iCalDateToUnixTimestamp($val); - }); - - if (isset($anEvent['UID'])) { - if (isset($this->alteredRecurrenceInstances[$anEvent['UID']]) && in_array($yearRecurringTimestamp, $this->alteredRecurrenceInstances[$anEvent['UID']])) { - $isExcluded = true; - } - } - - if (!$isExcluded) { - $events[] = $anEvent; - $this->eventCount++; - - // If RRULE[COUNT] is reached then break - if (isset($rrules['COUNT'])) { - $countNb++; - - if ($countNb >= $countOrig) { - break 2; - } - } - } } // Move forwards