Skip to content

Commit

Permalink
Merge pull request #190 from marcelstoer/feat/early-range-filter
Browse files Browse the repository at this point in the history
Support reducing the number of events to be parsed
  • Loading branch information
u01jmg3 authored Nov 4, 2018
2 parents b891978 + 8014e87 commit e302096
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
| `$defaultWeekStart` | :ballot_box_with_check: | `MO` | The two letter representation of the first day of the week |
| `$disableCharacterReplacement` | :ballot_box_with_check: | `false` | Toggles whether to disable all character replacement. Will replace curly quotes and other special characters with their standard equivalents if `false`. Can be a costly operation! |
| `$eventCount` | :heavy_multiplication_x: | N/A | Tracks the number of events in the current iCal feed |
| `$filterDaysAfter` | :ballot_box_with_check: | N/A | With this set the parser will ignore all events more than roughly this many days _after_ now. To be on the safe side it is advised that you make the filter window +-1d larger than necessary. For performance reasons this filter is applied before any date and time zone calculations are done. Hence, depending the time zone settings of the parser and the calendar the cut-off date is not "calibrated". You can then use `$ical->eventsFromRange()` to precisely shrink the window. See [#184](https://github.com/u01jmg3/ics-parser/issues/184#issuecomment-423669971) for rationale and performance analysis.|
| `$filterDaysBefore` | :ballot_box_with_check: | N/A | With this set the parser will ignore all events more than roughly this many days _before_ now. See `$filterDaysAfter` above for more details. |
| `$freeBusyCount` | :heavy_multiplication_x: | N/A | Tracks the free/busy count in the current iCal feed |
| `$replaceWindowsTimeZoneIds` | :ballot_box_with_check: | `false` | Toggles whether to replace (non-CLDR) Windows time zone IDs with their IANA equivalent e.g. "Mountain Standard Time" would be replaced with "America/Denver". As there are 130+ Windows time zone IDs that need to be searched and replaced this flag should only be turned on if you know that your calendar file contains such time zone IDs. **Microsoft Exchange** calendars are often seen using such IDs. |
| `$skipRecurrence` | :ballot_box_with_check: | `false` | Toggles whether to skip the parsing of recurrence rules |
Expand Down
53 changes: 53 additions & 0 deletions src/ICal/ICal.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ class ICal
*/
public $replaceWindowsTimeZoneIds = false;

/**
* With this being non-null the parser will ignore all events more than roughly this many days before now.
*
* @var integer
*/
public $filterDaysAfter;

/**
* With this being non-null the parser will ignore all events more than roughly this many days after now.
*
* @var integer
*/
public $filterDaysBefore;

/**
* The parsed calendar
*
Expand Down Expand Up @@ -222,6 +236,8 @@ class ICal
'defaultTimeZone',
'defaultWeekStart',
'disableCharacterReplacement',
'filterDaysAfter',
'filterDaysBefore',
'replaceWindowsTimeZoneIds',
'skipRecurrence',
'useTimeZoneWithRRules',
Expand Down Expand Up @@ -609,10 +625,47 @@ protected function initLines(array $lines)
}
}

if (!is_null($this->filterDaysAfter) || !is_null($this->filterDaysBefore)) {
$this->reduceEventsToMinMaxRange();
}

$this->processDateConversions();
}
}

function reduceEventsToMinMaxRange()
{
$events = (isset($this->cal['VEVENT'])) ? $this->cal['VEVENT'] : array();

if (empty($events)) {
return false;
}

// ideally you would use PHP_INT_MIN but that was only introduced with PHP 7
$minTimestamp = is_null($this->filterDaysBefore) ? -2147483648 : (new \DateTime('now'))->sub(new \DateInterval('P' . $this->filterDaysBefore . 'D'))->getTimestamp();
$maxTimestamp = is_null($this->filterDaysAfter) ? PHP_INT_MAX : (new \DateTime('now'))->add(new \DateInterval('P' . $this->filterDaysAfter . 'D'))->getTimestamp();

foreach ($events as $key => $anEvent) {
if (!$this->isValidDate($anEvent['DTSTART']) || $this->isOutOfRange($anEvent['DTSTART'], $minTimestamp, $maxTimestamp)) {
unset($events[$key]);
$this->eventCount--;

continue;
}
}

$this->cal['VEVENT'] = $events;
}


private function isOutOfRange($eventStart, $minTimestamp, $maxTimestamp)
{
// at this point $dtstart is guaranteed to be stripped of any timezone identifier i.e. it's a pure timestamp
// and won't look like e.g. DTSTART;TZID=US-Eastern:19980119T020000
$eventStartTimestamp = strtotime(explode("T", $eventStart)[0]);
return $eventStartTimestamp < $minTimestamp || $eventStartTimestamp > $maxTimestamp;
}

/**
* Unfolds an iCal file in preparation for parsing
* (https://icalendar.org/iCalendar-RFC-5545/3-1-content-lines.html)
Expand Down

0 comments on commit e302096

Please sign in to comment.