From 1490a96f14a0da2b9ef25e6aa68896c3eaa59887 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 17 May 2018 15:41:10 +0200 Subject: [PATCH] added icalparser from https://github.com/OzzyCzech/icalparser.git --- chch-calendar.sh | 1 + icalparser/.travis.yml | 27 + icalparser/LICENSE | 24 + icalparser/composer.json | 28 + icalparser/example/index.php | 32 + icalparser/readme.md | 44 ++ icalparser/src/Freq.php | 600 +++++++++++++++++ icalparser/src/IcalParser.php | 471 +++++++++++++ icalparser/src/Recurrence.php | 234 +++++++ icalparser/src/WindowsTimezones.php | 210 ++++++ icalparser/tests/blank_description.phpt | 19 + icalparser/tests/cal/FrenchHolidays.ics | 382 +++++++++++ icalparser/tests/cal/basic.ics | 616 ++++++++++++++++++ icalparser/tests/cal/blank_description.ics | 51 ++ icalparser/tests/cal/blank_line_end.ics | 4 + icalparser/tests/cal/blank_line_mid.ics | 4 + icalparser/tests/cal/daily_recur.ics | 52 ++ icalparser/tests/cal/daily_recur2.ics | 41 ++ .../tests/cal/day_long_recur_yearly.ics | 52 ++ icalparser/tests/cal/forced_types.ics | 50 ++ icalparser/tests/cal/google_birthday.ics | 90 +++ icalparser/tests/cal/minimal.ics | 39 ++ .../tests/cal/multiline_description.ics | 52 ++ icalparser/tests/cal/multiple_attachments.ics | 45 ++ icalparser/tests/cal/multiple_rrules.ics | 45 ++ icalparser/tests/cal/parserv2.ics | 45 ++ icalparser/tests/cal/readme.md | 3 + icalparser/tests/cal/recur_instances.ics | 92 +++ .../tests/cal/recur_instances_finite.ics | 50 ++ .../recur_instances_with_modifications.ics | 63 ++ ...tances_with_modifications_and_interval.ics | 578 ++++++++++++++++ ...tances_with_modifications_to_first_day.ics | 57 ++ icalparser/tests/cal/utc_negative_zero.ics | 27 + .../tests/cal/weird_windows_timezones.ics | 237 +++++++ icalparser/tests/cal/wrong_dates.ics | 37 ++ icalparser/tests/multiline_description.phpt | 18 + icalparser/tests/multiple_attachments.phpt | 20 + icalparser/tests/recurring_events.phpt | 170 +++++ icalparser/tests/sort_events.phpt | 29 + icalparser/tests/timezone.phpt | 28 + icalparser/tests/wrong_dates.phpt | 19 + icalparser/tools/windowstimezones.php | 19 + 42 files changed, 4705 insertions(+) create mode 100644 icalparser/.travis.yml create mode 100644 icalparser/LICENSE create mode 100644 icalparser/composer.json create mode 100644 icalparser/example/index.php create mode 100644 icalparser/readme.md create mode 100644 icalparser/src/Freq.php create mode 100644 icalparser/src/IcalParser.php create mode 100644 icalparser/src/Recurrence.php create mode 100644 icalparser/src/WindowsTimezones.php create mode 100644 icalparser/tests/blank_description.phpt create mode 100644 icalparser/tests/cal/FrenchHolidays.ics create mode 100644 icalparser/tests/cal/basic.ics create mode 100755 icalparser/tests/cal/blank_description.ics create mode 100755 icalparser/tests/cal/blank_line_end.ics create mode 100755 icalparser/tests/cal/blank_line_mid.ics create mode 100755 icalparser/tests/cal/daily_recur.ics create mode 100755 icalparser/tests/cal/daily_recur2.ics create mode 100755 icalparser/tests/cal/day_long_recur_yearly.ics create mode 100755 icalparser/tests/cal/forced_types.ics create mode 100755 icalparser/tests/cal/google_birthday.ics create mode 100755 icalparser/tests/cal/minimal.ics create mode 100644 icalparser/tests/cal/multiline_description.ics create mode 100755 icalparser/tests/cal/multiple_attachments.ics create mode 100755 icalparser/tests/cal/multiple_rrules.ics create mode 100755 icalparser/tests/cal/parserv2.ics create mode 100644 icalparser/tests/cal/readme.md create mode 100755 icalparser/tests/cal/recur_instances.ics create mode 100755 icalparser/tests/cal/recur_instances_finite.ics create mode 100644 icalparser/tests/cal/recur_instances_with_modifications.ics create mode 100644 icalparser/tests/cal/recur_instances_with_modifications_and_interval.ics create mode 100755 icalparser/tests/cal/recur_instances_with_modifications_to_first_day.ics create mode 100755 icalparser/tests/cal/utc_negative_zero.ics create mode 100644 icalparser/tests/cal/weird_windows_timezones.ics create mode 100644 icalparser/tests/cal/wrong_dates.ics create mode 100644 icalparser/tests/multiline_description.phpt create mode 100644 icalparser/tests/multiple_attachments.phpt create mode 100644 icalparser/tests/recurring_events.phpt create mode 100644 icalparser/tests/sort_events.phpt create mode 100644 icalparser/tests/timezone.phpt create mode 100644 icalparser/tests/wrong_dates.phpt create mode 100644 icalparser/tools/windowstimezones.php diff --git a/chch-calendar.sh b/chch-calendar.sh index f533750..5cd8d79 100755 --- a/chch-calendar.sh +++ b/chch-calendar.sh @@ -12,5 +12,6 @@ else mv /tmp/chch_$RAND.ical $CHCH_ICAL fi +# this requires -> icalparser from https://github.com/OzzyCzech/icalparser.git head $CHCH_ICAL -n1 >/dev/null 2>&1 && php /etc/chch/chch_ical2mediawiki.php $CHCH_ICAL 2>/dev/null | php /var/www/chch/maintenance/edit.php -m -b --no-rc -s "automatische Aktualisierung aus dem ical feed" "Aktuelle_ChCh-Termine" >/dev/null 2>&1 echo "Hauptseite" | php /var/www/chch/maintenance/purgeList.php --purge > /dev/null 2>&1 diff --git a/icalparser/.travis.yml b/icalparser/.travis.yml new file mode 100644 index 0000000..6eec526 --- /dev/null +++ b/icalparser/.travis.yml @@ -0,0 +1,27 @@ +language: php +php: + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - hhvm + +matrix: + allow_failures: + - php: 7.1 + - php: 7.2 + - php: hhvm + + include: + - php: 5.6 + env: dependencies="--prefer-lowest --prefer-stable" + +script: + - vendor/bin/tester tests -s -p php + +after_failure: + - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done + +before_script: + - composer self-update + - composer update --no-interaction --prefer-source $dependencies \ No newline at end of file diff --git a/icalparser/LICENSE b/icalparser/LICENSE new file mode 100644 index 0000000..e449f31 --- /dev/null +++ b/icalparser/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2014, Roman Ožana +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/icalparser/composer.json b/icalparser/composer.json new file mode 100644 index 0000000..3eb9cc8 --- /dev/null +++ b/icalparser/composer.json @@ -0,0 +1,28 @@ +{ + "name": "om/icalparser", + "description": "Simple ical parser", + "keywords": [ + "ical", + "calendar", + "parser" + ], + "license": ["BSD-3-Clause"], + "authors": [ + { + "name": "Roman Ožana", + "email": "ozana@omdesign.cz" + } + ], + "require": { + "php": ">=5.6.0" + }, + "autoload": { + "classmap": ["src/"] + }, + "require-dev": { + "nette/tester": "v2.0.1" + }, + "scripts": { + "tests": ["./vendor/bin/tester tests -s -p php"] + } +} diff --git a/icalparser/example/index.php b/icalparser/example/index.php new file mode 100644 index 0000000..5c468d5 --- /dev/null +++ b/icalparser/example/index.php @@ -0,0 +1,32 @@ + + + + + + + + Ical Parser example + + + + + +
+

Czech holidays

