Skip to content

Latest commit

 

History

History
234 lines (189 loc) · 37.6 KB

README.md

File metadata and controls

234 lines (189 loc) · 37.6 KB

PHP ICal Package

ICal package for php >=7.4 based on u01jmg3/ics-parser parser and sabre-io/vobject packages.

Roadmap:

  • Get rid of support old php versions (5.6 - 7.3)
  • Replace logging calls with exceptions
  • Strict types
  • Event->toArray() method
  • Classes for ics file generation

Installation

Requirements

Setup

  • Install Composer
    • Add the following dependency to composer.json
    • To access new features you can require dev-master

Running tests

composer test

How to use

How to instantiate the Parser

What will the parser return?

  • Each key/value pair from the iCal file will be parsed creating an associative array for both the calendar and every event it contains.

  • Also injected will be content under dtstart_tz and dtend_tz for accessing start and end dates with time zone data applied.

  • Where possible DateTime objects are used and returned.

    • ℹ️ Note the parser is limited to relative date formats which can inhibit how complex recurrence rule parts are processed (e.g. BYDAY combined with BYSETPOS)
    // Dump the whole calendar
    var_dump($ical->cal);
    
    // Dump every event
    var_dump($ical->events());
  • Also included are special {property}_array arrays which further resolve the contents of a key/value pair.

    // Dump a parsed event's start date
    var_dump($event->dtstart_array);
    
    // array (size=4)
    //   0 =>
    //     array (size=1)
    //       'TZID' => string 'America/Detroit' (length=15)
    //   1 => string '20160409T090000' (length=15)
    //   2 => int 1460192400
    //   3 => string 'TZID=America/Detroit:20160409T090000' (length=36)

When Parsing an iCal Feed

Parsing iCal/iCalendar/ICS resources can pose several challenges. One challenge is that the specification is a moving target; the original RFC has only been updated four times in ten years. The other challenge is that vendors were both liberal (read: creative) in interpreting the specification and productive implementing proprietary extensions.

However, what impedes efficient parsing most directly are recurrence rules for events. This library parses the original calendar into an easy to work with memory model. This requires that each recurring event is expanded or exploded. Hence, a single event that occurs daily will generate a new event instance for each day as this parser processes the calendar ($defaultSpan limits this). To get an idea how this is done take a look at the call graph.

As a consequence the entire calendar is parsed line-by-line, and thus loaded into memory, first. As you can imagine large calendars tend to get huge when exploded i.e. with all their recurrence rules evaluated. This is exacerbated when old calendars do not remove past events as they get fatter and fatter every year.

This limitation is particularly painful if you only need a window into the original calendar. It seems wasteful to parse the entire fully exploded calendar into memory if you later are going to call the eventsFromInterval() or eventsFromRange() on it.

In late 2018 #190 added the option to drop all events outside a given range very early in the parsing process at the cost of some precision (time zone calculations are not calculated at that point). This massively reduces the total time for parsing a calendar. The same goes for memory consumption. The precondition is that you know upfront that you don't care about events outside a given range.

Let's say you are only interested in events from yesterday, today and tomorrow. To compensate for the fact that the tricky time zone transformations and calculations have not been executed yet by the time the parser has to decide whether to keep or drop an event you can set it to filter for +-2d instead of +-1d. Once it is done you would then call eventsFromRange() with +-1d to get precisely the events in the window you are interested in. That is what the variables $filterDaysBefore and $filterDaysAfter are for.

In Q1 2019 #213 further improved the performance by immediately dropping non-recurring events once parsed if they are outside that fuzzy window. This greatly reduces the maximum memory consumption for large calendars. PHP by default does not allocate more than 128MB heap and would otherwise crash with Fatal error: Allowed memory size of 134217728 bytes exhausted. It goes without saying that recurring events first need to be evaluated before non-fitting events can be dropped.


API

ICal API

Variables

