Giter Site home page Giter Site logo

ics-parser's Introduction

PHP ICS Parser

Latest Stable Release Total Downloads


Installation

Requirements

Setup

  • Install Composer
    • Add the following dependency to composer.json
      • ⚠️ Note with Composer the owner is johngrogg and not u01jmg3
    • To access the latest stable branch (v3) use the following
      • To access new features you can require dev-master

        {
            "require": {
                "johngrogg/ics-parser": "^3"
            }
        }

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)

Are you using Outlook?

Outlook has a quirk where it requires the User Agent string to be set in your request headers.

We have done this for you by injecting a default User Agent string, if one has not been specified.

If you wish to provide your own User agent string you can do so by using the httpUserAgent argument when creating your ICal object.

$ical = new ICal($url, array('httpUserAgent' => 'A Different User Agent'));

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
initFile $file protected Initialises lines from a file
initLines $lines protected Initialises the parser using an array containing each line of iCal content
initString $string protected Initialises lines from a string
initUrl $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
cleanCharacters $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
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
getDefaultTimeZone $forceReturnSystemDefault private Returns the default time zone if set or falls back to the system default if not set
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()
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 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

Credits

  • Jonathan Goode (programming, bug fixing, codebase enhancement, coding standard adoption)
  • s0600204 (major enhancements to RRULE support, many bug fixes and other contributions)

Tools for Testing

ics-parser's People

Contributors

antoniocoratelli avatar asakapab0i avatar bairdj avatar dependabot-preview[bot] avatar dependabot[bot] avatar dxdc avatar easy-es avatar gnatsnapper avatar grimmlink avatar hikariii avatar hubermat avatar jandor64 avatar jeromecombes avatar johngrogg avatar lucafilosofi avatar marcelstoer avatar noec764 avatar petermillgram avatar phalkunz avatar roelverdonschot avatar room34 avatar s0600204 avatar scrivna avatar tacman avatar tiefpunkt avatar tkoop avatar todeveni avatar torstehu avatar u01jmg3 avatar vastoa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ics-parser's Issues

Recurring Events: last event gets cut off and is not part of the $events array

Hey there,

I recently (this week) discovered that this happens with events from last year. Especially in calendar2.ics you can reproduce this when using the example.php from this repository with it. When you compare the output with the actual .ics file (i.e. look at the last lines or import it in a calendar application) you will notice in the week from 18th of January 2016 and 24th of January 2016 basically all recurring events end and they are not included in $events.

I am currently taking a look at the 260+ lines function process_recurrences() and found a hint:
The switch case "WEEKLY" in switch ($frequency) starting at line 425 is not localized and ignores the timezone by always using the american scheme for weekdays (week starts on sundays instead of mondays). I can't confirm this yet, but I will look into this tomorrow.

calendar2.ics.zip

calendar.ics.zip

Test Issue

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).

Parsing recurring events doesn't account for event that has a changed DTSTART and/or DTEND listed in the file.

I have a recurring event on an ICAL file I pull from Google Apps. We recently had to modify the time of one instance of the recurring events. The modified event came up in the list at the proper new time, but the parser still generated and returned the event at the original time as well. It looks like events would be parsed for the UID of the recurring event and find out if for that RECURRENCE-ID the event differs from the RRULE setting for DTSTART and/or DTEND, and if so replace that particular event in the array generated from the RRULEs.

The events are in question are listed, with some redactions. There should be enough to replicate the issue.

Recurring event rule:

BEGIN:VEVENT
DTSTART;TZID=America/Chicago:20160406T103000
DTEND;TZID=America/Chicago:20160406T110000
RRULE:FREQ=WEEKLY;BYDAY=WE
DTSTAMP:20160426T220705Z
UID:[email protected]
CREATED:20160406T175048Z
LAST-MODIFIED:20160406T210132Z
LOCATION:LOCATION
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:MEETING
TRANSP:OPAQUE
END:VEVENT

And a modified instance of the recurring event:

BEGIN:VEVENT
DTSTART;TZID=America/Chicago:20160427T130000
DTEND;TZID=America/Chicago:20160427T133000
DTSTAMP:20160426T220705Z
UID:[email protected]
RECURRENCE-ID;TZID=America/Chicago:20160427T103000
CREATED:20160406T175048Z
LAST-MODIFIED:20160425T184416Z
LOCATION:LOCATION
SEQUENCE:2
STATUS:CONFIRMED
SUMMARY:MEETING
TRANSP:OPAQUE
END:VEVENT

Support for unimplemented options allowed in `RRULE`