+ +
    + parseFile( + 'https://www.google.com/calendar/ical/cs.czech%23holiday%40group.v.calendar.google.com/public/basic.ics' + ); + + foreach ($cal->getSortedEvents() as $r) { + echo sprintf('
  • %s - %s
  • ' . PHP_EOL, $r['DTSTART']->format('j.n.Y'), $r['SUMMARY']); + } + + ?>
+
+ + \ No newline at end of file diff --git a/icalparser/readme.md b/icalparser/readme.md new file mode 100644 index 0000000..1b965c4 --- /dev/null +++ b/icalparser/readme.md @@ -0,0 +1,44 @@ +# PHP iCal Parser + +[![Build Status](https://travis-ci.org/OzzyCzech/icalparser.svg?branch=master)](https://travis-ci.org/OzzyCzech/icalparser) [![Latest Stable Version](https://poser.pugx.org/om/icalparser/v/stable.png)](https://packagist.org/packages/om/icalparser) [![Total Downloads](https://poser.pugx.org/om/icalparser/downloads.png)](https://packagist.org/packages/om/icalparser) [![Latest Unstable Version](https://poser.pugx.org/om/icalparser/v/unstable.png)](https://packagist.org/packages/om/icalparser) [![License](https://poser.pugx.org/om/icalparser/license.png)](https://packagist.org/packages/om/icalparser) + +Internet Calendaring Parser [rfc2445](http://www.ietf.org/rfc/rfc2445.txt) or iCal parser is simple PHP 5.6+ class for parsing format into array. + +## How to install + +```bash +composer require om/icalparser +``` + +## Usage + +```php +parseFile( + 'https://www.google.com/calendar/ical/cs.czech%23holiday%40group.v.calendar.google.com/public/basic.ics' +); + +foreach ($cal->getSortedEvents() as $r) { + echo sprintf('
  • %s - %s
  • ' . PHP_EOL, $r['DTSTART']->format('j.n.Y'), $r['SUMMARY']); +} +``` + +## Requirements + +- PHP 5.6+ + +## Run tests + +iCal parser using [Nette Tester](https://github.com/nette/tester). +The tests can be invoked via [composer](https://getcomposer.org/). + +```bash +composer update +composer tests +``` + +## TODO + +- add ATTENDEE support http://www.kanzaki.com/docs/ical/attendee.html diff --git a/icalparser/src/Freq.php b/icalparser/src/Freq.php new file mode 100644 index 0000000..71ad208 --- /dev/null +++ b/icalparser/src/Freq.php @@ -0,0 +1,600 @@ + + */ + +/** + * A class to store Frequency-rules in. Will allow a easy way to find the + * last and next occurrence of the rule. + * + * No - this is so not pretty. But.. ehh.. You do it better, and I will + * gladly accept patches. + * + * Created by trail-and-error on the examples given in the RFC. + * + * TODO: Update to a better way of doing calculating the different options. + * Instead of only keeping track of the best of the current dates found + * it should instead keep a array of all the calculated dates within the + * period. + * This should fix the issues with multi-rule + multi-rule interference, + * and make it possible to implement the SETPOS rule. + * By pushing the next period onto the stack as the last option will + * (hopefully) remove the need for the awful simpleMode + * + * @author Morten Fangel (C) 2008 + * @author Michael Kahn (C) 2013 + * @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK + */ +class Freq { + protected $weekdays = [ + 'MO' => 'monday', 'TU' => 'tuesday', 'WE' => 'wednesday', 'TH' => 'thursday', 'FR' => 'friday', 'SA' => 'saturday', + 'SU' => 'sunday' + ]; + protected $knownRules = [ + 'month', 'weekno', 'day', 'monthday', 'yearday', 'hour', 'minute' + ]; //others : 'setpos', 'second' + protected $ruleModifiers = ['wkst']; + protected $simpleMode = true; + + protected $rules = ['freq' => 'yearly', 'interval' => 1]; + protected $start = 0; + protected $freq = ''; + + protected $excluded; //EXDATE + protected $added; //RDATE + + protected $cache; // getAllOccurrences() + + /** + * Constructs a new Freqency-rule + * + * @param $rule string + * @param $start int Unix-timestamp (important : Need to be the start of Event) + * @param $excluded array of int (timestamps), see EXDATE documentation + * @param $added array of int (timestamps), see RDATE documentation + */ + public function __construct($rule, $start, $excluded = [], $added = []) { + $this->start = $start; + $this->excluded = []; + + $rules = []; + foreach ($rule AS $k => $v) { + $this->rules[strtolower($k)] = $v; + } + + if (isset($this->rules['until']) && is_string($this->rules['until'])) { + $this->rules['until'] = strtotime($this->rules['until']); + } else if ($this->rules['until'] instanceof \DateTime) { + $this->rules['until'] = $this->rules['until']->getTimestamp(); + } + $this->freq = strtolower($this->rules['freq']); + + foreach ($this->knownRules AS $rule) { + if (isset($this->rules['by' . $rule])) { + if ($this->isPrerule($rule, $this->freq)) { + $this->simpleMode = false; + } + } + } + + if (!$this->simpleMode) { + if (!(isset($this->rules['byday']) || isset($this->rules['bymonthday']) || isset($this->rules['byyearday']))) { + $this->rules['bymonthday'] = date('d', $this->start); + } + } + + //set until, and cache + if (isset($this->rules['count'])) { + + $cache[$ts] = $ts = $this->start; + for ($n = 1; $n < $this->rules['count']; $n++) { + $ts = $this->findNext($ts); + $cache[$ts] = $ts; + } + $this->rules['until'] = $ts; + + //EXDATE + if (!empty($excluded)) { + foreach ($excluded as $ts) { + unset($cache[$ts]); + } + } + //RDATE + if (!empty($added)) { + $cache = array_unique(array_merge(array_values($cache), $added)); + asort($cache); + } + + $this->cache = array_values($cache); + } + + $this->excluded = $excluded; + $this->added = $added; + } + + /** + * Returns all timestamps array(), build the cache if not made before + * + * @return array + */ + public function getAllOccurrences() { + if (empty($this->cache)) { + $cache = []; + + //build cache + $next = $this->firstOccurrence(); + while ($next) { + $cache[] = $next; + $next = $this->findNext($next); + } + if (!empty($this->added)) { + $cache = array_unique(array_merge($cache, $this->added)); + asort($cache); + } + $this->cache = $cache; + } + + return $this->cache; + } + + /** + * Returns the previous (most recent) occurrence of the rule from the + * given offset + * + * @param int $offset + * @return int + */ + public function previousOccurrence($offset) { + if (!empty($this->cache)) { + $t2 = $this->start; + foreach ($this->cache as $ts) { + if ($ts >= $offset) + return $t2; + $t2 = $ts; + } + } else { + $ts = $this->start; + while (($t2 = $this->findNext($ts)) < $offset) { + if ($t2 == false) { + break; + } + $ts = $t2; + } + } + + return $ts; + } + + /** + * Returns the next occurrence of this rule after the given offset + * + * @param int $offset + * @return int + */ + public function nextOccurrence($offset) { + if ($offset < $this->start) + return $this->firstOccurrence(); + return $this->findNext($offset); + } + + /** + * Finds the first occurrence of the rule. + * + * @return int timestamp + */ + public function firstOccurrence() { + $t = $this->start; + if (in_array($t, $this->excluded)) + $t = $this->findNext($t); + + return $t; + } + + /** + * Finds the absolute last occurrence of the rule from the given offset. + * Builds also the cache, if not set before... + * + * @return int timestamp + */ + public function lastOccurrence() { + //build cache if not done + $this->getAllOccurrences(); + //return last timestamp in cache + return end($this->cache); + } + + /** + * Calculates the next time after the given offset that the rule + * will apply. + * + * The approach to finding the next is as follows: + * First we establish a timeframe to find timestamps in. This is + * between $offset and the end of the period that $offset is in. + * + * We then loop though all the rules (that is a Prerule in the + * current freq.), and finds the smallest timestamp inside the + * timeframe. + * + * If we find something, we check if the date is a valid recurrence + * (with validDate). If it is, we return it. Otherwise we try to + * find a new date inside the same timeframe (but using the new- + * found date as offset) + * + * If no new timestamps were found in the period, we try in the + * next period + * + * @param int $offset + * @return int + */ + public function findNext($offset) { + if (!empty($this->cache)) { + foreach ($this->cache as $ts) { + if ($ts > $offset) + return $ts; + } + } + + $debug = false; + + //make sure the offset is valid + if ($offset === false || (isset($this->rules['until']) && $offset > $this->rules['until'])) { + if ($debug) echo 'STOP: ' . date('r', $offset) . "\n"; + + return false; + } + + $found = true; + + //set the timestamp of the offset (ignoring hours and minutes unless we want them to be + //part of the calculations. + if ($debug) echo 'O: ' . date('r', $offset) . "\n"; + $hour = (in_array($this->freq, ['hourly', 'minutely']) && $offset > $this->start) ? date('H', $offset) : date( + 'H', $this->start + ); + $minute = (($this->freq === 'minutely' || isset($this->rules['byminute'])) && $offset > $this->start) ? date( + 'i', $offset + ) : date('i', $this->start); + $t = mktime($hour, $minute, date('s', $this->start), date('m', $offset), date('d', $offset), date('Y', $offset)); + if ($debug) echo 'START: ' . date('r', $t) . "\n"; + + if ($this->simpleMode) { + if ($offset < $t) { + $ts = $t; + if ($ts && in_array($ts, $this->excluded)) + $ts = $this->findNext($ts); + } else { + $ts = $this->findStartingPoint($t, $this->rules['interval'], false); + if (!$this->validDate($ts)) { + $ts = $this->findNext($ts); + } + } + + return $ts; + } + + $eop = $this->findEndOfPeriod($offset); + if ($debug) echo 'EOP: ' . date('r', $eop) . "\n"; + + foreach ($this->knownRules AS $rule) { + if ($found && isset($this->rules['by' . $rule])) { + if ($this->isPrerule($rule, $this->freq)) { + $subrules = explode(',', $this->rules['by' . $rule]); + $_t = null; + foreach ($subrules AS $subrule) { + $imm = call_user_func_array([$this, 'ruleBy' . $rule], [$subrule, $t]); + if ($imm === false) { + break; + } + if ($debug) echo strtoupper($rule) . ': ' . date( + 'r', $imm + ) . ' A: ' . ((int)($imm > $offset && $imm < $eop)) . "\n"; + if ($imm > $offset && $imm <= $eop && ($_t == null || $imm < $_t)) { + $_t = $imm; + } + } + if ($_t !== null) { + $t = $_t; + } else { + $found = $this->validDate($t); + } + } + } + } + + if ($offset < $this->start && $this->start < $t) { + $ts = $this->start; + } elseif ($found && ($t != $offset)) { + if ($this->validDate($t)) { + if ($debug) echo 'OK' . "\n"; + $ts = $t; + } else { + if ($debug) echo 'Invalid' . "\n"; + $ts = $this->findNext($t); + } + } else { + if ($debug) echo 'Not found' . "\n"; + $ts = $this->findNext($this->findStartingPoint($offset, $this->rules['interval'])); + } + if ($ts && in_array($ts, $this->excluded)) + return $this->findNext($ts); + + return $ts; + } + + /** + * Finds the starting point for the next rule. It goes $interval + * 'freq' forward in time since the given offset + * + * @param int $offset + * @param int $interval + * @param boolean $truncate + * @return int + */ + private function findStartingPoint($offset, $interval, $truncate = true) { + $_freq = ($this->freq === 'daily') ? 'day__' : $this->freq; + $t = '+' . $interval . ' ' . substr($_freq, 0, -2) . 's'; + if ($_freq === 'monthly' && $truncate) { + if ($interval > 1) { + $offset = strtotime('+' . ($interval - 1) . ' months ', $offset); + } + $t = '+' . (date('t', $offset) - date('d', $offset) + 1) . ' days'; + } + + $sp = strtotime($t, $offset); + + if ($truncate) { + $sp = $this->truncateToPeriod($sp, $this->freq); + } + + return $sp; + } + + /** + * Finds the earliest timestamp posible outside this perioid + * + * @param int $offset + * @return int + */ + public function findEndOfPeriod($offset) { + return $this->findStartingPoint($offset, 1); + } + + /** + * Resets the timestamp to the beginning of the + * period specified by freq + * + * Yes - the fall-through is on purpose! + * + * @param int $time + * @param int $freq + * @return int + */ + private function truncateToPeriod($time, $freq) { + $date = getdate($time); + switch ($freq) { + case 'yearly': + $date['mon'] = 1; + case 'monthly': + $date['mday'] = 1; + case 'daily': + $date['hours'] = 0; + case 'hourly': + $date['minutes'] = 0; + case 'minutely': + $date['seconds'] = 0; + break; + case 'weekly': + if (date('N', $time) == 1) { + $date['hours'] = 0; + $date['minutes'] = 0; + $date['seconds'] = 0; + } else { + $date = getdate(strtotime('last monday 0:00', $time)); + } + break; + } + $d = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']); + + return $d; + } + + /** + * Applies the BYDAY rule to the given timestamp + * + * @param string $rule + * @param int $t + * @return int + */ + private function ruleByday($rule, $t) { + $dir = ($rule{0} == '-') ? -1 : 1; + $dir_t = ($dir == 1) ? 'next' : 'last'; + + $d = $this->weekdays[substr($rule, -2)]; + $s = $dir_t . ' ' . $d . ' ' . date('H:i:s', $t); + + if ($rule == substr($rule, -2)) { + if (date('l', $t) == ucfirst($d)) { + $s = 'today ' . date('H:i:s', $t); + } + + $_t = strtotime($s, $t); + + if ($_t == $t && in_array($this->freq, ['weekly', 'monthly', 'yearly'])) { + // Yes. This is not a great idea.. but hey, it works.. for now + $s = 'next ' . $d . ' ' . date('H:i:s', $t); + $_t = strtotime($s, $_t); + } + + return $_t; + } else { + $_f = $this->freq; + if (isset($this->rules['bymonth']) && $this->freq === 'yearly') { + $this->freq = 'monthly'; + } + if ($dir == -1) { + $_t = $this->findEndOfPeriod($t); + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + } + $this->freq = $_f; + + $c = preg_replace('/[^0-9]/', '', $rule); + $c = ($c == '') ? 1 : $c; + + $n = $_t; + while ($c > 0) { + if ($dir == 1 && $c == 1 && date('l', $t) == ucfirst($d)) { + $s = 'today ' . date('H:i:s', $t); + } + $n = strtotime($s, $n); + $c--; + } + + return $n; + } + } + + private function ruleBymonth($rule, $t) { + $_t = mktime(date('H', $t), date('i', $t), date('s', $t), $rule, date('d', $t), date('Y', $t)); + if ($t == $_t && isset($this->rules['byday'])) { + // TODO: this should check if one of the by*day's exists, and have a multi-day value + return false; + } else { + return $_t; + } + } + + private function ruleBymonthday($rule, $t) { + if ($rule < 0) { + $rule = date('t', $t) + $rule + 1; + } + + return mktime(date('H', $t), date('i', $t), date('s', $t), date('m', $t), $rule, date('Y', $t)); + } + + private function ruleByyearday($rule, $t) { + if ($rule < 0) { + $_t = $this->findEndOfPeriod(); + $d = '-'; + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + $d = '+'; + } + $s = $d . abs($rule - 1) . ' days ' . date('H:i:s', $t); + + return strtotime($s, $_t); + } + + private function ruleByweekno($rule, $t) { + if ($rule < 0) { + $_t = $this->findEndOfPeriod(); + $d = '-'; + } else { + $_t = $this->truncateToPeriod($t, $this->freq); + $d = '+'; + } + + $sub = (date('W', $_t) == 1) ? 2 : 1; + $s = $d . abs($rule - $sub) . ' weeks ' . date('H:i:s', $t); + $_t = strtotime($s, $_t); + + return $_t; + } + + private function ruleByhour($rule, $t) { + $_t = mktime($rule, date('i', $t), date('s', $t), date('m', $t), date('d', $t), date('Y', $t)); + + return $_t; + } + + private function ruleByminute($rule, $t) { + $_t = mktime(date('h', $t), $rule, date('s', $t), date('m', $t), date('d', $t), date('Y', $t)); + + return $_t; + } + + private function validDate($t) { + if (isset($this->rules['until']) && $t > $this->rules['until']) { + return false; + } + + if (in_array($t, $this->excluded)) { + return false; + } + + if (isset($this->rules['bymonth'])) { + $months = explode(',', $this->rules['bymonth']); + if (!in_array(date('m', $t), $months)) { + return false; + } + } + if (isset($this->rules['byday'])) { + $days = explode(',', $this->rules['byday']); + foreach ($days As $i => $k) { + $days[$i] = $this->weekdays[preg_replace('/[^A-Z]/', '', $k)]; + } + if (!in_array(strtolower(date('l', $t)), $days)) { + return false; + } + } + if (isset($this->rules['byweekno'])) { + $weeks = explode(',', $this->rules['byweekno']); + if (!in_array(date('W', $t), $weeks)) { + return false; + } + } + if (isset($this->rules['bymonthday'])) { + $weekdays = explode(',', $this->rules['bymonthday']); + foreach ($weekdays As $i => $k) { + if ($k < 0) { + $weekdays[$i] = date('t', $t) + $k + 1; + } + } + if (!in_array(date('d', $t), $weekdays)) { + return false; + } + } + if (isset($this->rules['byhour'])) { + $hours = explode(',', $this->rules['byhour']); + if (!in_array(date('H', $t), $hours)) { + return false; + } + } + + return true; + } + + private function isPrerule($rule, $freq) { + if ($rule === 'year') + + return false; + if ($rule === 'month' && $freq === 'yearly') + + return true; + if ($rule === 'monthday' && in_array($freq, ['yearly', 'monthly']) && !isset($this->rules['byday'])) + + return true; + // TODO: is it faster to do monthday first, and ignore day if monthday exists? - prolly by a factor of 4.. + if ($rule === 'yearday' && $freq === 'yearly') + + return true; + if ($rule === 'weekno' && $freq === 'yearly') + + return true; + if ($rule === 'day' && in_array($freq, ['yearly', 'monthly', 'weekly'])) + + return true; + if ($rule === 'hour' && in_array($freq, ['yearly', 'monthly', 'weekly', 'daily'])) + + return true; + if ($rule === 'minute') + + return true; + + return false; + } +} diff --git a/icalparser/src/IcalParser.php b/icalparser/src/IcalParser.php new file mode 100644 index 0000000..9a91656 --- /dev/null +++ b/icalparser/src/IcalParser.php @@ -0,0 +1,471 @@ + + */ +class IcalParser { + + /** @var \DateTimeZone */ + public $timezone; + + /** @var array */ + public $data = []; + + /** @var array */ + protected $counters = []; + + /** @var array */ + private $windowsTimezones; + + protected $arrayKeyMappings = [ + 'ATTACH' => 'ATTACHMENTS', + 'EXDATE' => 'EXDATES', + 'RDATE' => 'RDATES', + ]; + + public function __construct() { + $this->windowsTimezones = require __DIR__ . '/WindowsTimezones.php'; // load Windows timezones from separate file + } + + /** + * @param string $file + * @param null $callback + * @return array|null + * @throws \RuntimeException + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function parseFile($file, $callback = null) { + if (!$handle = fopen($file, 'r')) { + throw new \RuntimeException('Can\'t open file' . $file . ' for reading'); + } + fclose($handle); + + return $this->parseString(file_get_contents($file), $callback); + } + + /** + * @param string $string + * @param null $callback + * @param boolean $add if true the parsed string is added to existing data + * @return array|null + * @throws \InvalidArgumentException + * @throws \Exception + */ + public function parseString($string, $callback = null, $add = false) { + if ($add === false) { + // delete old data + $this->data = []; + $this->counters = []; + } + + if (!preg_match('/BEGIN:VCALENDAR/', $string)) { + throw new \InvalidArgumentException('Invalid ICAL data format'); + } + + $section = 'VCALENDAR'; + + // Replace \r\n with \n + $string = str_replace("\r\n", "\n", $string); + + // Unfold multi-line strings + $string = str_replace("\n ", '', $string); + + foreach (explode("\n", $string) as $row) { + + switch ($row) { + case 'BEGIN:DAYLIGHT': + case 'BEGIN:VALARM': + case 'BEGIN:VTIMEZONE': + case 'BEGIN:VFREEBUSY': + case 'BEGIN:VJOURNAL': + case 'BEGIN:STANDARD': + case 'BEGIN:VTODO': + case 'BEGIN:VEVENT': + $section = substr($row, 6); + $this->counters[$section] = isset($this->counters[$section]) ? $this->counters[$section] + 1 : 0; + continue 2; // while + break; + case 'END:VEVENT': + $section = substr($row, 4); + $currCounter = $this->counters[$section]; + $event = $this->data[$section][$currCounter]; + if (!empty($event['RECURRENCE-ID'])) { + $this->data['_RECURRENCE_IDS'][$event['RECURRENCE-ID']] = $event; + } + + continue 2; // while + break; + case 'END:DAYLIGHT': + case 'END:VALARM': + case 'END:VTIMEZONE': + case 'END:VFREEBUSY': + case 'END:VJOURNAL': + case 'END:STANDARD': + case 'END:VTODO': + continue 2; // while + break; + + case 'END:VCALENDAR': + $veventSection = 'VEVENT'; + if (!empty($this->data[$veventSection])) { + foreach ($this->data[$veventSection] as $currCounter => $event) { + if (!empty($event['RRULE']) || !empty($event['RDATE'])) { + $recurrences = $this->parseRecurrences($event); + if (!empty($recurrences)) { + $this->data[$veventSection][$currCounter]['RECURRENCES'] = $recurrences; + } + + if (!empty($event['UID'])) { + $this->data["_RECURRENCE_COUNTERS_BY_UID"][$event['UID']] = $currCounter; + } + } + } + } + continue 2; // while + break; + } + + list($key, $middle, $value) = $this->parseRow($row); + + + if ($callback) { + // call user function for processing line + call_user_func($callback, $row, $key, $middle, $value, $section, $this->counters[$section]); + } else { + if ($section === 'VCALENDAR') { + $this->data[$key] = $value; + } else { + if (isset($this->arrayKeyMappings[$key])) { + // use an array since there can be multiple entries for this key. This does not + // break the current implementation--it leaves the original key alone and adds + // a new one specifically for the array of values. + $arrayKey = $this->arrayKeyMappings[$key]; + $this->data[$section][$this->counters[$section]][$arrayKey][] = $value; + } + + $this->data[$section][$this->counters[$section]][$key] = $value; + } + + } + } + + return ($callback) ? null : $this->data; + } + + /** + * @param $row + * @return array + */ + private function parseRow($row) { + preg_match('#^([\w-]+);?([\w-]+="[^"]*"|.*?):(.*)$#i', $row, $matches); + + $key = false; + $middle = null; + $value = null; + + if ($matches) { + $key = $matches[1]; + $middle = $matches[2]; + $value = $matches[3]; + $timezone = null; + + if ($key === 'X-WR-TIMEZONE' || $key === 'TZID') { + if (preg_match('#(\w+/\w+)$#i', $value, $matches)) { + $value = $matches[1]; + } + $value = $this->toTimezone($value); + $this->timezone = new \DateTimeZone($value); + } + + // have some middle part ? + if ($middle && preg_match_all('#(?[^=;]+)=(?[^;]+)#', $middle, $matches, PREG_SET_ORDER)) { + $middle = []; + foreach ($matches as $match) { + if ($match['key'] === 'TZID') { + $match['value'] = trim($match['value'], "'\""); + $match['value'] = $this->toTimezone($match['value']); + try { + $middle[$match['key']] = $timezone = new \DateTimeZone($match['value']); + } catch (\Exception $e) { + $middle[$match['key']] = $match['value']; + } + } else if ($match['key'] === 'ENCODING') { + if ($match['value'] === 'QUOTED-PRINTABLE') { + $value = quoted_printable_decode($value); + } + } + } + } + } + + // process simple dates with timezone + if (in_array($key, ['DTSTAMP', 'LAST-MODIFIED', 'CREATED', 'DTSTART', 'DTEND'], true)) { + try { + $value = new \DateTime($value, ($timezone ?: $this->timezone)); + } catch (\Exception $e) { + $value = null; + } + } else if (in_array($key, ['EXDATE', 'RDATE'])) { + $values = []; + foreach (explode(',', $value) as $singleValue) { + try { + $values[] = new \DateTime($singleValue, ($timezone ?: $this->timezone)); + } catch (\Exception $e) { + // pass + } + } + if (count($values) === 1) { + $value = $values[0]; + } else { + $value = $values; + } + } + + if ($key === 'RRULE' && preg_match_all('#(?[^=;]+)=(?[^;]+)#', $value, $matches, PREG_SET_ORDER)) { + $middle = null; + $value = []; + foreach ($matches as $match) { + if (in_array($match['key'], ['UNTIL'])) { + try { + $value[$match['key']] = new \DateTime($match['value'], ($timezone ?: $this->timezone)); + } catch (\Exception $e) { + $value[$match['key']] = $match['value']; + } + } else { + $value[$match['key']] = $match['value']; + } + } + } + + //split by comma, escape \, + if ($key === 'CATEGORIES') { + $value = preg_split('/(? '\\', '\\N' => "\n", '\\n' => "\n", '\\;' => ';', '\\,' => ',']); + } + } else { + $value = strtr($value, ['\\\\' => '\\', '\\N' => "\n", '\\n' => "\n", '\\;' => ';', '\\,' => ',']); + } + } + + return [$key, $middle, $value]; + } + + /** + * @param $event + * @return array + * @throws \Exception + */ + public function parseRecurrences($event) { + $recurring = new Recurrence($event['RRULE']); + $exclusions = []; + $additions = []; + + if (!empty($event['EXDATES'])) { + foreach ($event['EXDATES'] as $exDate) { + if (is_array($exDate)) { + foreach ($exDate as $singleExDate) { + $exclusions[] = $singleExDate->getTimestamp(); + } + } else { + $exclusions[] = $exDate->getTimestamp(); + } + } + } + + if (!empty($event['RDATES'])) { + foreach ($event['RDATES'] as $rDate) { + if (is_array($rDate)) { + foreach ($rDate as $singleRDate) { + $additions[] = $singleRDate->getTimestamp(); + } + } else { + $additions[] = $rDate->getTimestamp(); + } + } + } + + $until = $recurring->getUntil(); + if ($until === false) { + //forever... limit to 3 years + $end = clone($event['DTSTART']); + $end->add(new \DateInterval('P3Y')); // + 3 years + $recurring->setUntil($end); + $until = $recurring->getUntil(); + } + + date_default_timezone_set($event['DTSTART']->getTimezone()->getName()); + $frequency = new Freq($recurring->rrule, $event['DTSTART']->getTimestamp(), $exclusions, $additions); + $recurrenceTimestamps = $frequency->getAllOccurrences(); + $recurrences = []; + foreach ($recurrenceTimestamps as $recurrenceTimestamp) { + $tmp = new \DateTime('now', $event['DTSTART']->getTimezone()); + $tmp->setTimestamp($recurrenceTimestamp); + + $recurrenceIDDate = $tmp->format('Ymd'); + $recurrenceIDDateTime = $tmp->format('Ymd\THis'); + if (empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDate]) && + empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDateTime])) { + $gmtCheck = new \DateTime("now", new \DateTimeZone('UTC')); + $gmtCheck->setTimestamp($recurrenceTimestamp); + $recurrenceIDDateTimeZ = $gmtCheck->format('Ymd\THis\Z'); + if (empty($this->data['_RECURRENCE_IDS'][$recurrenceIDDateTimeZ])) { + $recurrences[] = $tmp; + } + } + } + + return $recurrences; + } + + /** + * @return array + */ + public function getEvents() { + $events = []; + if (isset($this->data['VEVENT'])) { + for ($i = 0; $i < count($this->data['VEVENT']); $i++) { + $event = $this->data['VEVENT'][$i]; + + if (empty($event['RECURRENCES'])) { + if (!empty($event['RECURRENCE-ID']) && !empty($event['UID']) && isset($event['SEQUENCE'])) { + $modifiedEventUID = $event['UID']; + $modifiedEventRecurID = $event['RECURRENCE-ID']; + $modifiedEventSeq = intval($event['SEQUENCE'], 10); + + if (isset($this->data["_RECURRENCE_COUNTERS_BY_UID"][$modifiedEventUID])) { + $counter = $this->data["_RECURRENCE_COUNTERS_BY_UID"][$modifiedEventUID]; + + $originalEvent = $this->data["VEVENT"][$counter]; + if (isset($originalEvent['SEQUENCE'])) { + $originalEventSeq = intval($originalEvent['SEQUENCE'], 10); + $originalEventFormattedStartDate = $originalEvent['DTSTART']->format('Ymd\THis'); + if ($modifiedEventRecurID === $originalEventFormattedStartDate && $modifiedEventSeq > $originalEventSeq) { + // this modifies the original event + $modifiedEvent = array_replace_recursive($originalEvent, $event); + $this->data["VEVENT"][$counter] = $modifiedEvent; + foreach ($events as $z => $event) { + if ($events[$z]['UID'] === $originalEvent['UID'] && + $events[$z]['SEQUENCE'] === $originalEvent['SEQUENCE']) { + // replace the original event with the modified event + $events[$z] = $modifiedEvent; + break; + } + } + $event = null; // don't add this to the $events[] array again + } else if (!empty($originalEvent['RECURRENCES'])) { + for ($j = 0; $j < count($originalEvent['RECURRENCES']); $j++) { + $recurDate = $originalEvent['RECURRENCES'][$j]; + $formattedStartDate = $recurDate->format('Ymd\THis'); + if ($formattedStartDate === $modifiedEventRecurID) { + unset($this->data["VEVENT"][$counter]['RECURRENCES'][$j]); + $this->data["VEVENT"][$counter]['RECURRENCES'] = array_values($this->data["VEVENT"][$counter]['RECURRENCES']); + break; + } + } + } + } + } + } + + if (!empty($event)) { + $events[] = $event; + } + } else { + $recurrences = $event['RECURRENCES']; + $event['RECURRING'] = true; + $event['DTEND'] = !empty($event['DTEND']) ? $event['DTEND'] : $event['DTSTART']; + $eventInterval = $event['DTSTART']->diff($event['DTEND']); + + $firstEvent = true; + foreach ($recurrences as $j => $recurDate) { + $newEvent = $event; + if (!$firstEvent) { + unset($newEvent['RECURRENCES']); + $newEvent['DTSTART'] = $recurDate; + $newEvent['DTEND'] = clone($recurDate); + $newEvent['DTEND']->add($eventInterval); + } + + $newEvent['RECURRENCE_INSTANCE'] = $j; + $events[] = $newEvent; + $firstEvent = false; + } + } + } + } + return $events; + } + + + /** + * Process timezone and return correct one... + * + * @param string $zone + * @return mixed|null + */ + private function toTimezone($zone) { + return isset($this->windowsTimezones[$zone]) ? $this->windowsTimezones[$zone] : $zone; + } + + /** + * @return array + */ + public function getAlarms() { + return isset($this->data['VALARM']) ? $this->data['VALARM'] : []; + } + + /** + * @return array + */ + public function getTimezones() { + return isset($this->data['VTIMEZONE']) ? $this->data['VTIMEZONE'] : []; + } + + /** + * Return sorted event list as array + * + * @return array + */ + public function getSortedEvents() { + if ($events = $this->getEvents()) { + usort( + $events, function ($a, $b) { + return $a['DTSTART'] > $b['DTSTART']; + } + ); + return $events; + } + return []; + } + + /** + * @return array + */ + public function getReverseSortedEvents() { + if ($events = $this->getEvents()) { + usort( + $events, function ($a, $b) { + return $a['DTSTART'] < $b['DTSTART']; + } + ); + return $events; + } + return []; + } + +} diff --git a/icalparser/src/Recurrence.php b/icalparser/src/Recurrence.php new file mode 100644 index 0000000..b199532 --- /dev/null +++ b/icalparser/src/Recurrence.php @@ -0,0 +1,234 @@ +parseRrule($rrule); + } + + /** + * Parses an 'RRULE' array and sets the member variables of this object. + * Expects a string that looks like this: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=SU,TU,WE' + * + * @param $rrule + */ + protected function parseRrule($rrule) { + $this->rrule = $rrule; + //loop through the properties in the line and set their associated + //member variables + foreach ($this->rrule as $propertyName => $propertyValue) { + //need the lower-case name for setting the member variable + $propertyName = strtolower($propertyName); + //split up the list of values into an array (if it's a list) + if (in_array($propertyName, $this->listProperties, true)) { + $propertyValue = explode(',', $propertyValue); + } + $this->$propertyName = $propertyValue; + } + } + + /** + * Set the $until member + * + * @param mixed timestamp (int) / Valid DateTime format (string) + */ + public function setUntil($ts) { + if ($ts instanceof DateTime) { + $dt = $ts; + } else if (is_int($ts)) { + $dt = new DateTime('@' . $ts); + } else { + $dt = new DateTime($ts); + } + $this->until = $dt->format('Ymd\THisO'); + $this->rrule['until'] = $this->until; + } + + /** + * Retrieves the desired member variable and returns it (if it's set) + * + * @param string $member name of the member variable + * @return mixed the variable value (if set), false otherwise + */ + protected function getMember($member) { + if (isset($this->$member)) { + return $this->$member; + } + return false; + } + + /** + * Returns the frequency - corresponds to FREQ in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getFreq() { + return $this->getMember('freq'); + } + + /** + * Returns when the event will go until - corresponds to UNTIL in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getUntil() { + return $this->getMember('until'); + } + + /** + * Returns the count of the times the event will occur (should only appear if 'until' + * does not appear) - corresponds to COUNT in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getCount() { + return $this->getMember('count'); + } + + /** + * Returns the interval - corresponds to INTERVAL in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getInterval() { + return $this->getMember('interval'); + } + + /** + * Returns the bysecond part of the event - corresponds to BYSECOND in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getBySecond() { + return $this->getMember('bysecond'); + } + + /** + * Returns the byminute information for the event - corresponds to BYMINUTE in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMinute() { + return $this->getMember('byminute'); + } + + /** + * Corresponds to BYHOUR in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByHour() { + return $this->getMember('byhour'); + } + + /** + *Corresponds to BYDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByDay() { + return $this->getMember('byday'); + } + + /** + * Corresponds to BYMONTHDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMonthDay() { + return $this->getMember('bymonthday'); + } + + /** + * Corresponds to BYYEARDAY in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByYearDay() { + return $this->getMember('byyearday'); + } + + /** + * Corresponds to BYWEEKNO in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByWeekNo() { + return $this->getMember('byweekno'); + } + + /** + * Corresponds to BYMONTH in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getByMonth() { + return $this->getMember('bymonth'); + } + + /** + * Corresponds to BYSETPOS in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getBySetPos() { + return $this->getMember('bysetpos'); + } + + /** + * Corresponds to WKST in RFC 2445. + * + * @return mixed string if the member has been set, false otherwise + */ + public function getWkst() { + return $this->getMember('wkst'); + } +} diff --git a/icalparser/src/WindowsTimezones.php b/icalparser/src/WindowsTimezones.php new file mode 100644 index 0000000..f2105c3 --- /dev/null +++ b/icalparser/src/WindowsTimezones.php @@ -0,0 +1,210 @@ + 'Etc/GMT+12', + '(UTC-12:00) International Date Line West' => 'Etc/GMT+12', + 'UTC-11' => 'Etc/GMT+11', + '(UTC-11:00) Coordinated Universal Time -11' => 'Etc/GMT+11', + 'Hawaiian Standard Time' => 'Pacific/Honolulu', + '(UTC-10:00) Hawaii' => 'Pacific/Honolulu', + 'Alaskan Standard Time' => 'America/Anchorage', + '(UTC-09:00) Alaska' => 'America/Anchorage', + 'Pacific Standard Time (Mexico)' => 'America/Santa_Isabel', + '(UTC-08:00) Baja California' => 'America/Santa_Isabel', + 'Pacific Standard Time' => 'America/Los_Angeles', + 'Pacific Time' => 'America/Los_Angeles', + '(UTC-08:00) Pacific Time (US and Canada)' => 'America/Los_Angeles', + '(UTC-08:00) Pacific Time (US & Canada)' => 'America/Los_Angeles', + 'US Mountain Standard Time' => 'America/Phoenix', + '(UTC-07:00) Arizona' => 'America/Phoenix', + 'Mountain Standard Time (Mexico)' => 'America/Chihuahua', + '(UTC-07:00) Chihuahua, La Paz, Mazatlan' => 'America/Chihuahua', + 'Mountain Standard Time' => 'America/Denver', + 'Mountain Time' => 'America/Denver', + '(UTC-07:00) Mountain Time (US and Canada)' => 'America/Denver', + '(UTC-07:00) Mountain Time (US & Canada)' => 'America/Denver', + 'Central America Standard Time' => 'America/Guatemala', + '(UTC-06:00) Central America' => 'America/Guatemala', + 'Central Standard Time' => 'America/Chicago', + 'Central Time' => 'America/Chicago', + '(UTC-06:00) Central Time (US and Canada)' => 'America/Chicago', + '(UTC-06:00) Central Time (US & Canada)' => 'America/Chicago', + 'Central Standard Time (Mexico)' => 'America/Mexico_City', + '(UTC-06:00) Guadalajara, Mexico City, Monterrey' => 'America/Mexico_City', + 'Canada Central Standard Time' => 'America/Regina', + '(UTC-06:00) Saskatchewan' => 'America/Regina', + 'SA Pacific Standard Time' => 'America/Bogota', + '(UTC-05:00) Bogota, Lima, Quito' => 'America/Bogota', + 'Eastern Standard Time' => 'America/New_York', + 'Eastern Time' => 'America/New_York', + '(UTC-05:00) Eastern Time (US and Canada)' => 'America/New_York', + '(UTC-05:00) Eastern Time (US & Canada)' => 'America/New_York', + 'US Eastern Standard Time' => 'America/Indianapolis', + '(UTC-05:00) Indiana (East)' => 'America/Indianapolis', + 'Venezuela Standard Time' => 'America/Caracas', + '(UTC-04:30) Caracas' => 'America/Caracas', + 'Paraguay Standard Time' => 'America/Asuncion', + '(UTC-04:00) Asuncion' => 'America/Asuncion', + 'Atlantic Standard Time' => 'America/Halifax', + '(UTC-04:00) Atlantic Time (Canada)' => 'America/Halifax', + 'Central Brazilian Standard Time' => 'America/Cuiaba', + '(UTC-04:00) Cuiaba' => 'America/Cuiaba', + 'SA Western Standard Time' => 'America/La_Paz', + '(UTC-04:00) Georgetown, La Paz, Manaus, San Juan' => 'America/La_Paz', + 'Pacific SA Standard Time' => 'America/Santiago', + '(UTC-04:00) Santiago' => 'America/Santiago', + 'Newfoundland Standard Time' => 'America/St_Johns', + '(UTC-03:30) Newfoundland' => 'America/St_Johns', + 'E. South America Standard Time' => 'America/Sao_Paulo', + '(UTC-03:00) Brasilia' => 'America/Sao_Paulo', + 'Argentina Standard Time' => 'America/Buenos_Aires', + '(UTC-03:00) Buenos Aires' => 'America/Buenos_Aires', + 'SA Eastern Standard Time' => 'America/Cayenne', + '(UTC-03:00) Cayenne, Fortaleza' => 'America/Cayenne', + 'Greenland Standard Time' => 'America/Godthab', + '(UTC-03:00) Greenland' => 'America/Godthab', + 'Montevideo Standard Time' => 'America/Montevideo', + '(UTC-03:00) Montevideo' => 'America/Montevideo', + 'Bahia Standard Time' => 'America/Bahia', + 'UTC-02' => 'Etc/GMT+2', + '(UTC-02:00) Coordinated Universal Time -02' => 'Etc/GMT+2', + 'Azores Standard Time' => 'Atlantic/Azores', + '(UTC-01:00) Azores' => 'Atlantic/Azores', + 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', + '(UTC-01:00) Cabo Verde Is.' => 'Atlantic/Cape_Verde', + 'Morocco Standard Time' => 'Africa/Casablanca', + '(UTC) Casablanca' => 'Africa/Casablanca', + 'UTC' => 'Etc/GMT', + 'Microsoft/Utc' => 'Etc/GMT', + 'GMT Standard Time' => 'Europe/London', + '(UTC) Dublin, Edinburgh, Lisbon, London' => 'Europe/London', + 'Greenwich Standard Time' => 'Atlantic/Reykjavik', + '(UTC) Monrovia, Reykjavik' => 'Atlantic/Reykjavik', + 'W. Europe Standard Time' => 'Europe/Berlin', + '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna' => 'Europe/Berlin', + 'Central Europe Standard Time' => 'Europe/Budapest', + '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague' => 'Europe/Budapest', + 'Romance Standard Time' => 'Europe/Paris', + '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris' => 'Europe/Paris', + 'Central European Standard Time' => 'Europe/Warsaw', + '(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb' => 'Europe/Warsaw', + 'W. Central Africa Standard Time' => 'Africa/Lagos', + '(UTC+01:00) West Central Africa' => 'Africa/Lagos', + 'Namibia Standard Time' => 'Africa/Windhoek', + '(UTC+01:00) Windhoek' => 'Africa/Windhoek', + 'GTB Standard Time' => 'Europe/Bucharest', + '(UTC+02:00) Athens, Bucharest' => 'Europe/Bucharest', + 'Middle East Standard Time' => 'Asia/Beirut', + '(UTC+02:00) Beirut' => 'Asia/Beirut', + 'Egypt Standard Time' => 'Africa/Cairo', + '(UTC+02:00) Cairo' => 'Africa/Cairo', + 'Syria Standard Time' => 'Asia/Damascus', + '(UTC+02:00) Damascus' => 'Asia/Damascus', + 'South Africa Standard Time' => 'Africa/Johannesburg', + '(UTC+02:00) Harare, Pretoria' => 'Africa/Johannesburg', + 'FLE Standard Time' => 'Europe/Kiev', + '(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius' => 'Europe/Kiev', + 'Turkey Standard Time' => 'Europe/Istanbul', + '(UTC+02:00) Istanbul' => 'Europe/Istanbul', + 'Israel Standard Time' => 'Asia/Jerusalem', + '(UTC+02:00) Jerusalem' => 'Asia/Jerusalem', + 'Libya Standard Time' => 'Africa/Tripoli', + 'Jordan Standard Time' => 'Asia/Amman', + '(UTC+02:00) Amman' => 'Asia/Amman', + 'Arabic Standard Time' => 'Asia/Baghdad', + '(UTC+03:00) Baghdad' => 'Asia/Baghdad', + 'Kaliningrad Standard Time' => 'Europe/Kaliningrad', + '(UTC+03:00) Kaliningrad' => 'Europe/Kaliningrad', + 'Arab Standard Time' => 'Asia/Riyadh', + '(UTC+03:00) Kuwait, Riyadh' => 'Asia/Riyadh', + 'E. Africa Standard Time' => 'Africa/Nairobi', + '(UTC+03:00) Nairobi' => 'Africa/Nairobi', + 'Iran Standard Time' => 'Asia/Tehran', + '(UTC+03:30) Tehran' => 'Asia/Tehran', + 'Arabian Standard Time' => 'Asia/Dubai', + '(UTC+04:00) Abu Dhabi, Muscat' => 'Asia/Dubai', + 'Azerbaijan Standard Time' => 'Asia/Baku', + '(UTC+04:00) Baku' => 'Asia/Baku', + 'Russian Standard Time' => 'Europe/Moscow', + '(UTC+04:00) Moscow, St. Petersburg, Volgograd' => 'Europe/Moscow', + 'Mauritius Standard Time' => 'Indian/Mauritius', + '(UTC+04:00) Port Louis' => 'Indian/Mauritius', + 'Georgian Standard Time' => 'Asia/Tbilisi', + '(UTC+04:00) Tbilisi' => 'Asia/Tbilisi', + 'Caucasus Standard Time' => 'Asia/Yerevan', + '(UTC+04:00) Yerevan' => 'Asia/Yerevan', + 'Afghanistan Standard Time' => 'Asia/Kabul', + '(UTC+04:30) Kabul' => 'Asia/Kabul', + 'West Asia Standard Time' => 'Asia/Tashkent', + '(UTC+05:00) Tashkent' => 'Asia/Tashkent', + 'Pakistan Standard Time' => 'Asia/Karachi', + '(UTC+05:00) Islamabad, Karachi' => 'Asia/Karachi', + 'India Standard Time' => 'Asia/Calcutta', + '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi' => 'Asia/Calcutta', + 'Sri Lanka Standard Time' => 'Asia/Colombo', + '(UTC+05:30) Sri Jayawardenepura' => 'Asia/Colombo', + 'Nepal Standard Time' => 'Asia/Katmandu', + '(UTC+05:45) Kathmandu' => 'Asia/Katmandu', + 'Central Asia Standard Time' => 'Asia/Almaty', + '(UTC+06:00) Astana' => 'Asia/Almaty', + 'Bangladesh Standard Time' => 'Asia/Dhaka', + '(UTC+06:00) Dhaka' => 'Asia/Dhaka', + 'Ekaterinburg Standard Time' => 'Asia/Yekaterinburg', + '(UTC+06:00) Ekaterinburg' => 'Asia/Yekaterinburg', + 'Myanmar Standard Time' => 'Asia/Rangoon', + '(UTC+06:30) Yangon (Rangoon)' => 'Asia/Rangoon', + 'SE Asia Standard Time' => 'Asia/Bangkok', + '(UTC+07:00) Bangkok, Hanoi, Jakarta' => 'Asia/Bangkok', + 'N. Central Asia Standard Time' => 'Asia/Novosibirsk', + '(UTC+07:00) Novosibirsk' => 'Asia/Novosibirsk', + 'China Standard Time' => 'Asia/Shanghai', + '(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi' => 'Asia/Shanghai', + 'North Asia Standard Time' => 'Asia/Krasnoyarsk', + '(UTC+08:00) Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Singapore Standard Time' => 'Asia/Singapore', + '(UTC+08:00) Kuala Lumpur, Singapore' => 'Asia/Singapore', + 'W. Australia Standard Time' => 'Australia/Perth', + '(UTC+08:00) Perth' => 'Australia/Perth', + 'Taipei Standard Time' => 'Asia/Taipei', + '(UTC+08:00) Taipei' => 'Asia/Taipei', + 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', + '(UTC+08:00) Ulaanbaatar' => 'Asia/Ulaanbaatar', + 'North Asia East Standard Time' => 'Asia/Irkutsk', + '(UTC+09:00) Irkutsk' => 'Asia/Irkutsk', + 'Tokyo Standard Time' => 'Asia/Tokyo', + '(UTC+09:00) Osaka, Sapporo, Tokyo' => 'Asia/Tokyo', + 'Korea Standard Time' => 'Asia/Seoul', + '(UTC+09:00) Seoul' => 'Asia/Seoul', + 'Cen. Australia Standard Time' => 'Australia/Adelaide', + '(UTC+09:30) Adelaide' => 'Australia/Adelaide', + 'AUS Central Standard Time' => 'Australia/Darwin', + '(UTC+09:30) Darwin' => 'Australia/Darwin', + 'E. Australia Standard Time' => 'Australia/Brisbane', + '(UTC+10:00) Brisbane' => 'Australia/Brisbane', + 'AUS Eastern Standard Time' => 'Australia/Sydney', + '(UTC+10:00) Canberra, Melbourne, Sydney' => 'Australia/Sydney', + 'West Pacific Standard Time' => 'Pacific/Port_Moresby', + '(UTC+10:00) Guam, Port Moresby' => 'Pacific/Port_Moresby', + 'Tasmania Standard Time' => 'Australia/Hobart', + '(UTC+10:00) Hobart' => 'Australia/Hobart', + 'Yakutsk Standard Time' => 'Asia/Yakutsk', + '(UTC+10:00) Yakutsk' => 'Asia/Yakutsk', + 'Central Pacific Standard Time' => 'Pacific/Guadalcanal', + '(UTC+11:00) Solomon Is., New Caledonia' => 'Pacific/Guadalcanal', + 'Vladivostok Standard Time' => 'Asia/Vladivostok', + '(UTC+11:00) Vladivostok' => 'Asia/Vladivostok', + 'New Zealand Standard Time' => 'Pacific/Auckland', + '(UTC+12:00) Auckland, Wellington' => 'Pacific/Auckland', + 'UTC+12' => 'Etc/GMT-12', + '(UTC+12:00) Coordinated Universal Time +12' => 'Etc/GMT-12', + 'Fiji Standard Time' => 'Pacific/Fiji', + '(UTC+12:00) Fiji' => 'Pacific/Fiji', + 'Magadan Standard Time' => 'Asia/Magadan', + '(UTC+12:00) Magadan' => 'Asia/Magadan', + 'Tonga Standard Time' => 'Pacific/Tongatapu', + '(UTC+13:00) Nuku\'alofa' => 'Pacific/Tongatapu', + 'Samoa Standard Time' => 'Pacific/Apia', + '(UTC-11:00)Samoa' => 'Pacific/Apia', +]; \ No newline at end of file diff --git a/icalparser/tests/blank_description.phpt b/icalparser/tests/blank_description.phpt new file mode 100644 index 0000000..8ca0ec0 --- /dev/null +++ b/icalparser/tests/blank_description.phpt @@ -0,0 +1,19 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); +date_default_timezone_set('Europe/Prague'); + +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/blank_description.ics'); + +Assert::same('', $results['VEVENT'][0]['DESCRIPTION']); +Assert::same('America/Los_Angeles', $cal->timezone->getName()); + +Assert::same($results['DAYLIGHT'][0]['RRULE']['FREQ'], 'YEARLY'); diff --git a/icalparser/tests/cal/FrenchHolidays.ics b/icalparser/tests/cal/FrenchHolidays.ics new file mode 100644 index 0000000..efb0143 --- /dev/null +++ b/icalparser/tests/cal/FrenchHolidays.ics @@ -0,0 +1,382 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +BEGIN:VTIMEZONE +TZID:/mozilla.org/20070129_1/Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20070606T141629Z +LAST-MODIFIED:20070606T154611Z +DTSTAMP:20070607T120859Z +UID:5d1ae55f-3910-4de9-8b65-d652768fb2f2 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070409 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070410 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T161310Z +LAST-MODIFIED:20070606T161327Z +DTSTAMP:20070607T120859Z +UID:9e19b119-f077-4ae9-934e-cb62322ca81f +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080501 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080502 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070605T163903Z +LAST-MODIFIED:20070606T161656Z +DTSTAMP:20070607T120859Z +UID:c6a930d6-4ed5-45d8-bb3d-d3587a32b8aa +SUMMARY:Jour de l'an +CLASS:PUBLIC +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070101 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070102 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T141739Z +LAST-MODIFIED:20070606T161721Z +DTSTAMP:20070607T120859Z +UID:f439c81b-fd09-4b40-a629-3a9663dd29ff +SUMMARY:Fête du Travail +CLASS:PUBLIC +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070501 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070502 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T141853Z +LAST-MODIFIED:20070606T161731Z +DTSTAMP:20070607T120859Z +UID:b1c0e1ed-a09f-4fc2-aab1-170d3f661f13 +SUMMARY:Armistice 1945 +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070508 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070509 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T141932Z +LAST-MODIFIED:20070606T161741Z +DTSTAMP:20070607T120859Z +UID:5468e1a0-9dda-4fd2-88c0-dc3e727c1183 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070517 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070518 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T154927Z +LAST-MODIFIED:20070606T161748Z +DTSTAMP:20070607T120859Z +UID:67a89bb6-9ad4-461b-951c-c4c8482d8618 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070528 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070529 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T142033Z +LAST-MODIFIED:20070606T161758Z +DTSTAMP:20070607T120859Z +UID:00eb3adc-c059-47c8-a1c0-4ca7048051b9 +SUMMARY:Fête Nationale +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070714 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070715 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T142112Z +LAST-MODIFIED:20070606T162012Z +DTSTAMP:20070607T120859Z +UID:854e5499-9276-4a96-9be3-bcbaa7caafcf +SUMMARY:Assomption +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070815 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20070816 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T142147Z +LAST-MODIFIED:20070606T162034Z +DTSTAMP:20070607T120859Z +UID:725c8c16-c37a-49f0-9d2e-dc976d6c3ea5 +SUMMARY:Toussaint +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071101 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071102 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T142211Z +LAST-MODIFIED:20070606T162044Z +DTSTAMP:20070607T120859Z +UID:5218f724-6b66-434d-a8c5-138dfed64e07 +SUMMARY:Armistice 1918 +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071111 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071112 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T142243Z +LAST-MODIFIED:20070606T162054Z +DTSTAMP:20070607T120859Z +UID:18617cde-2d15-46c6-900f-b3341a7b7f98 +SUMMARY:Noël +RRULE:FREQ=YEARLY;INTERVAL=1 +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071225 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20071226 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T150648Z +LAST-MODIFIED:20070606T162128Z +DTSTAMP:20070607T120859Z +UID:19d50a52-f5a9-4d70-8fd7-a2caa97f6959 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080324 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080325 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T105427Z +LAST-MODIFIED:20070607T105450Z +DTSTAMP:20070607T120859Z +UID:18c90b99-edfa-4418-9bc7-992a07384967 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090413 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090414 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T105613Z +LAST-MODIFIED:20070607T105630Z +DTSTAMP:20070607T120859Z +UID:b989d129-8f2f-42be-a027-183f6d81eb40 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090521 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090522 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T105907Z +LAST-MODIFIED:20070607T105928Z +DTSTAMP:20070607T120859Z +UID:7c6fe35c-44a5-450b-aea6-dd2baef78f06 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090601 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20090602 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T110046Z +LAST-MODIFIED:20070607T110100Z +DTSTAMP:20070607T120859Z +UID:98c1c381-8632-45ce-9b11-10757face7c4 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100405 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100406 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T110121Z +LAST-MODIFIED:20070607T110135Z +DTSTAMP:20070607T120859Z +UID:aaeb1ba5-60f0-4e49-a25a-c0ff949acdd9 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100513 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100514 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T110144Z +LAST-MODIFIED:20070607T110201Z +DTSTAMP:20070607T120859Z +UID:1f68f978-969e-4791-a7e3-cb3011134a34 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100524 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20100525 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T112820Z +LAST-MODIFIED:20070607T112836Z +DTSTAMP:20070607T120859Z +UID:fa0d4b5b-8eeb-4b98-b35c-b6a547b96953 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110602 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110603 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T112846Z +LAST-MODIFIED:20070607T112906Z +DTSTAMP:20070607T120859Z +UID:5be6194a-9fbd-45da-8e3c-568cac66b997 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110613 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110614 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T112958Z +LAST-MODIFIED:20070607T113016Z +DTSTAMP:20070607T120859Z +UID:4162d242-5468-4007-8814-10286b9589a0 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120409 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120410 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113034Z +LAST-MODIFIED:20070607T113048Z +DTSTAMP:20070607T120859Z +UID:6d368a35-6b6d-4ec1-9f48-97c53b25502e +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120517 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120518 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113055Z +LAST-MODIFIED:20070607T113114Z +DTSTAMP:20070607T120859Z +UID:e8913a3d-3e84-447d-afb4-f0b082948449 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120528 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20120529 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113205Z +LAST-MODIFIED:20070607T113229Z +DTSTAMP:20070607T120859Z +UID:ebc79872-a185-4641-bc22-1092d5139efb +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130401 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130402 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113245Z +LAST-MODIFIED:20070607T113257Z +DTSTAMP:20070607T120859Z +UID:9714bd39-429b-433c-ab5c-5fd6a67e658e +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130509 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130510 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113305Z +LAST-MODIFIED:20070607T113325Z +DTSTAMP:20070607T120859Z +UID:79f5f02f-e92d-417f-9637-9e2a88840583 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130520 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20130521 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113408Z +LAST-MODIFIED:20070607T113426Z +DTSTAMP:20070607T120859Z +UID:172dcce6-6f95-4c1f-ada9-e8bf2a7e8245 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140421 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140422 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113439Z +LAST-MODIFIED:20070607T113452Z +DTSTAMP:20070607T120859Z +UID:6625ed89-bf9f-478a-9796-c3dc203ab5e6 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140529 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140530 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113504Z +LAST-MODIFIED:20070607T113523Z +DTSTAMP:20070607T120859Z +UID:7abad826-ab68-447f-81b2-1e04a4c2ef88 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140609 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20140610 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113606Z +LAST-MODIFIED:20070607T113622Z +DTSTAMP:20070607T120859Z +UID:cf42813f-4a74-422b-aa19-90dcefccc92d +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150406 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150407 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113643Z +LAST-MODIFIED:20070607T113657Z +DTSTAMP:20070607T120859Z +UID:e7d30f2b-d546-4b7e-8780-0754a46e6970 +SUMMARY:Ascension +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150514 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150515 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T113706Z +LAST-MODIFIED:20070607T113726Z +DTSTAMP:20070607T120859Z +UID:a8ccc771-4e8b-4594-ae30-33e9ce384ac9 +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150525 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20150526 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070606T162350Z +LAST-MODIFIED:20070607T115133Z +DTSTAMP:20070607T120859Z +UID:419f1bff-a820-4d07-bf4e-1a6ec139e3df +SUMMARY:Lundi de Pentecôte +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080512 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20080513 +CATEGORIES:Jours fériés +END:VEVENT +BEGIN:VEVENT +CREATED:20070607T112742Z +LAST-MODIFIED:20070607T115439Z +DTSTAMP:20070607T120859Z +UID:570d9fe7-cdc9-4fdf-9794-909edf520383 +SUMMARY:Lundi de Pâques +DTSTART;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110425 +DTEND;VALUE=DATE;TZID=/mozilla.org/20070129_1/Europe/Paris:20110426 +CATEGORIES:Jours fériés +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/basic.ics b/icalparser/tests/cal/basic.ics new file mode 100644 index 0000000..0c58974 --- /dev/null +++ b/icalparser/tests/cal/basic.ics @@ -0,0 +1,616 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-TIMEZONE:UTC +X-WR-CALDESC:České svátky +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131224 +DTEND;VALUE=DATE:20131225 +DTSTAMP:20140202T201100Z +UID:h@9bae7e86083abdfdff45712c19ef81bc41f44111@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Štedrý den +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141224 +DTEND;VALUE=DATE:20141225 +DTSTAMP:20140202T201100Z +UID:h@26c4829203feb7deebebdf226285322a079b9b7c@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Štedrý den +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20151224 +DTEND;VALUE=DATE:20151225 +DTSTAMP:20140202T201100Z +UID:h@1c3d67eb7865010f0ee809055f7c465124091d1b@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Štedrý den +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150406 +DTEND;VALUE=DATE:20150407 +DTSTAMP:20140202T201100Z +UID:h@e5a21162ed9d250ee5f689058366a09f10cf4ccb@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Velikonoční pondělí +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140421 +DTEND;VALUE=DATE:20140422 +DTSTAMP:20140202T201100Z +UID:h@9595a34a188b5376d452c829499a8f8354ec6049@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Velikonoční pondělí +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150501 +DTEND;VALUE=DATE:20150502 +DTSTAMP:20140202T201100Z +UID:h@7470a1d77684e63d5aa048babb939885ad1066c5@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Svátek práce +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140501 +DTEND;VALUE=DATE:20140502 +DTSTAMP:20140202T201100Z +UID:h@59ea753d474913d8c7ab474eaba701f8ec5c2ba1@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Svátek práce +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130501 +DTEND;VALUE=DATE:20130502 +DTSTAMP:20140202T201100Z +UID:h@19480e43102b11f061a7fc231692a6504e6f55de@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Svátek práce +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140101 +DTEND;VALUE=DATE:20140102 +DTSTAMP:20140202T201100Z +UID:h@cdcc3450b77b919b7e5c1d0186439b81fe315723@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Nový rok +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130101 +DTEND;VALUE=DATE:20130102 +DTSTAMP:20140202T201100Z +UID:h@93d93af5add7e8e8542f9d2be9bf9b189adc7088@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Nový rok +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150101 +DTEND;VALUE=DATE:20150102 +DTSTAMP:20140202T201100Z +UID:h@3be21c366071b22c3322e48e898803d89a2873c8@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Nový rok +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140928 +DTEND;VALUE=DATE:20140929 +DTSTAMP:20140202T201100Z +UID:h@910a92f331886c719cdab7cae0f8d56387788789@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den české státnosti +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130928 +DTEND;VALUE=DATE:20130929 +DTSTAMP:20140202T201100Z +UID:h@4bdee7defc8620b327b23400a9da3be5d0721a10@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den české státnosti +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150928 +DTEND;VALUE=DATE:20150929 +DTSTAMP:20140202T201100Z +UID:h@0534aebb7a00356b125529a447b38be655046ca9@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den české státnosti +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150508 +DTEND;VALUE=DATE:20150509 +DTSTAMP:20140202T201100Z +UID:h@f234114d721bac5776f1683f23a58ed2ffd53c84@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vítězství +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130508 +DTEND;VALUE=DATE:20130509 +DTSTAMP:20140202T201100Z +UID:h@5b971aece3bf450de53261bafe25898080af2454@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vítězství +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140508 +DTEND;VALUE=DATE:20140509 +DTSTAMP:20140202T201100Z +UID:h@574f6e8b741d1eb0e80ed4caea1441858393ff58@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vítězství +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131028 +DTEND;VALUE=DATE:20131029 +DTSTAMP:20140202T201100Z +UID:h@be894303693f374fad9017da7ac6391c23eca0f6@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vzniku samostatného československého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20151028 +DTEND;VALUE=DATE:20151029 +DTSTAMP:20140202T201100Z +UID:h@7e708d1f45342cd36f819e1f0d3d9f9b7eacc02f@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vzniku samostatného československého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141028 +DTEND;VALUE=DATE:20141029 +DTSTAMP:20140202T201100Z +UID:h@4dcdf85aaa09aea21fd49914085b47cdb44ccf0f@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den vzniku samostatného československého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150706 +DTEND;VALUE=DATE:20150707 +DTSTAMP:20140202T201100Z +UID:h@d2253851fcafd5063bdbece5367507e338b5eea0@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den upálení mistra Jana Husa +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140706 +DTEND;VALUE=DATE:20140707 +DTSTAMP:20140202T201100Z +UID:h@2f980e6bc7f0445430367c33d3e4a27441196199@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den upálení mistra Jana Husa +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130706 +DTEND;VALUE=DATE:20130707 +DTSTAMP:20140202T201100Z +UID:h@09c62186abafba1bac59818bde105765a7a02121@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den upálení mistra Jana Husa +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150705 +DTEND;VALUE=DATE:20150706 +DTSTAMP:20140202T201100Z +UID:h@df873adcd2b61ea7c1b13761d7561b9fe6faae5c@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den slovanských věrozvěstů Cyrila a Metoděje +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140705 +DTEND;VALUE=DATE:20140706 +DTSTAMP:20140202T201100Z +UID:h@cc790a245031c0186f1d6d9ab8560db87c86269d@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den slovanských věrozvěstů Cyrila a Metoděje +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130705 +DTEND;VALUE=DATE:20130706 +DTSTAMP:20140202T201100Z +UID:h@4b4d8fc982e6734d799afdd5de90041a009e56b5@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den slovanských věrozvěstů Cyrila a Metoděje +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20150101 +DTEND;VALUE=DATE:20150102 +DTSTAMP:20140202T201100Z +UID:h@e35dfa2052d4cf25d186e005626c66b794792d35@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den obnovy samostatného českého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20130101 +DTEND;VALUE=DATE:20130102 +DTSTAMP:20140202T201100Z +UID:h@e13a9a864c4beec0b4f1055fb4e1bb14076b5a9b@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den obnovy samostatného českého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140101 +DTEND;VALUE=DATE:20140102 +DTSTAMP:20140202T201100Z +UID:h@a87543a1136d4a980a8037471eb49ea00f087c81@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den obnovy samostatného českého státu +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141117 +DTEND;VALUE=DATE:20141118 +DTSTAMP:20140202T201100Z +UID:h@94e14807e30f52a82572a2cd28a51c89a033d09f@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den boje za svobodu a demokracii +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131117 +DTEND;VALUE=DATE:20131118 +DTSTAMP:20140202T201100Z +UID:h@873a2a1499e25477876926c6729dc214b2f1f0f9@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den boje za svobodu a demokracii +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20151117 +DTEND;VALUE=DATE:20151118 +DTSTAMP:20140202T201100Z +UID:h@599f0d6373565c265b3491b084a2c6198b0b00b9@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Den boje za svobodu a demokracii +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20151226 +DTEND;VALUE=DATE:20151227 +DTSTAMP:20140202T201100Z +UID:h@e65fdbb6f9703e75e775c9f407f55663c80d4764@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:2. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141226 +DTEND;VALUE=DATE:20141227 +DTSTAMP:20140202T201100Z +UID:h@807088064c072183b2fa351251155bff44f09787@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:2. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131226 +DTEND;VALUE=DATE:20131227 +DTSTAMP:20140202T201100Z +UID:h@6165f686632ab29cdd7bdb46a4c3578a48045428@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:2. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141225 +DTEND;VALUE=DATE:20141226 +DTSTAMP:20140202T201100Z +UID:h@e2f049ac27b03ada54a27294becbcd1cc2680d9a@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:1. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131225 +DTEND;VALUE=DATE:20131226 +DTSTAMP:20140202T201100Z +UID:h@7f26c52a3d0e69b81f2fc35490224899490c0014@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:1. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20151225 +DTEND;VALUE=DATE:20151226 +DTSTAMP:20140202T201100Z +UID:h@3ddadf7953340f067a4e3b07a4621f9343ff1518@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE + STS=0:mailto:cdpisorqclhmg8r8dtm6ip31f506esjfelo2sthecdgmopbechgn4bj7dtnmer + 355phmur8%40virtual +CLASS:PUBLIC +CREATED:20140202T183416Z +LAST-MODIFIED:20140202T183416Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:1. svátek vánoční +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/blank_description.ics b/icalparser/tests/cal/blank_description.ics new file mode 100755 index 0000000..568d7e0 --- /dev/null +++ b/icalparser/tests/cal/blank_description.ics @@ -0,0 +1,51 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTEND;TZID=America/Los_Angeles:20120630T070000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER:-P0DT0H30M0S +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H30M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/blank_line_end.ics b/icalparser/tests/cal/blank_line_end.ics new file mode 100755 index 0000000..2c397c2 --- /dev/null +++ b/icalparser/tests/cal/blank_line_end.ics @@ -0,0 +1,4 @@ +BEGIN:VCALENDAR +END:VCALENDAR + + diff --git a/icalparser/tests/cal/blank_line_mid.ics b/icalparser/tests/cal/blank_line_mid.ics new file mode 100755 index 0000000..5e54e2e --- /dev/null +++ b/icalparser/tests/cal/blank_line_mid.ics @@ -0,0 +1,4 @@ +BEGIN:VCALENDAR +COMMENT:This blank line is invalid + +END:VCALENDAR diff --git a/icalparser/tests/cal/daily_recur.ics b/icalparser/tests/cal/daily_recur.ics new file mode 100755 index 0000000..9983098 --- /dev/null +++ b/icalparser/tests/cal/daily_recur.ics @@ -0,0 +1,52 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120801T050000 +DTEND;TZID=America/Los_Angeles:20120801T060000 +RRULE:FREQ=DAILY +DTSTAMP:20120803T221236Z +UID:tgh9qho17b07pk2n2ji3gluans@google.com +CREATED:20120803T221236Z +DESCRIPTION: +LAST-MODIFIED:20120803T221236Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Every day recurring +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER:-P0DT0H30M0S +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H30M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/daily_recur2.ics b/icalparser/tests/cal/daily_recur2.ics new file mode 100755 index 0000000..346f54e --- /dev/null +++ b/icalparser/tests/cal/daily_recur2.ics @@ -0,0 +1,41 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170821 +DTEND;VALUE=DATE:20170822 +RRULE:FREQ=WEEKLY;UNTIL=20170911;BYDAY=MO +DTSTAMP:20170818T191547Z +UID:37mfdqtlcrrvbil9b3n7vicb1t@google.com +CLASS:PUBLIC +CREATED:20170818T175332Z +DESCRIPTION: +LAST-MODIFIED:20170818T175332Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Late Start\, SMS +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/day_long_recur_yearly.ics b/icalparser/tests/cal/day_long_recur_yearly.ics new file mode 100755 index 0000000..0c0aeb5 --- /dev/null +++ b/icalparser/tests/cal/day_long_recur_yearly.ics @@ -0,0 +1,52 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20120803 +DTEND;VALUE=DATE:20120804 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +DTSTAMP:20120803T221306Z +UID:4pfh824gvims850j0gar361t04@google.com +CREATED:20120803T221306Z +DESCRIPTION: +LAST-MODIFIED:20120803T221306Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Day Long Event +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER;VALUE=DATE-TIME:20120802T233000Z +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER;VALUE=DATE-TIME:20120802T233000Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/forced_types.ics b/icalparser/tests/cal/forced_types.ics new file mode 100755 index 0000000..f213f01 --- /dev/null +++ b/icalparser/tests/cal/forced_types.ics @@ -0,0 +1,50 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20120904 +DTEND;VALUE=DATE:20120905 +DTSTAMP:20120905T084734Z +UID:redgrb1l0aju5edm6h0s102eu4@google.com +CREATED:20120905T084734Z +DESCRIPTION: +LAST-MODIFIED:20120905T084734Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Event +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER;VALUE=DATE-TIME:20120903T233000Z +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/google_birthday.ics b/icalparser/tests/cal/google_birthday.ics new file mode 100755 index 0000000..f9b7c9d --- /dev/null +++ b/icalparser/tests/cal/google_birthday.ics @@ -0,0 +1,90 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:Contacts' birthdays and events +X-WR-TIMEZONE:America/Los_Angeles +X-WR-CALDESC:Your contacts' birthdays and anniversaries +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141210 +DTEND;VALUE=DATE:20141211 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1 +RDATE:20131210Z +RDATE:20121210Z +DTSTAMP:20121207T183041Z +UID:2014_BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +CLASS:PUBLIC +CREATED:20121207T183041Z +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20121210 +DTEND;VALUE=DATE:20121211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20121210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131210 +DTEND;VALUE=DATE:20131211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20131210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141210 +DTEND;VALUE=DATE:20141211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20141210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/minimal.ics b/icalparser/tests/cal/minimal.ics new file mode 100755 index 0000000..b97d2ac --- /dev/null +++ b/icalparser/tests/cal/minimal.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTEND;TZID=America/Los_Angeles:20120630T070000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/multiline_description.ics b/icalparser/tests/cal/multiline_description.ics new file mode 100644 index 0000000..b5ff35c --- /dev/null +++ b/icalparser/tests/cal/multiline_description.ics @@ -0,0 +1,52 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTEND;TZID=America/Los_Angeles:20120630T070000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION:Here is a description that spans multiple lines!\n\nThis should be on + a new line as well because the description contains newline characters. +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER:-P0DT0H30M0S +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H30M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/multiple_attachments.ics b/icalparser/tests/cal/multiple_attachments.ics new file mode 100755 index 0000000..0c25f82 --- /dev/null +++ b/icalparser/tests/cal/multiple_attachments.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:1334F9B7-6136-444E-A58D-472564C6AA73 +SUMMARY:sahaja <> frashed +DESCRIPTION:weekly 1on1 +ATTACH:http://globe-views.com/dcim/dreams/dog/dog-01.jpg +ATTACH:http://www.hdanimals.com/static/cache/e9/59/e959f33a6b440f36cad24beca8bf6d1c.jpg +ATTENDEE;CN=James Lal;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS + -ACTION;RSVP=TRUE:mailto:jlal@mozilla.com +ORGANIZER;CN=Faramarz Rashed:mailto:frashed@mozilla.com +DTSTART;TZID=America/Los_Angeles:20120326T110000 +DTEND;TZID=America/Los_Angeles:20120326T113000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120326T161522Z +DTSTAMP:20120730T165637Z +SEQUENCE:9 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR + diff --git a/icalparser/tests/cal/multiple_rrules.ics b/icalparser/tests/cal/multiple_rrules.ics new file mode 100755 index 0000000..339c318 --- /dev/null +++ b/icalparser/tests/cal/multiple_rrules.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:1334F9B7-6136-444E-A58D-472564C6AA73 +RRULE:FREQ=WEEKLY;UNTIL=20120730T065959Z +RRULE:FREQ=MONTHLY;BYDAY=SU;UNTIL=20120730T065959Z +SUMMARY:sahaja <> frashed +DESCRIPTION:weekly 1on1 +ATTENDEE;CN=James Lal;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS + -ACTION;RSVP=TRUE:mailto:jlal@mozilla.com +ORGANIZER;CN=Faramarz Rashed:mailto:frashed@mozilla.com +DTSTART;TZID=America/Los_Angeles:20120326T110000 +DTEND;TZID=America/Los_Angeles:20120326T113000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120326T161522Z +DTSTAMP:20120730T165637Z +SEQUENCE:9 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR + diff --git a/icalparser/tests/cal/parserv2.ics b/icalparser/tests/cal/parserv2.ics new file mode 100755 index 0000000..11c8301 --- /dev/null +++ b/icalparser/tests/cal/parserv2.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:44c10eaa-db0b-4223-8653-cf2b63f26326 +RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR +SUMMARY:Calendar +DESCRIPTION:desc +ATTENDEE;CN=XXX;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRU + E:mailto:foo@bar.com +ATTENDEE;CN=XXXX;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TR + UE:mailto:x@bar.com +ORGANIZER;CN=foobar:mailto:x@bar.com +DTSTART;TZID=America/Los_Angeles:20120911T103000 +DTEND;TZID=America/Los_Angeles:20120911T110000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120911T184851Z +DTSTAMP:20120911T184851Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/readme.md b/icalparser/tests/cal/readme.md new file mode 100644 index 0000000..4e3ec57 --- /dev/null +++ b/icalparser/tests/cal/readme.md @@ -0,0 +1,3 @@ +# iCal source + +- https://github.com/mozilla-comm/ical.js/tree/master/samples diff --git a/icalparser/tests/cal/recur_instances.ics b/icalparser/tests/cal/recur_instances.ics new file mode 100755 index 0000000..d26903f --- /dev/null +++ b/icalparser/tests/cal/recur_instances.ics @@ -0,0 +1,92 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +DESCRIPTION:IAM FOO +RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU +SUMMARY:Crazy Event Thingy! +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Sahaja + Lal;X-NUM-GUESTS=0:mailto:calmozilla1@gmail.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ja + mes@lightsofapollo.com;X-NUM-GUESTS=0:mailto:james@lightsofapollo.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ia + m.revelation@gmail.com;X-NUM-GUESTS=0:mailto:iam.revelation@gmail.com +LOCATION:PLACE +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T100000 +DTEND;TZID=America/Los_Angeles:20121002T103000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120912T171506Z +DTSTAMP:20120912T171506Z +SEQUENCE:0 +RDATE;TZID=America/Los_Angeles:20121105T100000 +RDATE;TZID=America/Los_Angeles:20121110T100000,20121130T100000 +EXDATE;TZID=America/Los_Angeles:20130402T100000 +EXDATE;TZID=America/Los_Angeles:20121204T100000 +EXDATE;TZID=America/Los_Angeles:20130205T100000 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +SUMMARY:Crazy Event Thingy! +DESCRIPTION:I HAZ CHANGED! +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T150000 +DTEND;TZID=America/Los_Angeles:20121002T153000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +RECURRENCE-ID;TZID=America/Los_Angeles:20121002T100000 +LAST-MODIFIED:20120912T171540Z +DTSTAMP:20120912T171540Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +SUMMARY:Crazy Event Thingy! +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121106T200000 +DTEND;TZID=America/Los_Angeles:20121106T203000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +RECURRENCE-ID:20121105T180000Z +LAST-MODIFIED:20120912T171820Z +DTSTAMP:20120912T171820Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/recur_instances_finite.ics b/icalparser/tests/cal/recur_instances_finite.ics new file mode 100755 index 0000000..e7dc481 --- /dev/null +++ b/icalparser/tests/cal/recur_instances_finite.ics @@ -0,0 +1,50 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +DESCRIPTION:IAM FOO +RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU;UNTIL=20121231T100000 +SUMMARY:Crazy Event Thingy! +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Sahaja + Lal;X-NUM-GUESTS=0:mailto:calmozilla1@gmail.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ja + mes@lightsofapollo.com;X-NUM-GUESTS=0:mailto:james@lightsofapollo.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ia + m.revelation@gmail.com;X-NUM-GUESTS=0:mailto:iam.revelation@gmail.com +LOCATION:PLACE +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T100000 +DTEND;TZID=America/Los_Angeles:20121002T103000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120912T171506Z +DTSTAMP:20120912T171506Z +SEQUENCE:0 +RDATE;TZID=America/Los_Angeles:20121110T100000 +RDATE;TZID=America/Los_Angeles:20121105T100000 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/recur_instances_with_modifications.ics b/icalparser/tests/cal/recur_instances_with_modifications.ics new file mode 100644 index 0000000..dcaef06 --- /dev/null +++ b/icalparser/tests/cal/recur_instances_with_modifications.ics @@ -0,0 +1,63 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:School Board +X-WR-TIMEZONE:America/Chicago +X-WR-CALDESC: +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:20160808T180000 +DTEND;TZID=America/Chicago:20160808T203000 +DTSTAMP:20160718T160226Z +UID:kpgr4590djmi89l3sv51gmalrc@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=School + Board;X-NUM-GUESTS=0:mailto:aliceisd.net_7hatmpq9uek3a1h6if0r0sn5bc@group. + calendar.google.com +RECURRENCE-ID;TZID=America/Chicago:20160808T183000 +CREATED:20150715T183809Z +DESCRIPTION: +LAST-MODIFIED:20160718T155538Z +LOCATION: +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Regular Board Meeting Modified +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=America/Chicago:20150810T183000 +DTEND;TZID=America/Chicago:20150810T210000 +RRULE:FREQ=MONTHLY;UNTIL=20180709T233000Z;BYDAY=2MO +DTSTAMP:20160718T160226Z +UID:kpgr4590djmi89l3sv51gmalrc@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=School + Board;X-NUM-GUESTS=0:mailto:aliceisd.net_7hatmpq9uek3a1h6if0r0sn5bc@group. + calendar.google.com +CREATED:20150715T183809Z +DESCRIPTION: +LAST-MODIFIED:20160718T155538Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Regular Board Meeting +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/recur_instances_with_modifications_and_interval.ics b/icalparser/tests/cal/recur_instances_with_modifications_and_interval.ics new file mode 100644 index 0000000..202393e --- /dev/null +++ b/icalparser/tests/cal/recur_instances_with_modifications_and_interval.ics @@ -0,0 +1,578 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:SB-1213 +X-WR-TIMEZONE:America/Denver +X-WR-CALDESC: +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:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170602 +DTEND;VALUE=DATE:20170603 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170601 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:4 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170531 +DTEND;VALUE=DATE:20170601 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170530 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:4 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170524 +DTEND;VALUE=DATE:20170525 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170524 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:5 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170216 +DTEND;VALUE=DATE:20170217 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170215 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170214 +DTEND;VALUE=DATE:20170215 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170213 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170210 +DTEND;VALUE=DATE:20170211 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170209 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170208 +DTEND;VALUE=DATE:20170209 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170207 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170206 +DTEND;VALUE=DATE:20170207 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170205 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170202 +DTEND;VALUE=DATE:20170203 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170201 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170131 +DTEND;VALUE=DATE:20170201 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170130 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170127 +DTEND;VALUE=DATE:20170128 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170126 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170125 +DTEND;VALUE=DATE:20170126 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170124 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170123 +DTEND;VALUE=DATE:20170124 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170122 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170119 +DTEND;VALUE=DATE:20170120 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170118 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170113 +DTEND;VALUE=DATE:20170114 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170112 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170111 +DTEND;VALUE=DATE:20170112 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170110 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170109 +DTEND;VALUE=DATE:20170110 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170108 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170105 +DTEND;VALUE=DATE:20170106 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170104 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170103 +DTEND;VALUE=DATE:20170104 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170102 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20161122 +DTEND;VALUE=DATE:20161123 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20161121 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20161118 +DTEND;VALUE=DATE:20161119 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20161117 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20161116 +DTEND;VALUE=DATE:20161117 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20161115 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20161114 +DTEND;VALUE=DATE:20161115 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20161113 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20161110 +DTEND;VALUE=DATE:20161111 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20161109 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160923 +DTEND;VALUE=DATE:20160924 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160922 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160921 +DTEND;VALUE=DATE:20160922 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160920 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160919 +DTEND;VALUE=DATE:20160920 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160916 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160915 +DTEND;VALUE=DATE:20160916 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160914 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160913 +DTEND;VALUE=DATE:20160914 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160912 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160909 +DTEND;VALUE=DATE:20160910 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160908 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160907 +DTEND;VALUE=DATE:20160908 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20160906 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20160817 +DTEND;VALUE=DATE:20160818 +EXDATE;VALUE=DATE:20160821 +EXDATE;VALUE=DATE:20160827 +EXDATE;VALUE=DATE:20160904 +EXDATE;VALUE=DATE:20160910 +EXDATE;VALUE=DATE:20160918 +EXDATE;VALUE=DATE:20160924 +EXDATE;VALUE=DATE:20160926 +EXDATE;VALUE=DATE:20161002 +EXDATE;VALUE=DATE:20161008 +EXDATE;VALUE=DATE:20161016 +EXDATE;VALUE=DATE:20161022 +EXDATE;VALUE=DATE:20161030 +EXDATE;VALUE=DATE:20161105 +EXDATE;VALUE=DATE:20161107 +EXDATE;VALUE=DATE:20161111 +EXDATE;VALUE=DATE:20161119 +EXDATE;VALUE=DATE:20161123 +EXDATE;VALUE=DATE:20161125 +EXDATE;VALUE=DATE:20161127 +EXDATE;VALUE=DATE:20161203 +EXDATE;VALUE=DATE:20161211 +EXDATE;VALUE=DATE:20161217 +EXDATE;VALUE=DATE:20161219 +EXDATE;VALUE=DATE:20161221 +EXDATE;VALUE=DATE:20161223 +EXDATE;VALUE=DATE:20161225 +EXDATE;VALUE=DATE:20161227 +EXDATE;VALUE=DATE:20161229 +EXDATE;VALUE=DATE:20161231 +EXDATE;VALUE=DATE:20170106 +EXDATE;VALUE=DATE:20170114 +EXDATE;VALUE=DATE:20170116 +EXDATE;VALUE=DATE:20170120 +EXDATE;VALUE=DATE:20170128 +EXDATE;VALUE=DATE:20170203 +EXDATE;VALUE=DATE:20170211 +EXDATE;VALUE=DATE:20170217 +EXDATE;VALUE=DATE:20170219 +EXDATE;VALUE=DATE:20170225 +EXDATE;VALUE=DATE:20170305 +EXDATE;VALUE=DATE:20170311 +EXDATE;VALUE=DATE:20170319 +EXDATE;VALUE=DATE:20170325 +EXDATE;VALUE=DATE:20170327 +EXDATE;VALUE=DATE:20170402 +EXDATE;VALUE=DATE:20170408 +EXDATE;VALUE=DATE:20170410 +EXDATE;VALUE=DATE:20170412 +EXDATE;VALUE=DATE:20170414 +EXDATE;VALUE=DATE:20170416 +EXDATE;VALUE=DATE:20170422 +EXDATE;VALUE=DATE:20170430 +EXDATE;VALUE=DATE:20170506 +EXDATE;VALUE=DATE:20170514 +EXDATE;VALUE=DATE:20170520 +EXDATE;VALUE=DATE:20170528 +RRULE:FREQ=DAILY;UNTIL=20170601;INTERVAL=2 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:B +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20170601 +DTEND;VALUE=DATE:20170602 +DTSTAMP:20160829T191852Z +UID:h9i3hjtisl53goaljdcdb681fc@google.com +RECURRENCE-ID;VALUE=DATE:20170526 +CREATED:20160829T134437Z +DESCRIPTION: +LAST-MODIFIED:20160829T134450Z +LOCATION: +SEQUENCE:6 +STATUS:CONFIRMED +SUMMARY:A +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/recur_instances_with_modifications_to_first_day.ics b/icalparser/tests/cal/recur_instances_with_modifications_to_first_day.ics new file mode 100755 index 0000000..7dd2939 --- /dev/null +++ b/icalparser/tests/cal/recur_instances_with_modifications_to_first_day.ics @@ -0,0 +1,57 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:G. C. Hawley Middle School +X-WR-TIMEZONE:America/New_York +X-WR-CALDESC:This is the website calendar for GC Hawley MS. +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:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/New_York:20160915T123000 +DTEND;TZID=America/New_York:20160915T160000 +RRULE:FREQ=MONTHLY;UNTIL=20161020T035959Z;BYDAY=3TH +DTSTAMP:20160913T221241Z +UID:d28i2lkvhp2qb2v7n6oe9poikc@google.com +CREATED:20160705T193430Z +DESCRIPTION:Professional Development for Staff +LAST-MODIFIED:20160825T003008Z +LOCATION: +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Early Release for Students +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=America/New_York:20160929T123000 +DTEND;TZID=America/New_York:20160929T160000 +DTSTAMP:20160913T221241Z +UID:d28i2lkvhp2qb2v7n6oe9poikc@google.com +RECURRENCE-ID;TZID=America/New_York:20160915T123000 +CREATED:20160705T193430Z +DESCRIPTION:Professional Development for Staff +LAST-MODIFIED:20160825T003008Z +LOCATION: +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:Early Release for Students +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/utc_negative_zero.ics b/icalparser/tests/cal/utc_negative_zero.ics new file mode 100755 index 0000000..e61ad9a --- /dev/null +++ b/icalparser/tests/cal/utc_negative_zero.ics @@ -0,0 +1,27 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:Etc/GMT +BEGIN:STANDARD +DTSTART:19710101T000000 +TZOFFSETTO:-0000 +TZOFFSETFROM:-0000 +TZNAME:GMT +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:d118e997-3683-4552-8fe8-57c641f1f179 +SUMMARY:And another +ORGANIZER;CN=Sahaja Lal:mailto:calmozilla1@yahoo.com +DTSTART;TZID=Etc/GMT:20120821T210000 +DTEND;TZID=Etc/GMT:20120821T213000 +STATUS:CONFIRMED +CLASS:PUBLIC +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +TRANSP:OPAQUE +X-MICROSOFT-DISALLOW-COUNTER:TRUE +DTSTAMP:20120817T032509Z +SEQUENCE:0 +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/weird_windows_timezones.ics b/icalparser/tests/cal/weird_windows_timezones.ics new file mode 100644 index 0000000..5d736a1 --- /dev/null +++ b/icalparser/tests/cal/weird_windows_timezones.ics @@ -0,0 +1,237 @@ +BEGIN:VCALENDAR +METHOD:PUBLISH +PRODID:Microsoft Exchange Server 2010 +VERSION:2.0 +X-WR-CALNAME:High School Counselors Office +BEGIN:VTIMEZONE +TZID:(UTC-06:00) Central Time (US & Canada) +BEGIN:STANDARD +DTSTART:16010101T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0600 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:-0600 +TZOFFSETTO:-0500 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:Eastern Time +BEGIN:STANDARD +DTSTART:16010101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:Greenwich Standard Time +BEGIN:STANDARD +DTSTART:16010101T000000 +TZOFFSETFROM:+0000 +TZOFFSETTO:+0000 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T000000 +TZOFFSETFROM:+0000 +TZOFFSETTO:+0000 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DESCRIPTION:All seniors MUST make an appointment in the counseling office\, + to visit with a counselor and go over their application for graduation.\n +UID:040000008200E00074C5B7101A82E008000000004A8A50D0FC09D201000000000000000 + 010000000D27D08FB73761145BC661D80CE521463 +SUMMARY:Senior Appointment Sign-up +DTSTART;VALUE=DATE:20160914 +DTEND;VALUE=DATE:20160915 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +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:Registration for the PSAT test begins in the counseling office. + The test is open to any junior or sophomore. The test is $15 and must be + paid at the time of registration. Test date is Wednesday\, October 19th.\n +UID:040000008200E00074C5B7101A82E008000000004B8D8376DB0AD201000000000000000 + 010000000CF196AE75B9C684C9E51C33A403CA455 +SUMMARY:PSAT Registration +DTSTART;VALUE=DATE:20160915 +DTEND;VALUE=DATE:20160916 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +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:Financial Aid night for all seniors and their parents at 7:00pm + in the high school auditorium.\n +UID:040000008200E00074C5B7101A82E00800000000A3D802C7DB0AD201000000000000000 + 0100000004690B516982D4841A6A5808F1BF285D2 +SUMMARY:Financial Aid NIght +DTSTART;TZID="(UTC-06:00) Central Time (US & Canada)":20160919T190000 +DTEND;TZID="(UTC-06:00) Central Time (US & Canada)":20160919T200000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION: +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:0 +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:This is the last day to register for the PSAT test in October! + Please see the counseling office if you would like to register.\n +UID:040000008200E00074C5B7101A82E00800000000DE3BE28BDB0AD201000000000000000 + 0100000008C413809A1751A4E82CEB8F1BB526C61 +SUMMARY:PSAT Test registration ends +DTSTART;VALUE=DATE:20160923 +DTEND;VALUE=DATE:20160924 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +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:College Application Day for all interested seniors! The Bank of + North Dakota is waiving the application fee for any student to apply to a + North Dakota college or university. Counselors and college staff will be + assisting students with their applications periods 1-3 in the high school + library!\n +UID:040000008200E00074C5B7101A82E0080000000019668644DC0AD201000000000000000 + 010000000DB1D0B39660D4045A6AC5C232EB0D76B +SUMMARY:College Application Day +DTSTART;TZID="(UTC-06:00) Central Time (US & Canada)":20161005T080000 +DTEND;TZID="(UTC-06:00) Central Time (US & Canada)":20161005T081500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION: +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:0 +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:PSAT Test in the high school auditorium for all students that r + egistered. Please report to the auditorium at 8:30am with a sharpened #2 p + encil and calculator.\n +UID:040000008200E00074C5B7101A82E00800000000E68A2E67DC0AD201000000000000000 + 01000000080E5BC118E41944ABC1C94B815AB2235 +SUMMARY:PSAT Test +DTSTART;VALUE=DATE:20161019 +DTEND;VALUE=DATE:20161020 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +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:North Dakota State Assessment for all juniors (periods 2-4).\n +UID:040000008200E00074C5B7101A82E008000000002EB6D123DD0AD201000000000000000 + 01000000061F7AF4CD0D2C84B9C6A916D2C633339 +SUMMARY:NDSA Science Assessment +DTSTART;TZID="(UTC-06:00) Central Time (US & Canada)":20161025T091500 +DTEND;TZID="(UTC-06:00) Central Time (US & Canada)":20161025T114500 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20160916T193959Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION: +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:0 +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +BEGIN:VEVENT +DESCRIPTION:\n +UID:040000008200E00074C5B7101A82E0080000000052D533E856D6D301000000000000000 + 010000000568B47E6EF679B4B812809E063BB64E1 +SUMMARY:Test Event +DTSTART;TZID=Eastern Time:20180419T103000 +DTEND;TZID=Eastern Time:20180419T113000 +CLASS:PUBLIC +PRIORITY:5 +DTSTAMP:20180417T144309Z +TRANSP:OPAQUE +STATUS:CONFIRMED +SEQUENCE:0 +LOCATION:Boardroom +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:0 +X-MICROSOFT-DONOTFORWARDMEETING:FALSE +X-MICROSOFT-DISALLOW-COUNTER:FALSE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/cal/wrong_dates.ics b/icalparser/tests/cal/wrong_dates.ics new file mode 100644 index 0000000..0468020 --- /dev/null +++ b/icalparser/tests/cal/wrong_dates.ics @@ -0,0 +1,37 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:2014929 +DTEND;VALUE=DATE:20140930 +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20140929 +DTEND;VALUE=DATE:2014930 +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/icalparser/tests/multiline_description.phpt b/icalparser/tests/multiline_description.phpt new file mode 100644 index 0000000..c4f5d23 --- /dev/null +++ b/icalparser/tests/multiline_description.phpt @@ -0,0 +1,18 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); +date_default_timezone_set('Europe/Prague'); + +// sort by date +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/multiline_description.ics'); +$events = $cal->getSortedEvents(); +Assert::same('30.6.2012 06:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); +Assert::same("Here is a description that spans multiple lines!\n\nThis should be on a new line as well because the description contains newline characters.", $events[0]['DESCRIPTION']); diff --git a/icalparser/tests/multiple_attachments.phpt b/icalparser/tests/multiple_attachments.phpt new file mode 100644 index 0000000..c689748 --- /dev/null +++ b/icalparser/tests/multiple_attachments.phpt @@ -0,0 +1,20 @@ + + */ + +use Tester\Assert; + +require_once __DIR__ . '/../vendor/autoload.php'; +\Tester\Environment::setup(); +date_default_timezone_set('Europe/Prague'); + +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/multiple_attachments.ics'); + +// Backwards compatibility, there is only ever one key displayed +Assert::type('string', $results['VEVENT'][0]['ATTACH']); + +// The new key 'ATTACHMENTS' is an array with 1 or more attachments +Assert::type('array', $results['VEVENT'][0]['ATTACHMENTS']); +Assert::count(2, $results['VEVENT'][0]['ATTACHMENTS']); diff --git a/icalparser/tests/recurring_events.phpt b/icalparser/tests/recurring_events.phpt new file mode 100644 index 0000000..ed73c3f --- /dev/null +++ b/icalparser/tests/recurring_events.phpt @@ -0,0 +1,170 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); + +$cal = new \om\IcalParser(); + + +$results = $cal->parseFile(__DIR__ . '/cal/recur_instances_finite.ics'); +$events = $cal->getSortedEvents(); + +// DTSTART;TZID=America/Los_Angeles:20121002T100000 +// DTEND;TZID=America/Los_Angeles:20121002T103000 +// RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU;UNTIL=20121231T100000 +// RDATE;TZID=America/Los_Angeles:20121110T100000 +// RDATE;TZID=America/Los_Angeles:20121105T100000 +Assert::equal(5, sizeof($events)); +Assert::equal('2.10.2012 10:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('5.11.2012 10:00:00', $events[1]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('6.11.2012 10:00:00', $events[2]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('10.11.2012 10:00:00', $events[3]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('4.12.2012 10:00:00', $events[4]['DTSTART']->format('j.n.Y H:i:s')); + +$results = $cal->parseFile(__DIR__ . '/cal/recur_instances.ics'); +$events = $cal->getSortedEvents(); + +$recurrences = []; +foreach($events as $i => $event) { + $recurrences[] = $event['DTSTART']; +} + +// DTSTART;TZID=America/Los_Angeles:20121002T100000 +// DTEND;TZID=America/Los_Angeles:20121002T103000 +// RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU +// RDATE;TZID=America/Los_Angeles:20121105T100000 +// RDATE;TZID=America/Los_Angeles:20121110T100000,20121130T100000 +// EXDATE;TZID=America/Los_Angeles:20130402T100000 +// EXDATE;TZID=America/Los_Angeles:20121204T100000 +// EXDATE;TZID=America/Los_Angeles:20130205T100000 +// total = 36 events - 3 exclusions + 3 additions +// because there is no "UNTIL", we only calculate the next 3 years of repeating events +Assert::equal(35, sizeof($recurrences)); +Assert::equal('02.10.2012 15:00:00', $recurrences[0]->format('d.m.Y H:i:s')); +Assert::equal('06.11.2012 20:00:00', $recurrences[1]->format('d.m.Y H:i:s')); +Assert::equal('10.11.2012 10:00:00', $recurrences[2]->format('d.m.Y H:i:s')); +Assert::equal('30.11.2012 10:00:00', $recurrences[3]->format('d.m.Y H:i:s')); +Assert::equal('01.01.2013 10:00:00', $recurrences[4]->format('d.m.Y H:i:s')); +Assert::equal('05.03.2013 10:00:00', $recurrences[5]->format('d.m.Y H:i:s')); +Assert::equal('07.05.2013 10:00:00', $recurrences[6]->format('d.m.Y H:i:s')); +Assert::equal('04.06.2013 10:00:00', $recurrences[7]->format('d.m.Y H:i:s')); +Assert::equal('02.07.2013 10:00:00', $recurrences[8]->format('d.m.Y H:i:s')); +Assert::equal('06.08.2013 10:00:00', $recurrences[9]->format('d.m.Y H:i:s')); +Assert::equal('03.09.2013 10:00:00', $recurrences[10]->format('d.m.Y H:i:s')); +Assert::equal('01.10.2013 10:00:00', $recurrences[11]->format('d.m.Y H:i:s')); +Assert::equal('05.11.2013 10:00:00', $recurrences[12]->format('d.m.Y H:i:s')); +Assert::equal('03.12.2013 10:00:00', $recurrences[13]->format('d.m.Y H:i:s')); +Assert::equal('07.01.2014 10:00:00', $recurrences[14]->format('d.m.Y H:i:s')); +Assert::equal('04.02.2014 10:00:00', $recurrences[15]->format('d.m.Y H:i:s')); +Assert::equal('04.03.2014 10:00:00', $recurrences[16]->format('d.m.Y H:i:s')); +Assert::equal('01.04.2014 10:00:00', $recurrences[17]->format('d.m.Y H:i:s')); +Assert::equal('06.05.2014 10:00:00', $recurrences[18]->format('d.m.Y H:i:s')); +Assert::equal('03.06.2014 10:00:00', $recurrences[19]->format('d.m.Y H:i:s')); +Assert::equal('01.07.2014 10:00:00', $recurrences[20]->format('d.m.Y H:i:s')); +Assert::equal('05.08.2014 10:00:00', $recurrences[21]->format('d.m.Y H:i:s')); +Assert::equal('02.09.2014 10:00:00', $recurrences[22]->format('d.m.Y H:i:s')); +Assert::equal('07.10.2014 10:00:00', $recurrences[23]->format('d.m.Y H:i:s')); +Assert::equal('04.11.2014 10:00:00', $recurrences[24]->format('d.m.Y H:i:s')); +Assert::equal('02.12.2014 10:00:00', $recurrences[25]->format('d.m.Y H:i:s')); +Assert::equal('06.01.2015 10:00:00', $recurrences[26]->format('d.m.Y H:i:s')); +Assert::equal('03.02.2015 10:00:00', $recurrences[27]->format('d.m.Y H:i:s')); +Assert::equal('03.03.2015 10:00:00', $recurrences[28]->format('d.m.Y H:i:s')); +Assert::equal('07.04.2015 10:00:00', $recurrences[29]->format('d.m.Y H:i:s')); +Assert::equal('05.05.2015 10:00:00', $recurrences[30]->format('d.m.Y H:i:s')); +Assert::equal('02.06.2015 10:00:00', $recurrences[31]->format('d.m.Y H:i:s')); +Assert::equal('07.07.2015 10:00:00', $recurrences[32]->format('d.m.Y H:i:s')); +Assert::equal('04.08.2015 10:00:00', $recurrences[33]->format('d.m.Y H:i:s')); +Assert::equal('01.09.2015 10:00:00', $recurrences[34]->format('d.m.Y H:i:s')); + +foreach ($events[0]['EXDATES'] as $exDate) { + Assert::notContains($exDate, $recurrences); +} + + +$results = $cal->parseFile(__DIR__ . '/cal/recur_instances_with_modifications.ics'); +$events = $cal->getSortedEvents(true); + +Assert::false(empty($events[0]['RECURRENCES'])); +// the 12th entry is the modified event, related to the remaining recurring events +Assert::true(empty($events[12]['RECURRENCES'])); + +$recurrences = $events[0]['RECURRENCES']; +$modifiedEvent = $events[12]; + +// There should be 35 total recurrences because the modified event should've removed 1 recurrence +Assert::equal(35, sizeof($recurrences)); +// There should be 36 total events because of the modified event + 35 recurrences +Assert::equal(36, sizeof($events)); +Assert::notContains($modifiedEvent['DTSTART'], $recurrences); + +$results = $cal->parseFile(__DIR__ . '/cal/recur_instances_with_modifications_and_interval.ics'); + +// Build the cache of RECURRENCE-IDs and EXDATES first, so that we can properly determine the interval +$eventCache = array(); +foreach($results['VEVENT'] as $event) { + $eventSequence = empty($event['SEQUENCE']) ? "0" : $event['SEQUENCE']; + $eventRecurrenceID = empty($event['RECURRENCE-ID']) ? "0" : $event['RECURRENCE-ID']; + + $eventCache[$event['UID']][$eventRecurrenceID][$eventSequence] = $event; +} +$trueEvents = array(); +foreach($results['VEVENT'] as $event) { + if(empty($event['RECURRENCES'])) { + $trueEvents[] = $event; + } else { + $eventUID = $event['UID']; + foreach($event['RECURRENCES'] as $recurrence) { + $eventRecurrenceID = $recurrence->format("Ymd"); + if(empty($eventCache[$eventUID][$eventRecurrenceID])) { + $trueEvents[$eventRecurrenceID] = array('DTSTART' => $recurrence); + } else { + krsort($eventCache[$eventUID][$eventRecurrenceID]); + $keys = array_keys($eventCache[$eventUID][$eventRecurrenceID]); + $trueEvents[$eventRecurrenceID] = $eventCache[$eventUID][$eventRecurrenceID][$keys[0]]; + } + } + } +} + +usort($trueEvents, function ($a, $b) { + return $a['DTSTART'] > $b['DTSTART']; +}); + +$events = $cal->getSortedEvents(true); +Assert::false(empty($events[0]['RECURRENCES'])); +Assert::equal(count($trueEvents), count($events)); +foreach($trueEvents as $index => $trueEvent) { + Assert::equal($trueEvent['DTSTART']->format("Ymd"), $events[$index]['DTSTART']->format("Ymd")); +} + + +// There is still an issue that needs to be resolved when modifications are made to the initial event that is the +// base of the recurrences. The below ICS file has a great edge case example: one event, no recurrences in the +// recurring ruleset, and a modification to the initial event. +$results = $cal->parseFile(__DIR__ . '/cal/recur_instances_with_modifications_to_first_day.ics'); +$events = $cal->getSortedEvents(); +Assert::true(empty($events[0]['RECURRENCES'])); // edited event +Assert::true(empty($events[1]['RECURRENCES'])); // recurring event base with no recurrences +Assert::equal(1, count($events)); + +$results = $cal->parseFile(__DIR__ . '/cal/daily_recur.ics'); +$events = $cal->getSortedEvents(); +$period = new DatePeriod(new DateTime('20120801T050000'), new DateInterval('P1D'), 365 * 3); +foreach($period as $i => $day) { + Assert::equal($day->format('j.n.Y H:i:s'), $events[$i]['DTSTART']->format('j.n.Y H:i:s')); +} + +$results = $cal->parseFile(__DIR__ . '/cal/daily_recur2.ics'); +$events = $cal->getSortedEvents(); + +Assert::equal(4, sizeof($events)); +Assert::equal('21.8.2017 00:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('28.8.2017 00:00:00', $events[1]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('4.9.2017 00:00:00', $events[2]['DTSTART']->format('j.n.Y H:i:s')); +Assert::equal('11.9.2017 00:00:00', $events[3]['DTSTART']->format('j.n.Y H:i:s')); \ No newline at end of file diff --git a/icalparser/tests/sort_events.phpt b/icalparser/tests/sort_events.phpt new file mode 100644 index 0000000..d3cfbcf --- /dev/null +++ b/icalparser/tests/sort_events.phpt @@ -0,0 +1,29 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); +date_default_timezone_set('Europe/Prague'); + +// sort by date +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/basic.ics'); +$events = $cal->getSortedEvents(); +Assert::same('1.1.2013 00:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); + +// reverse sort (parseFile) +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/basic.ics'); +$events = $cal->getReverseSortedEvents(); +Assert::same('26.12.2015 00:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); + +// reverse sort (parseString) +$cal = new \om\IcalParser(); +$results = $cal->parseString(file_get_contents(__DIR__ . '/cal/basic.ics')); +$events = $cal->getReverseSortedEvents(); +Assert::same('26.12.2015 00:00:00', $events[0]['DTSTART']->format('j.n.Y H:i:s')); diff --git a/icalparser/tests/timezone.phpt b/icalparser/tests/timezone.phpt new file mode 100644 index 0000000..d632622 --- /dev/null +++ b/icalparser/tests/timezone.phpt @@ -0,0 +1,28 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); +date_default_timezone_set('Europe/Paris'); + +//some clean timezone +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/blank_description.ics'); +Assert::same('America/Los_Angeles', $cal->timezone->getName()); + +$results = $cal->parseFile(__DIR__ . '/cal/utc_negative_zero.ics'); +Assert::same('Etc/GMT', $cal->timezone->getName()); + +//time zone with custom prefixes (Mozilla files tken from here: https://www.mozilla.org/en-US/projects/calendar/holidays/) +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/FrenchHolidays.ics'); +Assert::same('Europe/Paris', $cal->timezone->getName()); + +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/weird_windows_timezones.ics'); +$events = $cal->getSortedEvents(); diff --git a/icalparser/tests/wrong_dates.phpt b/icalparser/tests/wrong_dates.phpt new file mode 100644 index 0000000..95ed7be --- /dev/null +++ b/icalparser/tests/wrong_dates.phpt @@ -0,0 +1,19 @@ + + */ + +use Tester\Assert; +use Tester\Environment; + +require_once __DIR__ . '/../vendor/autoload.php'; +Environment::setup(); + +$cal = new \om\IcalParser(); +$results = $cal->parseFile(__DIR__ . '/cal/wrong_dates.ics'); +$events = $cal->getSortedEvents(); +Assert::same('29.9.2014 00:00:00', $events[1]['DTSTART']->format('j.n.Y H:i:s')); +Assert::same(null, $events[1]['DTEND']); + +Assert::same(null, $events[0]['DTSTART']); +Assert::same('30.9.2014 00:00:00', $events[0]['DTEND']->format('j.n.Y H:i:s')); diff --git a/icalparser/tools/windowstimezones.php b/icalparser/tools/windowstimezones.php new file mode 100644 index 0000000..46a15d0 --- /dev/null +++ b/icalparser/tools/windowstimezones.php @@ -0,0 +1,19 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +$windows_timezones = array(); +$windowstimezonexml = new DOMDocument(); +$windowstimezonexml->load('http://unicode.org/cldr/data/common/supplemental/windowsZones.xml'); +$zones = $windowstimezonexml->getElementsByTagName('mapZone'); +foreach($zones as $zone) { + if($zone->getAttribute('territory') == '001') { + $windows_timezones[$zone->getAttribute('other')] = $zone->getAttribute('type'); + } +} + +file_put_contents(__DIR__.'/windows_timezones.php', "