Name Configurable Default Value Description
$alarmCount ✖️ N/A Tracks the number of alarms in the current iCal feed
$cal ✖️ N/A The parsed calendar
$defaultSpan ☑️ 2 The value in years to use for indefinite, recurring events
$defaultTimeZone ☑️ System default Enables customisation of the default time zone
$defaultWeekStart ☑️ MO The two letter representation of the first day of the week
$disableCharacterReplacement ☑️ 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 ✖️ N/A Tracks the number of events in the current iCal feed
$filterDaysAfter ☑️ null When 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 +/- 1 day 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.
$filterDaysBefore ☑️ null When set the parser will ignore all events more than roughly this many days before now. See $filterDaysAfter above for more details.
$freeBusyCount ✖️ N/A Tracks the free/busy count in the current iCal feed
$httpBasicAuth ✖️ array() Holds the username and password for HTTP basic authentication
$httpUserAgent ☑️ null Holds the custom User Agent string header
$httpAcceptLanguage ✖️ null Holds the custom Accept Language request header, e.g. "en" or "de"
$httpProtocolVersion ✖️ null Holds the custom HTTP Protocol version, e.g. "1.0" or "1.1"
$shouldFilterByWindow ✖️ false true if either $filterDaysBefore or $filterDaysAfter are set
$skipRecurrence ☑️ false Toggles whether to skip the parsing of recurrence rules
$todoCount ✖️ N/A Tracks the number of todos in the current iCal feed
$windowMaxTimestamp ✖️ null If $filterDaysBefore or $filterDaysAfter are set then the events are filtered according to the window defined by this field and $windowMinTimestamp
$windowMinTimestamp ✖️ null If $filterDaysBefore or $filterDaysAfter are set then the events are filtered according to the window defined by this field and $windowMaxTimestamp

Methods