Complete Option Description
BYMONTH If given, it must be either an integer, or a sequence of integers, meaning the months to apply the recurrence to.
BYMONTHDAY If given, it must be either an integer, or a sequence of integers, meaning the month days to apply the recurrence to.
✔ (1770648) WKST The week start day. Must be one of the RRule.MO, RRule.TU, RRule.WE constants, or an integer, specifying the first day of the week. This will affect recurrences based on weekly periods. The default week start is RRule.MO.
✔ (4d4fbd1, #271) BYSETPOS If given, it must be either an integer, or a sequence of integers, positive or negative. Each given integer will specify an occurrence number, corresponding to the nth occurrence of the rule inside the frequency period. For example, a BYSETPOS of -1 if combined with a RRule.MONTHLY frequency, and a BYWEEKDAY of (RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR), will result in the last work day of every month.
✔ (#271) BYYEARDAY If given, it must be either an integer, or a sequence of integers, meaning the year days to apply the recurrence to.
✔ (#271) BYWEEKNO If given, it must be either an integer, or a sequence of integers, meaning the week numbers to apply the recurrence to. Week numbers have the meaning described in ISO8601, that is, the first week of the year is that containing at least four days of the new year.
BYHOUR If given, it must be either an integer, or a sequence of integers, meaning the hours to apply the recurrence to.
BYMINUTE If given, it must be either an integer, or a sequence of integers, meaning the minutes to apply the recurrence to.
BYSECOND If given, it must be either an integer, or a sequence of integers, meaning the seconds to apply the recurrence to.
Not part of RFC5545 spec BYWEEKDAY If given, it must be either an integer (RRule.MO === 0), a sequence of integers, one of the weekday constants (RRule.MO, RRule.TU, etc.), or a sequence of these constants. When given, these variables will define the weekdays where the recurrence will be applied. It's also possible to use an argument n for the weekday instances, which will mean the nth occurrence of this weekday in the period. For example, with RRule.MONTHLY, or with RRule.YEARLY and BYMONTH, using RRule.FR.nth(+1) or RRule.FR.nth(-1) in BYWEEKDAY will specify the first or last Friday of the month where the recurrence happens. Notice that the RFC documentation, this is specified as BYDAY, but was renamed to avoid the ambiguity of that argument.

Inline timezones not taken into account

When loading an event, the code will come across a line with a date and time, such as DTSTART.

This value can be in the form TZID={timezone}:{datetime}, for example: TZID=America/New_York:20160907T120000

In this case and example, the value is broken up and stored as an array, and then the DTSTART property is overwritten with just the datetime value.

The problem arises later on, when iCalToUnixTimestamp is called, and is passed the value of DTSTART. The DTSTART that no longer has a TZID param included.

Thus meaning that the resultant unix timestamp does not have the original inline timezone taken into account, leading to the timestamp being incorrect.

DURATION not supported

http://www.kanzaki.com/docs/ical/duration.html

The Duration Property is currently not parsed and ics-parser throws an error when there is no DTEND Property.

Something like this would not work:

BEGIN:VEVENT
SUMMARY:Test
DTSTART:20160425T150000Z
DURATION:PT1H
UID:calendar-62-e7c39bf02382917349672271dd781c89
END:VEVENT

Recurring all day events with time information

Parser adds T000000 in allday events, it should be date only

SUMMARY: test 1
DTSTART: 20160202T000000 - UNIX-Time: 1454351400
DTEND: 20160203T000000
DTSTAMP: 20110121T195741Z
UID: [email protected]
CREATED: 20110119T141901Z
LAST-MODIFIED: 20150409T150000Z
DESCRIPTION: 
LOCATION: 
SEQUENCE: 
STATUS: CONFIRMED
TRANSP: TRANSPARENT
ORGANIZER: 
ATTENDEE(S): 

Stop when RRULE[COUNT] is reached

If we have a weekly recurrence with 2 days in weeks and the count parameter set to 4, the function will count 4 weeks and will return 8 events, while it should return 4 events.

Example:

BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20160705T090000
DTEND;TZID=Europe/Paris:20160705T100000
RRULE:FREQ=WEEKLY;COUNT=4;BYDAY=TU,SU
DTSTAMP:20160514T170815Z
UID:[email protected]
CREATED:20150624T141227Z
DESCRIPTION:
LAST-MODIFIED:20160707T075813Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:TEST WKST MO
TRANSP:OPAQUE
END:VEVENT

To ICS support

Hello,

Thanks for the great script, it works like a charm.

IMHO the only missing feature is the ability to print the ICS back to it's original format. Sometimes you just want to parse the ICS, make some modifications and then write it back to an ICS.

All the best,
G.

Reoccurring DTSTART times interpreted wrong

Calendar entry is for 9am-10am CST, but when i do the following it returns 6pm.
php code:

<?php
require 'class.iCalReader.php';
$ical   = new ICal("test.ics");

$today = date("Y-m-d");
$nextweek = date("Y-m-d",strtotime("+1 week"));

$weeksevents = $ical->eventsFromRange("$today", "$nextweek");

foreach ($weeksevents as $event) {
    echo 'SUMMARY: ' . $event['SUMMARY'] . '<br/>';
    echo 'DTSTART: ' . $event['DTSTART'] . ' - UNIX-Time: ' . $ical->iCalDateToUnixTimestamp($event['DTSTART']) . '<br/>';
    echo 'DTEND: ' . $event['DTEND'] . '<br/>';
    echo 'DTSTAMP: ' . $event['DTSTAMP'] . '<br/>';
    echo 'UID: ' . $event['UID'] . '<br/>';
    echo 'CREATED: ' . $event['CREATED'] . '<br/>';
    echo 'DESCRIPTION: ' . $event['DESCRIPTION'] . '<br/>';
    echo 'LAST-MODIFIED: ' . $event['LAST-MODIFIED'] . '<br/>';
    echo 'LOCATION: ' . $event['LOCATION'] . '<br/>';
    echo 'SEQUENCE: ' . $event['SEQUENCE'] . '<br/>';
    echo 'STATUS: ' . $event['STATUS'] . '<br/>';
    echo 'TRANSP: ' . $event['TRANSP'] . '<br/>';
    echo '<hr/>';
}
?>

php output:

SUMMARY: test reoccuring
DTSTART: 20150507T180000 - UNIX-Time: 1431039600
DTEND: 20150507T180000
DTSTAMP: 20150507T181720Z
UID: [email protected]
CREATED: 20150507T113804Z
DESCRIPTION: 
LAST-MODIFIED: 20150507T113804Z
LOCATION: 
SEQUENCE: 0
STATUS: CONFIRMED
TRANSP: OPAQUE

ics file:

BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:events
X-WR-TIMEZONE:America/Chicago
BEGIN:VTIMEZONE
TZID:America/New_York
X-LIC-LOCATION:America/New_York
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARDevents
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:America/Chicago
X-LIC-LOCATION:America/Chicago
BEGIN:DAYLIGHT
TZOFFSETFROM:-0600
TZOFFSETTO:-0500
TZNAME:CDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0500
TZOFFSETTO:-0600
TZNAME:CST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Chicago:20150430T090000
DTEND;TZID=America/Chicago:20150430T100000
RRULE:FREQ=WEEKLY;BYDAY=TH
DTSTAMP:20150507T181720Z
UID:[email protected]
CREATED:20150507T113804Z
DESCRIPTION:
LAST-MODIFIED:20150507T113804Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:test reoccurring
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR

Make iCalDateToUnixTimestamp() static

iCalDateToUnixTimestamp() does not rely on any internal state of the ICS class, it's output is fully deterministic from its input parameters.

It should be a static member function.

Expect a PR :)

ICS from Office 365 not skipping EXDATE

A bunch of my recurring events were showing up that were individually deleted out of a series. I was able to hack around it. Any idea what is wrong with the event below that would be causing it not to skip?

BEGIN:VEVENT
DESCRIPTION:\n\n
RRULE:FREQ=MONTHLY;UNTIL=20160908T223000Z;INTERVAL=1;BYDAY=2TH
EXDATE;TZID=Eastern Standard Time:20160609T183000,20160714T183000,20160811T
 183000
UID:040000008200E00074C5B7101A82E00800000000A0CCE414EE70CF01000000000000000
 010000000BE1B73167CFE704F9795393E7E745F5D
SUMMARY:Personnel
DTSTART;TZID=Eastern Standard Time:20160414T183000
DTEND;TZID=Eastern Standard Time:20160414T193000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20160706T150005Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:202
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:1
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT

Events that repeat forever

Hi,

this is a follow-up on 2fd2db6 and 197975b.

The current behavior is inconsistent if I understand the code correctly. Repetitions from an events are returned but only if the event does not repeat forever. In that case, only the first instance of the event is returned.

I think the cleanest solution for the user of the API is that eventsFromRange() just returns all events in the correct range, with all repetitions. It's just not clear what should happen if the end date of the range is not given. This parameter might even be mandatory: if no recurring events are expected, then one can still set it explicitly far into the future (even the maximal timestamp), or just use getEvents().

This could be implemented by generating some of the events (next four years) and generating more only once eventsFromRange() needs them.

Support multiline values

The ics file I am parsing includes some values that span lines. When the value wraps a line, the next line begins with a space. I was able to pre-process the file to support this by removing the string "newline-space" (\n). This should be handled by the parser, though.

Need PHP 5.3

Hi,

$is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; });

