Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add early date range filter to parser #190

Merged
merged 1 commit into from
Nov 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -607,10 +623,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