Method Parameter(s) Visibility Description
__construct $files = false, $options = array() public Creates the ICal object
initFromFile $file protected Initialises lines from a file
initFromLines $lines protected Initialises the parser using an array containing each line of iCal content
initFromString $string protected Initialises lines from a string
initFromUrl $url, $username = null, $password = null, $userAgent = null, $acceptLanguage = null protected Initialises lines from a URL. Accepts a username/password combination for HTTP basic authentication, a custom User Agent string and the accepted client language
addCalendarComponentWithKeyAndValue $component, $keyword, $value protected Add one key and value pair to the $this->cal array
calendarDescription - public Returns the calendar description
calendarName - public Returns the calendar name
calendarTimeZone $ignoreUtc public Returns the calendar time zone
cleanData $data protected Replaces curly quotes and other special characters with their standard equivalents
eventsFromInterval $interval public Returns a sorted array of events following a given string, or false if no events exist in the range
eventsFromRange $rangeStart = false, $rangeEnd = false public Returns a sorted array of events in a given range, or an empty array if no events exist in the range
events - public Returns an array of Events
fileOrUrl $filename protected Reads an entire file or URL into an array
filterValuesUsingBySetPosRRule $bysetpos, $valueslist protected Filters a provided values-list by applying a BYSETPOS RRule
freeBusyEvents - public Returns an array of arrays with all free/busy events
getDaysOfMonthMatchingByDayRRule $bydays, $initialDateTime protected Find all days of a month that match the BYDAY stanza of an RRULE
getDaysOfMonthMatchingByMonthDayRRule $byMonthDays, $initialDateTime protected Find all days of a month that match the BYMONTHDAY stanza of an RRULE
getDaysOfYearMatchingByDayRRule $byDays, $initialDateTime protected Find all days of a year that match the BYDAY stanza of an RRULE
getDaysOfYearMatchingByMonthDayRRule $byMonthDays, $initialDateTime protected Find all days of a year that match the BYMONTHDAY stanza of an RRULE
getDaysOfYearMatchingByWeekNoRRule $byWeekNums, $initialDateTime protected Find all days of a year that match the BYWEEKNO stanza of an RRULE
getDaysOfYearMatchingByYearDayRRule $byYearDays, $initialDateTime protected Find all days of a year that match the BYYEARDAY stanza of an RRULE
hasEvents - public Returns a boolean value whether the current calendar has events or not
iCalDateToDateTime $icalDate public Returns a DateTime object from an iCal date time format
iCalDateToUnixTimestamp $icalDate public Returns a Unix timestamp from an iCal date time format
iCalDateWithTimeZone $event, $key, $format = DATE_TIME_FORMAT public Returns a date adapted to the calendar time zone depending on the event TZID
doesEventStartOutsideWindow $event protected Determines whether the event start date is outside $windowMinTimestamp / $windowMaxTimestamp
isFileOrUrl $filename protected Checks if a filename exists as a file or URL
isOutOfRange $calendarDate, $minTimestamp, $maxTimestamp protected Determines whether a valid iCalendar date is within a given range
isValidCldrTimeZoneId $timeZone protected Checks if a time zone is a valid CLDR time zone
isValidDate $value public Checks if a date string is a valid date
isValidIanaTimeZoneId $timeZone protected Checks if a time zone is a valid IANA time zone
isValidWindowsTimeZoneId $timeZone protected Checks if a time zone is a recognised Windows (non-CLDR) time zone
isValidTimeZoneId $timeZone protected Checks if a time zone is valid (IANA, CLDR, or Windows)
keyValueFromString $text public Gets the key value pair from an iCal string
parseLine $line protected Parses a line from an iCal file into an array of tokens
mb_chr $code protected Provides a polyfill for PHP 7.2's mb_chr(), which is a multibyte safe version of chr()
mb_str_replace $search, $replace, $subject, $count = 0 protected Replaces all occurrences of a search string with a given replacement string
escapeParamText $candidateText protected Places double-quotes around texts that have characters not permitted in parameter-texts, but are permitted in quoted-texts.
parseDuration $date, $duration, $format = 'U' protected Parses a duration and applies it to a date
parseExdates $event public Parses a list of excluded dates to be applied to an Event
processDateConversions - protected Processes date conversions using the time zone
processEvents - protected Performs admin tasks on all events as read from the iCal file
processRecurrences - protected Processes recurrence rules
reduceEventsToMinMaxRange protected Reduces the number of events to the defined minimum and maximum range
removeLastEventIfOutsideWindowAndNonRecurring protected Removes the last event (i.e. most recently parsed) if its start date is outside the window spanned by $windowMinTimestamp / $windowMaxTimestamp
removeUnprintableChars $data protected Removes unprintable ASCII and UTF-8 characters
resolveIndicesOfRange $indexes, $limit protected Resolves values from indices of the range 1 -> $limit
sortEventsWithOrder $events, $sortOrder = SORT_ASC public Sorts events based on a given sort order
timeZoneStringToDateTimeZone $timeZoneString public Returns a DateTimeZone object based on a string containing a time zone name.
unfold $lines protected Unfolds an iCal file in preparation for parsing

Constants

Name Description
DATE_TIME_FORMAT_PRETTY Default pretty date time format to use
DATE_TIME_FORMAT Default date time format to use
ICAL_DATE_TIME_TEMPLATE String template to generate an iCal date time
ISO_8601_WEEK_START First day of the week, as defined by ISO-8601
RECURRENCE_EVENT Used to isolate generated recurrence events
SECONDS_IN_A_WEEK The number of seconds in a week
TIME_FORMAT Default time format to use
TIME_ZONE_UTC UTC time zone string
UNIX_FORMAT Unix timestamp date format
UNIX_MIN_YEAR The year Unix time began

Event API (extends ICal API)

Methods

Method Parameter(s) Visibility Description
__construct $data = array() public Creates the Event object
prepareData $value protected Prepares the data for output
printData $html = HTML_TEMPLATE public Returns Event data excluding anything blank within an HTML template
snakeCase $input, $glue = '_', $separator = '-' protected Converts the given input to snake_case

Constants

Name Description
HTML_TEMPLATE String template to use when pretty printing content

Tools for Testing