is not working in PHP 5.2, you need PHP 5.3:

syntax error, unexpected T_FUNCTION in class.iCal Reader.php on line 415

Or you have to define the anonymous function outside the array_filter. I would to it but I don't know how to transmit the parameters to the function ($search_date...)

Problems with SUMMARY and DTSTART and DTEND

The DTSTART and DTEND key is not set, but they are placed in the summary?

array:36 [
  "DESCRIPTION" => "\n\n"
  "DESCRIPTION_array" => array:1 [
    0 => "\n\n"
  ]
  "RRULE" => "FREQ=WEEKLY;UNTIL=20160607T063000Z;INTERVAL=1;BYDAY=TU;WKST=MO"
  "RRULE_array" => array:1 [
    0 => "FREQ=WEEKLY;UNTIL=20160607T063000Z;INTERVAL=1;BYDAY=TU;WKST=MO"
  ]
  "UID" => "040000008200E00074C5B7101A82E008000000004047101ECC1ED1010000000000000000100000000E778727D1444248A18ACF76846D6501"
  "UID_array" => array:2 [
    0 => "040000008200E00074C5B7101A82E008000000004047101ECC1ED101000000000000000"
    1 => "040000008200E00074C5B7101A82E008000000004047101ECC1ED1010000000000000000100000000E778727D1444248A18ACF76846D6501"
  ]
  "SUMMARY" => "Core moedeDTSTART;TZID=Romance Standard Time:20151117T083000DTEND;TZID=Romance Standard Time:20151117T100000"
  "SUMMARY_array" => array:3 [
    0 => "Core moede"
    1 => "Core moedeDTSTART;TZID=Romance Standard Time:20151117T083000"
    2 => "Core moedeDTSTART;TZID=Romance Standard Time:20151117T083000DTEND;TZID=Romance Standard Time:20151117T100000"
  ]
  "CLASS" => "PUBLIC"
  "CLASS_array" => array:1 [
    0 => "PUBLIC"
  ]
  "PRIORITY" => "5"
  "PRIORITY_array" => array:1 [
    0 => "5"
  ]
  "DTSTAMP" => "20160207T160211Z"
  "DTSTAMP_array" => array:1 [
    0 => "20160207T160211Z"
  ]
  "TRANSP" => "OPAQUE"
  "TRANSP_array" => array:1 [
    0 => "OPAQUE"
  ]
  "STATUS" => "CONFIRMED"
  "STATUS_array" => array:1 [
    0 => "CONFIRMED"
  ]
  "SEQUENCE" => "0"
  "SEQUENCE_array" => array:1 [
    0 => "0"
  ]
  "LOCATION" => "MTHQ"
  "LOCATION_array" => array:1 [
    0 => "MTHQ"
  ]
  "X-MICROSOFT-CDO-APPT-SEQUENCE" => "0"
  "X-MICROSOFT-CDO-APPT-SEQUENCE_array" => array:1 [
    0 => "0"
  ]
  "X-MICROSOFT-CDO-BUSYSTATUS" => "BUSY"
  "X-MICROSOFT-CDO-BUSYSTATUS_array" => array:1 [
    0 => "BUSY"
  ]
  "X-MICROSOFT-CDO-INTENDEDSTATUS" => "BUSY"
  "X-MICROSOFT-CDO-INTENDEDSTATUS_array" => array:1 [
    0 => "BUSY"
  ]
  "X-MICROSOFT-CDO-ALLDAYEVENT" => "FALSE"
  "X-MICROSOFT-CDO-ALLDAYEVENT_array" => array:1 [
    0 => "FALSE"
  ]
  "X-MICROSOFT-CDO-IMPORTANCE" => "1"
  "X-MICROSOFT-CDO-IMPORTANCE_array" => array:1 [
    0 => "1"
  ]
  "X-MICROSOFT-CDO-INSTTYPE" => "1"
  "X-MICROSOFT-CDO-INSTTYPE_array" => array:1 [
    0 => "1"
  ]
  "X-MICROSOFT-DISALLOW-COUNTER" => "FALSE"
  "X-MICROSOFT-DISALLOW-COUNTER_array" => array:1 [
    0 => "FALSE"
  ]
]
  • ICS:
BEGIN:VEVENT
DESCRIPTION:\n\n
RRULE:FREQ=WEEKLY;UNTIL=20160607T063000Z;INTERVAL=1;BYDAY=TU;WKST=MO
UID:040000008200E00074C5B7101A82E008000000004047101ECC1ED101000000000000000
 0100000000E778727D1444248A18ACF76846D6501
SUMMARY:Core møde
DTSTART;TZID=Romance Standard Time:20151117T083000
DTEND;TZID=Romance Standard Time:20151117T100000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20160207T153033Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:MTHQ
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:1
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT

Malformed Output

I'm parsing an outlook/exchange calendar. Sometimes we use double quotation marks in the summary field and if the summary is split in two lines, each line having one double quotation mark, the result is an empty summary and a malformed description.
If I change the double quotation marks to inverted commas everything is fine. I think the parser is acting wrong in this case, could you please check?

Example for tests:

BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
X-WR-CALNAME:Schulgemeinschaft
BEGIN:VTIMEZONE
TZID:W. Europe Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DESCRIPTION:123\n\n
SUMMARY:Rücklauf der Kontrollausdrucke und der Bögen (9-10) "Zur Allgemein
 en Situation" der Firma -> Summary is Missing, Description wrong.
DTSTART;VALUE=DATE:20150929
DTEND;VALUE=DATE:20150930
UID:040000008200E00074C5B7101A82E00800000000203D8F02E7D4D001000000000000000
 010000000A717B53D15823B4FBA747B49155B9A61
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20151230T181202Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:FREE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
BEGIN:VEVENT
DESCRIPTION:123\n\n
SUMMARY:Rücklauf der Kontrollausdrucke und der Bögen (9-10) 'Zur Allgemein
 en Situation' der Firma -> Description OK, Summary OK.
DTSTART;VALUE=DATE:20150929
DTEND;VALUE=DATE:20150930
UID:040000008200E00074C5B7101A82E00800000000203D8F02E7D4D001000000000000000
 010000000A717B53D15823B4FBA747B49155B9A62
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20151230T181202Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:FREE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
END:VCALENDAR

Using RRULE[WKST] and a default first day of week

I use French calendars and weeks start on Mondays, not on Sundays.
If we don't set the correct first day of week, we can have some bad results with recurrences.
I offer to update the class so that we can provide a default first day of week.
The class might also adapt the result depending on the RRULE[WKST] parameter.

From http://www.kanzaki.com/docs/ical/rrule.html :
An example where the days generated makes a difference because of

WKST:
  DTSTART;TZID=US-Eastern:19970805T090000
  RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO
  ==> (1997 EDT)Aug 5,10,19,24

changing only WKST from MO to SU, yields different results...

DTSTART;TZID=US-Eastern:19970805T090000
  RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU
  ==> (1997 EDT)August 5,17,19,31

My ICS example (the only difference between the 2 events is the WKST parameter) :

BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20160705T090000
DTEND;TZID=Europe/Paris:20160705T100000
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO
DTSTAMP:20160514T170815Z
UID:[email protected]
CREATED:20150624T141227Z
DESCRIPTION:
LAST-MODIFIED:20160707T075813Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:TEST WKST MO
TRANSP:OPAQUE
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20160705T090000
DTEND;TZID=Europe/Paris:20160705T100000
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU
DTSTAMP:20160514T170815Z
UID:[email protected]
CREATED:20150624T141227Z
DESCRIPTION:
LAST-MODIFIED:20160707T075813Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:TEST WKST SU
TRANSP:OPAQUE
END:VEVENT

Recurring events on the 5th day name of a month goes onto next month if month does not have 5 of those days

When trying to processes recurring events that meet on the 5th dayname of a month (e.g. each 5th Sunday), the event spills onto the next month. Fixed this by adding the following code at line 526:

// Prevent 5th day of a month from showing up on the next month
// If BYDAY and the event falls outside the current month, skip the event

$compare_current_month = date('F', $recurring_timestamp);
$compare_event_month   = date('F', strtotime($event_start_desc));

if ($compare_current_month != $compare_event_month) {
    $recurring_timestamp = strtotime($offset, $recurring_timestamp);
    continue;
}

Exdate in repetitive tasks

I found problems in repetitive tasks. I want to parse an ical from Google Calendar and if event is throughout a day (no hour specified), DTSTART,DTEND and EXDATE are formated YYYYMMDD.
I add this code in process_recurrences function and it seems good.
before:

if ((!isset($anEvent['EXDATE_array'])) || (!in_array($anEvent['DTSTART'], $anEvent['EXDATE_array']))) {}

after:

if ((!isset($anEvent['EXDATE_array'])) || (!in_array($anEvent['DTSTART'], $anEvent['EXDATE_array'])) && (!in_array(substr($anEvent['DTSTART'],0,8), $anEvent['EXDATE_array']))) {}

There is probably a cleaner solution but this one works fine.

Thank you for this lib.

Add timezone support

I'm currently working with a server that produces output like this:

BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
SUMMARY:5 appointments
DTSTART;TZID=Europe/Berlin:20160425T150000
DTEND;TZID=Europe/Berlin:20160425T160000
RRULE:FREQ=DAILY;UNTIL=20160429T130000Z
END:VEVENT

Notice how the timezone is included in the DTSTART and DTEND properties, but not in the RRULE > UNTIL property. This means that the RRULE parsing doesn't work properly and the last event is always left out. It is of course an awfully convoluted output, but it technically lies within specifications.

I could take the time to implement this, but I want to make sure the way I do it is compatible with the current code/design philosophy. Does anyone have ideas how this could be accomplished without changing too much of the existing code? I propose a new class RRule that takes a DTSTART, RRULEs and EXDATEs and turns them into a stream of timestamps. This could be used to streamline the process_recurrences() method as well as figuring out which time offset to use (the offset in my example changes depending on whether daylight savings time is in effect, which is specified by two RRULEs).

After that is done, you could easily modify iCalDateToUnixTimestamp() to account for the timezone.

Help with passing in array of line

Hi,
I just started using ics-parse for my website. I want to use the feature of passing in an array of lines, instead of a file, because I'm pulling the VCALENDAR data out of a sqlite3 database. Each line is a VCALENDAR "blob". When I pass in the array of lines, the event_count is 0 and $ical->events() returns NULL. Obviously, my array of lines is incorrect. What should the format of the array of lines be?
Below is a snippet of my code:

$sqldb = new PDO("sqlite:db/db.sqlite") or die(print_r($sqldb->errorInfo(), true));
$sqldb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$statement = $sqldb->prepare("select * from calendarobjects where calendarid = 12");
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
if ($results)
{
    foreach($results as $i => $row)
    {
        $icsArray[$i] = $row["calendardata"];
    }
}
$ical = new ICal($icsArray);
echo $ical->event_count;
$ics_dates = $ical->events();
echo $ics_dates;

Any help is greatly appreciated!
Thanks! Mark

Only show today's events

How do I get the ics-parser to only display today's events?
Currently it lists all past events right back to the beginning of the year.
I just need the current days to display.

Thank you

Recurring weekly

Hi, there was a bug in weekly. If no BYDAY is provided, you shouldn't use days in all week. Instead select only day of the first event.

if(!isset($rrules['BYDAY']))
                        {
                            $weekTemp = array('SU','MO','TU','WE','TH','FR','SA');
                            $findDay = $weekTemp[date('w',$start_timestamp)];
                            $bydays = array($findDay);
                        }

eventsFromRange

Can you submit an example of eventsFromRange? I cannot get this to work.

Inclusion of versioning

  • No version number is present in class.iCalReader.php or in composer.json
    • Realistically this should tie in with the version defined in releases to help users with using the most up-to-date version of the parser
  • Current is 1.0.2

Installed with composer class.iCalReader.php file differs from the one in source

I'm not sure if it's a bug or i'm doing something wrong.
After installing ics-parser using composer i found out that installed version of class.iCalReader.php file is different from the one on github source.
Both are marked as 1.0.3 version but the one installed with composer doesn't contain the initString() method.

Here is a composer.json code

"require": {
    "johngrogg/ics-parser": "*"
},

Multiple instances of ICal-Objects (generated from URL) in different scripts not possible

Hello,

I have a PHP script which opens 3 other PHP scripts in iframes.
I want 2 of those 3 to create an ICal Object (different instances) In both scripts I use:

<?php
    require("./ics-parser-master/class.iCalReader.php");
    $ical = new ICal($icsUrl);
    $events = $ical->events();
?>

The problem I encounter is that on one script the $events array and the $ical itself are empty.

This randomly changes when refreshing the site (the one that houses the iframes). Sometimes the instance of the one script is empty, sometimes the other one is empty. But they are never filled at the same time.

I hope it is a mistake on my side, if so please guide me.
This is really crucial to me.

Event with recurrent : index 02 in place of 2

Add an cast in like this ;)

Menu

// Add specific month dates
if (isset($rrules['BYMONTH']) && $rrules['BYMONTH'] != '')
{
    $event_start_desc = "$day {$month_names[(int)$rrules['BYMONTH']]} " . date('Y H:i:s', $recurring_timestamp);
}
else
{
    $event_start_desc = $day . date('F Y H:i:s', $recurring_timestamp);
}

Not working with Yahoo calendar ics file

Parser cannot parse this ics file, this is imported from Yahoo calendar

BEGIN:VCALENDAR
PRODID://Yahoo//Calendar//EN
VERSION:2.0
METHOD:PUBLISH
BEGIN:VEVENT
SUMMARY:fgfgfgfg
CLASS:PUBLIC
DTSTART;TZID=Etc/GMT:20160308T110000Z
DTEND;TZID=Etc/GMT:20160308T113000Z
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:6fcbe1ba-0429-4512-8d0d-302934a4e52c
DTSTAMP:20160302T055451Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:
TRIGGER;RELATED=START:-PT30M
END:VALARM
END:VEVENT
BEGIN:VEVENT
SUMMARY:fgfgfg
CLASS:PUBLIC
DTSTART;TZID=Etc/GMT:20160308T100000Z
DTEND;TZID=Etc/GMT:20160308T103000Z
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:0c441560-68dd-45f5-a292-aefb88c0cbc3
DTSTAMP:20160302T055502Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:
TRIGGER;RELATED=START:-PT30M
END:VALARM
END:VEVENT
BEGIN:VEVENT
SUMMARY:New Event
CLASS:PUBLIC
DTSTART;TZID=Etc/GMT:20160302T113000Z
DTEND;TZID=Etc/GMT:20160302T123000Z
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:0b3bed01-e16b-4cf2-9f74-b7cbb2c9e7f0
DTSTAMP:20160302T055517Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:
TRIGGER;RELATED=START:-PT30M
END:VALARM
END:VEVENT
BEGIN:VEVENT
SUMMARY:fgfg
CLASS:PUBLIC
DTSTART;TZID=Etc/GMT:20160308T100000Z
DTEND;TZID=Etc/GMT:20160308T103000Z
LOCATION:fgfg
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:8aec1953-96c1-4555-8b7b-53c5e3fc04a5
DTSTAMP:20160302T055532Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:
TRIGGER;RELATED=START:-PT30M
END:VALARM
END:VEVENT
BEGIN:VTODO
SUMMARY:New To Do
DESCRIPTION:asas
CLASS:PUBLIC
PRIORITY:0
SEQUENCE:1
STATUS:NEEDS-ACTION
UID:c29a9bd1-43bf-472a-9efc-dcfa6e4eb9b8
DTSTAMP:20160415T095714Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
END:VTODO
BEGIN:VTODO
SUMMARY:AAA
DESCRIPTION:AA
CLASS:PUBLIC
PRIORITY:0
SEQUENCE:0
STATUS:NEEDS-ACTION
UID:d7781644-2aea-4b99-9695-ab0e89ede784
DTSTAMP:20160415T095713Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
END:VTODO
BEGIN:VTODO
SUMMARY:fgfgfgf
DESCRIPTION:gfg
CLASS:PUBLIC
PRIORITY:0
SEQUENCE:0
STATUS:NEEDS-ACTION
UID:bdbee562-1b20-43ef-88bb-cb56fa4f8e28
DTSTAMP:20160311T111539Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
END:VTODO
BEGIN:VTODO
SUMMARY:rtrtrt
DESCRIPTION:rtrt
CLASS:PUBLIC
PRIORITY:0
SEQUENCE:0
STATUS:NEEDS-ACTION
UID:d8a5ac0f-6994-4753-a8ee-69e1c9d5ae12
DTSTAMP:20160415T095815Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
X-YAHOO-YID:mymainj
END:VTODO
BEGIN:VEVENT
SUMMARY:yahooo
CLASS:PUBLIC
DTSTART;TZID=Asia/Kolkata:20160412T100000
DTEND;TZID=Asia/Kolkata:20160412T110000
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:f9797493-0728-431b-b801-e00c3849a816
DTSTAMP:20160415T035146Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
END:VEVENT
BEGIN:VEVENT
SUMMARY:yyyyyy
CLASS:PUBLIC
DTSTART;TZID=Asia/Kolkata:20160412T100000
DTEND;TZID=Asia/Kolkata:20160412T110000
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:95f513bd-59cc-4dcd-b74b-db99960aea19
DTSTAMP:20160415T035216Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
END:VEVENT
BEGIN:VEVENT
SUMMARY:opopopop
CLASS:PUBLIC
DTSTART;TZID=Asia/Kolkata:20160412T100000
DTEND;TZID=Asia/Kolkata:20160412T110000
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:fa310e42-1331-4214-9be3-7d56746fb889
DTSTAMP:20160415T035717Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
END:VEVENT
BEGIN:VEVENT
SUMMARY:New Event
CLASS:PUBLIC
DTSTART;TZID=Asia/Kolkata:20160412T100000
DTEND;TZID=Asia/Kolkata:20160412T110000
PRIORITY:0
SEQUENCE:0
STATUS:CONFIRMED
UID:de709166-dbc9-432c-937a-938bc14732a0
DTSTAMP:20160415T035759Z
ORGANIZER;CN=disousa;SENT-BY="mailto:[email protected]":mailto:rashn
 [email protected]
RRULE:FREQ=DAILY;UNTIL=20160419T043000Z;INTERVAL=1
X-YAHOO-YID:mymainj
TRANSP:OPAQUE
STATUS:CONFIRMED
X-YAHOO-USER-STATUS:BUSY
X-YAHOO-EVENT-STATUS:BUSY
END:VEVENT
BEGIN:VTIMEZONE
TZID:Asia/Kolkata
TZURL:http://tzurl.org/zoneinfo/Asia/Kolkata
X-LIC-LOCATION:Asia/Kolkata
BEGIN:STANDARD
TZOFFSETFROM:+055328
TZOFFSETTO:+055320
TZNAME:HMT
DTSTART:18800101T000000
RDATE:18800101T000000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+055320
TZOFFSETTO:+0630
TZNAME:BURT
DTSTART:19411001T000000
RDATE:19411001T000000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0630
TZOFFSETTO:+0530
TZNAME:IST
DTSTART:19420515T000000
RDATE:19420515T000000
RDATE:19451015T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0530
TZOFFSETTO:+0630
TZNAME:IST
DTSTART:19420901T000000
RDATE:19420901T000000
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VTIMEZONE
TZID:Europe/London
TZURL:http://tzurl.org/zoneinfo/Europe/London
X-LIC-LOCATION:Europe/London
BEGIN:DAYLIGHT
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
TZNAME:BST
DTSTART:19810329T010000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:19961027T020000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:-000115
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:18471201T000000
RDATE:18471201T000000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
TZNAME:BST
DTSTART:19160521T020000
RDATE:19160521T020000
RDATE:19170408T020000
RDATE:19180324T020000
RDATE:19190330T020000
RDATE:19200328T020000
RDATE:19210403T020000
RDATE:19220326T020000
RDATE:19230422T020000
RDATE:19240413T020000
RDATE:19250419T020000
RDATE:19260418T020000
RDATE:19270410T020000
RDATE:19280422T020000
RDATE:19290421T020000
RDATE:19300413T020000
RDATE:19310419T020000
RDATE:19320417T020000
RDATE:19330409T020000
RDATE:19340422T020000
RDATE:19350414T020000
RDATE:19360419T020000
RDATE:19370418T020000
RDATE:19380410T020000
RDATE:19390416T020000
RDATE:19400225T020000
RDATE:19460414T020000
RDATE:19470316T020000
RDATE:19480314T020000
RDATE:19490403T020000
RDATE:19500416T020000
RDATE:19510415T020000
RDATE:19520420T020000
RDATE:19530419T020000
RDATE:19540411T020000
RDATE:19550417T020000
RDATE:19560422T020000
RDATE:19570414T020000
RDATE:19580420T020000
RDATE:19590419T020000
RDATE:19600410T020000
RDATE:19610326T020000
RDATE:19620325T020000
RDATE:19630331T020000
RDATE:19640322T020000
RDATE:19650321T020000
RDATE:19660320T020000
RDATE:19670319T020000
RDATE:19680218T020000
RDATE:19720319T020000
RDATE:19730318T020000
RDATE:19740317T020000
RDATE:19750316T020000
RDATE:19760321T020000
RDATE:19770320T020000
RDATE:19780319T020000
RDATE:19790318T020000
RDATE:19800316T020000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:19161001T030000
RDATE:19161001T030000
RDATE:19170917T030000
RDATE:19180930T030000
RDATE:19190929T030000
RDATE:19201025T030000
RDATE:19211003T030000
RDATE:19221008T030000
RDATE:19230916T030000
RDATE:19240921T030000
RDATE:19251004T030000
RDATE:19261003T030000
RDATE:19271002T030000
RDATE:19281007T030000
RDATE:19291006T030000
RDATE:19301005T030000
RDATE:19311004T030000
RDATE:19321002T030000
RDATE:19331008T030000
RDATE:19341007T030000
RDATE:19351006T030000
RDATE:19361004T030000
RDATE:19371003T030000
RDATE:19381002T030000
RDATE:19391119T030000
RDATE:19451007T030000
RDATE:19461006T030000
RDATE:19471102T030000
RDATE:19481031T030000
RDATE:19491030T030000
RDATE:19501022T030000
RDATE:19511021T030000
RDATE:19521026T030000
RDATE:19531004T030000
RDATE:19541003T030000
RDATE:19551002T030000
RDATE:19561007T030000
RDATE:19571006T030000
RDATE:19581005T030000
RDATE:19591004T030000
RDATE:19601002T030000
RDATE:19611029T030000
RDATE:19621028T030000
RDATE:19631027T030000
RDATE:19641025T030000
RDATE:19651024T030000
RDATE:19661023T030000
RDATE:19671029T030000
RDATE:19711031T030000
RDATE:19721029T030000
RDATE:19731028T030000
RDATE:19741027T030000
RDATE:19751026T030000
RDATE:19761024T030000
RDATE:19771023T030000
RDATE:19781029T030000
RDATE:19791028T030000
RDATE:19801026T030000
RDATE:19811025T020000
RDATE:19821024T020000
RDATE:19831023T020000
RDATE:19841028T020000
RDATE:19851027T020000
RDATE:19861026T020000
RDATE:19871025T020000
RDATE:19881023T020000
RDATE:19891029T020000
RDATE:19901028T020000
RDATE:19911027T020000
RDATE:19921025T020000
RDATE:19931024T020000
RDATE:19941023T020000
RDATE:19951022T020000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:BDST
DTSTART:19410504T020000
RDATE:19410504T020000
RDATE:19420405T020000
RDATE:19430404T020000
RDATE:19440402T020000
RDATE:19450402T020000
RDATE:19470413T020000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:BST
DTSTART:19410810T030000
RDATE:19410810T030000
RDATE:19420809T030000
RDATE:19430815T030000
RDATE:19440917T030000
RDATE:19450715T030000
RDATE:19470810T030000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
TZNAME:BST
DTSTART:19681027T000000
RDATE:19681027T000000
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:+0000
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:19960101T000000
RDATE:19960101T000000
END:STANDARD
END:VTIMEZONE
END:VCALENDAR

Wrong spaces in the description...

Hey folks,

the description is trimmed wrong. I got this ICS from google calendar.
example

The lines have a leading space, that is NOT part of the description. But the space on the right side (i mark the space in the first line) is part of the description. So it is wrong to trim the right space. I remove the rtrim in the "initLines" function, to get the right description.

Wrong thought?

Regards,
Tim

Latest version has regex issue

My ical file has two lines that look like this:

DTSTART;TZID=America/New_York:20150601T064500
DTEND;TZID=America/New_York:20150601T073000

The output looks like this:

Array
(
    [York] => 20150601T073000
    [York_array] => Array
        (
            [0] => 20150601T064500
            [1] => 20150601T073000
        )
    ...

I was able to resolve this by modifying the regex here:

public function keyValueFromString($text)
    {
        preg_match('/([A-Za-z-\/;=^:]+)[:]([\w\W]*)/', $text, $matches);
        if (count($matches) == 0) {
            return false;
        }
        $matches = array_splice($matches, 1, 2);
        return $matches;
    }

If I change that to this, it works:

preg_match('/([^:]+)[:]([\w\W]*)/', $text, $matches);

Is there any reason why it is not that?


I hope this helps. And this is an awesome project. Thank you for making an maintaining it.

Get timezone

How to get timezone from ics contains start, end date like below

DTSTART;TZID=America/New_York:20160320T152000

Check if timezones are valid

Timezones detected into ICS files have to be checked to avoid PHP errors.
Some ICS files return something like TZID:GMT+02.000/+03.000 which is not a PHP timezone format and this might generate errors.

I offer an enhancement that allow the user to define a default timezone which will be used if a wrong timezone is detected with the php function timezone_identifiers_list.

Parser Doesnt Handle Language Tag

For text tags with the Language tag added, the parser doesn't know how to behave.

Example:
SUMMARY;LANGUAGE=en-us:Office Workgroup Focus Group
Normally this would be:
SUMMARY:Office Workgroup Focus Group

With added language tag, the SUMMARY tag, or whatever... isn't parsed properly.
(Outputs nothing)

Should return an array of:
('SUMMARY' => 'Office Workgroup Focus Group')

Instead returns an array of:
('SUMMARY' => '')

This issue might also affect timezone settings:
eg.
DTEND;TZID="Eastern Standard Time":20130502T180000
DTSTART;TZID="Eastern Standard Time":20130502T130000
etc.

Support ability to parse a negative BYDAY `$day_number` value such as -2MO, etc.

  • -1 is already supported as by using relative date formats we can use 'last day of ...'; however anything less than -1 won't work because penultimate, etc. is not offered with relative date formats

  • For example:
    • FREQ=MONTHLY;BYDAY=-2MO;COUNT=7
      • Every month on the 2nd last Monday for 7 times
    • FREQ=YEARLY;BYDAY=-3SU;BYMONTH=10
      • Every October on the 3rd last Sunday

  • The code below will convert a negative BYDAY value such as -2MO to its appropriate positive day ordinal such as the 'third'.
    • Will leave the implementation to someone else but this snippet should provide a start.
<?php
    /**
     * Get the number of days between a
     * start and end date
     *
     * @param  $days
     * @param  $start
     * @param  $end
     * @return integer
     */
    function numberOfDays($days, $start, $end){
        $w       = array(date('w', $start), date('w', $end));
        $oneWeek = 604800; // 7 * 24 * 60 * 60
        $x       = floor(($end - $start) / $oneWeek);
        $sum     = 0;

        for ($day = 0; $day < 7; ++$day) {
            if ($days & pow(2, $day)) {
                $sum += $x + ($w[0] > $w[1] ? $w[0] <= $day || $day <= $w[1] : $w[0] <= $day && $day <= $w[1]);
            }
        }

        return $sum;
    }

    /**
     * Convert a negative day ordinal to
     * its equivalent positive form
     *
     * @param  $dayNumber
     * @param  $weekday
     * @param  $timestamp
     * @return string
     */
    function convertDayOrdinalToPositive($dayNumber, $weekday, $timestamp){
        $dayNumber = empty($dayNumber) ? 1 : $dayNumber; // Returns 0 when no number defined in BYDAY

        $dayOrdinals = array(1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth');

        // We only care about negative BYDAY values
        if ($dayNumber >= 1) {
            return $dayOrdinals[$dayNumber];
        }

        $timestamp = (is_object($timestamp)) ? $timestamp : \DateTime::createFromFormat('U', $timestamp);
        $start = strtotime('first day of ' . $timestamp->format('F Y H:i:s'));
        $end   = strtotime('last day of ' . $timestamp->format('F Y H:i:s'));

        // Used with pow(2, X) so pow(2, 4) is THURSDAY
        $weekdays = array('SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6);

        $numberOfDays = numberOfDays(pow(2, $weekdays[$weekday]), $start, $end);

        // Create subset
        $dayOrdinals = array_slice($dayOrdinals, 0, $numberOfDays, true);

        //Reverse only the values
        $dayOrdinals = array_combine(array_keys($dayOrdinals), array_reverse(array_values($dayOrdinals)));

        return $dayOrdinals[$dayNumber * -1];
    }

    echo convertDayOrdinalToPositive(-2, 'MO', date_create('now'));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.