diff --git a/favicon.ico b/public/favicon.ico
similarity index 100%
rename from favicon.ico
rename to public/favicon.ico
diff --git a/images/background.jpg b/public/images/background.jpg
similarity index 100%
rename from images/background.jpg
rename to public/images/background.jpg
diff --git a/images/arrow-up.svg b/public/images/icons/arrow-up.svg
similarity index 100%
rename from images/arrow-up.svg
rename to public/images/icons/arrow-up.svg
diff --git a/public/images/icons/bars.svg b/public/images/icons/bars.svg
new file mode 100644
index 0000000..c144b77
--- /dev/null
+++ b/public/images/icons/bars.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/images/user-times.svg b/public/images/icons/user-times.svg
similarity index 100%
rename from images/user-times.svg
rename to public/images/icons/user-times.svg
diff --git a/public/images/icons/users.svg b/public/images/icons/users.svg
new file mode 100644
index 0000000..977f529
--- /dev/null
+++ b/public/images/icons/users.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/images/lageplan.svg b/public/images/lageplan.svg
similarity index 100%
rename from images/lageplan.svg
rename to public/images/lageplan.svg
diff --git a/index.html b/public/index.html
similarity index 98%
rename from index.html
rename to public/index.html
index 0ed1843..50f77c4 100644
--- a/index.html
+++ b/public/index.html
@@ -38,7 +38,9 @@
+END;
+ }
+}
+
+function getKrautButton(bool $status)
+{
+ if ($status) {
+ echo <<
+
+Raum ist geöffnet
+
+END;
+
+ } else {
+ echo <<
+
+Raum ist geschlossen
+
+END;
+ }
+}
diff --git a/src/getEvents.php b/src/getEvents.php
new file mode 100644
index 0000000..35c91bd
--- /dev/null
+++ b/src/getEvents.php
@@ -0,0 +1,487 @@
+data['DTSTART'];
+ /** @var string[] $eventDTStartValues */
+ $eventDTStartValues = $eventDTStart->value;
+ $event_start = $eventDTStartValues[0] ?? '';
+ $unix_start = $helper->fromiCaltoUnixDateTime($event_start);
+ $rrule = $event->data['RRULE']->value[0];
+
+ //todo rrule always set
+ if (EventIsPast($unix_start) === true AND isset ($rrule))
+ {
+ $new_unix = calculateNextStart($unix_start, $rrule);
+ $date = date('Ymd', $new_unix);
+ $time = date('His', $new_unix);
+ $connector = 'T';
+ $new_start = "{$date}{$connector}{$time}";
+ $event->data['DTSTART']->value[0] = $new_start;
+ }
+ }
+
+ /**
+ * Eventliste nach Startdatum sortieren
+ */
+ usort($events, 'compareEventStart');
+
+ /**
+ * Termine ausgeben
+ */
+ foreach ($events as $event)
+ {
+ printListItem($event);
+ }
+ return true;
+}
+
+function printListItem(ZCiCalNode $event): bool
+/**
+ * Die Ausgabefunktion für die Ausgabe der Termine als Liste. Hier wird die
+ * Zeitangabe des iCal-Formates in Datum, Uhrzeit und Wochentag umgewandelt.
+ * Anschließend erfolgt die Ausgabe der einzelnen Teile.
+ */
+{
+ date_default_timezone_set("UTC");
+ $helper = new ZDateHelper();
+ $hyph = " - ";
+ $uhr = " Uhr";
+ $komma = ", ";
+
+ $event_start = $event->data['DTSTART']->value[0];
+ $event_url = lowerURL($event->data['URL']->parameter['value']);
+ $event_title = $event->data['SUMMARY']->value[0];
+ $event_location = trim($event->data['LOCATION']->value[0], '"');
+ $event_descr = trim($event->data['DESCRIPTION']->value[0], '"');
+
+ $unix = $helper->fromiCaltoUnixDateTime($event_start);
+ $event_date = date('d.m.Y', $unix);
+ $event_time = date('H:i', $unix);
+ $event_day = toGerman(date('l', $unix));
+ $dateline = "{$event_day}{$komma}{$event_date}{$hyph}{$event_time}{$uhr}";
+
+ displayHeadline($dateline, $event_title);
+ displayLocation($event_location);
+ displayDescription($event_descr);
+ displayURL($event_url);
+ return true;
+}
+
+function displayHeadline(string $dateline, $event_title): bool
+{
+ echo "\n\n";
+ echo "" . $dateline . ": " . $event_title . "
\n";
+ return true;
+}
+
+function displayLocation(string $location): bool
+{
+ echo "\n";
+ echo "- $location
\n";
+ return true;
+}
+
+function displayDescription(string $description): bool
+{
+ echo "- $description
\n";
+ return true;
+}
+
+function displayURL(string $url): bool
+{
+ $stripped = trim($url, '"');
+ echo "- " . $stripped . "
\n";
+ echo "
\n";
+ echo "\n";
+ return true;
+}
+
+/**
+ * Die Startfuktion für die Ausgabe der Termine als Tabelle in termine.php
+ */
+
+function printEventTable(): bool
+{
+ $events = initEvents();
+ printTableHead();
+ foreach ($events as $event)
+ {
+ $event_array = getEventArray($event);
+ printTableItem($event_array);
+ }
+ echo "\t\n";
+ echo "\n";
+ return true;
+}
+
+function printTableHead()
+{
+ echo "\n\r\n";
+ echo "\t\n";
+ echo "\t\n";
+ echo "\t\tDatum | \n";
+ echo "\t\tWochentag | \n";
+ echo "\t\tZeit | \n";
+ echo "\t\tOrt | \n";
+ echo "\t\tTitel | \n";
+ echo "\t\tBeschreibung | \n";
+ echo "\t
\n";
+ echo "\t\n";
+ echo "\t\n";
+}
+
+function printTableItem($event_array): bool
+{
+ date_default_timezone_set("UTC");
+ $time = $event_array['DTSTART'];
+ $helper = new ZDateHelper();
+ $unix = $helper->fromiCaltoUnixDateTime($time);
+ $event_date = date('d.m.Y', $unix);
+ $event_time = date('H:i', $unix);
+ $event_day = toGerman(date('l', $unix));
+ $event_uid = $event_array['UID'];
+ $event_url = lowerURL($event_array['URL']);
+ $event_title = trim($event_array['SUMMARY'], '"');
+ $event_descr = trim($event_array['DESCRIPTION'], '"');
+ $event_location = trim($event_array['LOCATION'], '"');
+ echo "\t\n";
+ echo "\t\t" . $event_date . " | \n";
+ echo "\t\t" . $event_day . " | \n";
+ echo "\t\t" . $event_time . " Uhr | \n";
+ echo "\t\t" . $event_location . " | \n";
+ echo "\t\t" . $event_title . " | \n";
+ echo "\t\t" . $event_descr;
+ printURL($event_url);
+ echo "\t |
\n";
+ return true;
+}
+
+function printURL(string $url): bool
+{
+ $stripped = trim($url, '"');
+ if ($url != '')
+ {
+ echo "" . $stripped . "\n";
+ }
+ else
+ {
+ echo "\n";
+ }
+ return true;
+}
+
+/**
+ * Funktionen, die von beiden Ausgaben benutzt werden.
+ */
+
+/**
+ * @return ZCiCalNode[]|null
+ */
+function initEvents(): ?array
+/**
+ * Allgemeingültige Funktion zur Initialisierung. Enthält die Schritte, die
+ * von beiden Ausgaben gleichermaßen gebraucht werden.
+ * - Erstellen des iCalendar Objekts (initCalendar).
+ * - Schaut, ob der Kalender überhaupt Events enthält (printEventCount).
+ * - Sammel alle Events in einer Liste (grabEvents).
+ * Gibt zweidimmensionales assoziatives Array oder Null zurück.
+ */
+{
+ $iCalObj = initCalendar();
+ if (!isset ($iCalObj))
+ {
+ printError("Fehler beim Initialisieren des Kalenders");
+ return null;
+ }
+ $count = printEventCount($iCalObj);
+ if ($count == 0 or $count == false)
+ {
+ return null;
+ }
+ $events = grabEvents($iCalObj);
+ return $events;
+}
+
+function initCalendar(): ?ZCiCal
+/**
+ * Erstellt das Kalenderobjekt vom Typ ZCiCal.
+ * Gibt das Kalenderobjekt oder Null zurück.
+ */
+{
+ $iCalFile = '../public/krautspace.ics';
+ $iCalString = file_get_contents($iCalFile);
+ if ($iCalString == false)
+ {
+ printError("Kann Kalenderdatei nicht lesen");
+ return null;
+ }
+ $iCalObj = new ZCiCal($iCalString);
+ return $iCalObj;
+}
+
+function printEventCount(ZCiCal $iCalObj): ?int
+/**
+ * Gibt die Anzahl der Events zurück, die das übergebene
+ * Kalenderobjekt enthält. Im Fehlerfall wird Null zurück
+ * gegeben.
+ */
+{
+ $eventCount = $iCalObj->countEvents();
+ if (!isset ($eventCount))
+ {
+ printError("Fehler beim Parsen des Kalenders");
+ return null;
+ }
+ // echo "$eventCount anstehende Events
";
+ return $eventCount;
+}
+
+function grabEvents(ZCiCal $iCalObj): ?array
+{
+/**
+ * Läuft durch das iCalendar objekt und sammelt alle Nodes vom Typ
+ * 'VEVENT' ein. Gibt ein Array mit Objekten vom Typ 'ZCiCalNode' zurück.
+ * Im Fehlerfall wird Null zurück gegeben.
+ */
+ $events = [];
+ if (isset ($iCalObj->tree->child))
+ {
+ foreach ($iCalObj->tree->child as $node)
+ {
+ if ($node->getName() == "VEVENT")
+ {
+ $events[] = $node;
+ }
+ }
+ }
+ else
+ {
+ printError("Cant find nodes");
+ return null;
+ }
+ return $events;
+}
+
+function getEventArray(ZCiCalNode $node): array
+{
+/**
+ * Bekommt eine Event Node vom Typ 'ZiCalNode' übergeben und extrahiert
+ * daraus die gewünschten Elemente. Bildet daraus ein zweidimmensionales
+ * assoziatives Array. Gibt dieses Array zurück.
+ * Wird derzeit nur von der tabellarischen Ausgabe referenziert, welche
+ + momentan nicht genutzt wird.
+ */
+ /**
+ * @var ZCiCalNode $node
+ * @var ZCiCalDataNode $event
+ */
+ $event = $node->data;
+ $event_array = [];
+ $keys = array('DTSTART', 'SUMMARY', 'DESCRIPTION', 'URL', 'LOCATION');
+
+ foreach ($keys as $key)
+ {
+ $event_array[$key] = $event[$key]->value[0];
+ if ($key === 'DTSTART')
+ {
+ if (isset ($event[$key]->parameter['tzid']))
+ {
+ $event_array['TZ'] = $event[$key]->parameter['tzid'];
+ }
+ }
+ else if ($key === 'URL')
+ {
+ $event_array[$key] = $event[$key]->parameter['value'];
+ }
+ }
+ return $event_array;
+}
+
+function printError($errMsg)
+{
+ echo "\n\r$errMsg
\n\r";
+ return true;
+}
+
+function toGerman(string $day): string
+{
+ switch ($day)
+ {
+ case 'Monday':
+ return 'Montag';
+ case 'Tuesday':
+ return 'Dienstag';
+ case 'Wednesday':
+ return 'Mittwoch';
+ case 'Thursday':
+ return 'Donnerstag';
+ case 'Friday':
+ return 'Freitag';
+ case 'Saturday':
+ return 'Samstag';
+ case 'Sunday':
+ return 'Sonntag';
+ default:
+ return '?';
+ }
+}
+
+function lowerURL(string $url): string
+{
+ $old = array('HTTPS', 'HTTP', 'FTP', 'WWW', 'SSH');
+ $new = array('https', 'http', 'ftp', 'www', 'ssh');
+ $new_url = str_replace($old, $new, $url);
+ return $new_url;
+}
+
+function calculateNextStart(int $unix_start, string $rrule): int
+/**
+ * Berechnet für wiederkehrende Termine den aktuell nächsten Termin.
+ * dabei werden zur Zeit nur der der Zeitraum zwischen zwei Terminen
+ * und eine mögliche Anzahl der Termine berücksichtigt. Gibt den neuen
+ * Termin als Unix-Zeitstempel zurück.
+ */
+{
+ $counter = 0;
+
+ $rule_array = getRuleArray($rrule);
+ if (isset ($rule_array['COUNT']))
+ {
+ $count = $rule_array['COUNT'];
+ }
+ if (isset ($rule_array['FREQ']))
+ {
+ $frequency = $rule_array['FREQ'];
+ }
+ if (isset ($rule_array['INTERVAL']))
+ {
+ $interval = $rule_array['INTERVAL'];
+ }
+ if (isset ($rule_array['UNTIL']))
+ {
+ //todo implement
+ $until_string = $rule_array['UNTIL'];
+ }
+
+ $freq_offset = getOffset($frequency);
+ if (isset ($interval))
+ {
+ $offset = $freq_offset * $interval;
+ }
+ else
+ {
+ $offset = $freq_offset;
+ }
+ while ($unix_start <= time())
+ {
+ if (isset ($count))
+ {
+ if ($counter >= $count)
+ {
+ break;
+ }
+ }
+ $unix_start = $unix_start + $offset;
+ $counter = $counter + 1;
+ }
+ return $unix_start;
+}
+
+function getOffset(string $frequence): int
+{
+ switch ($frequence)
+ {
+ case 'HOURLY':
+ return 3600;
+ case 'DAILY':
+ return 86400;
+ case 'WEEKLY':
+ return 604800;
+ default:
+ return 1;
+ }
+}
+
+function getRuleArray(string $rrule): array
+/**
+ * Zerlegt den String einer RRULE und gibt die einzelnen Elemente als
+ * assoziatives Array zurück.
+ */
+{
+ $rule_array = [];
+ $rule_strings = explode(';', $rrule);
+ foreach ($rule_strings as $r_string)
+ {
+ $rule = explode('=', $r_string);
+ $rule_array[$rule[0]] = $rule[1];
+ }
+ return $rule_array;
+}
+
+function EventIsPast(int $unix_start): bool
+/**
+ * Prüft, ob die übergebenen Unixzeit älter als der aktuelle Tag ist. Gibt
+ * Wahr oder Falsch zurück.
+ */
+{
+ $event_date = date('d.m.Y', $unix_start);
+ $day_end = strtotime($event_date) + 86400;
+ $actual_date = time();
+
+ if ($day_end < $actual_date)
+ {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Bekommt zwei Eventnodes übergeben und vergleicht deren Startdatum. Die
+ * Funktion wird intern von ausort() benutzt, um das Array mit den Eventnodes
+ * nach Datum zu sortieren.
+ *
+ * @param ZCiCalNode $event_a
+ * @param ZCiCalNode $event_b
+ * @return int
+ */
+function compareEventStart(ZCiCalNode $event_a, ZCiCalNode $event_b): int
+{
+ $a = $event_a->data['DTSTART']->value[0];
+ $b = $event_b->data['DTSTART']->value[0];
+ return $a <=> $b;
+}
diff --git a/src/lib/README.md b/src/lib/README.md
new file mode 100644
index 0000000..11e64f6
--- /dev/null
+++ b/src/lib/README.md
@@ -0,0 +1,127 @@
+# Zap Calendar iCalendar Library
+
+(https://github.com/zcontent/icalendar)
+
+The Zap Calendar iCalendar Library is a PHP library for supporting the iCalendar (RFC 5545) standard.
+
+This PHP library is for reading and writing iCalendar formatted feeds and
+files. Features of the library include:
+
+- Read AND write support for iCalendar files
+- Object based creation and manipulation of iCalendar files
+- Supports expansion of RRULE to a list of repeating dates
+- Supports adding timezone info to iCalendar file
+
+All iCalendar data is stored in a PHP object tree.
+This allows any property to be added to the iCalendar feed without
+requiring specialized library function calls.
+With power comes responsibility. Missing or invalid properties can cause
+the resulting iCalendar file to be invalid. Visit [iCalendar.org](http://icalendar.org) to view valid
+properties and test your feed using the site's [iCalendar validator tool](http://icalendar.org/validator.html).
+
+Library API documentation can be found at http://icalendar.org/zapcallibdocs
+
+See the examples folder for programs that read and write iCalendar
+files. At its simpliest, you need to include the library at the top of your program:
+
+```php
+require_once($path_to_library . "/zapcallib.php");
+```
+
+Create an ical object using the ZCiCal object:
+
+```php
+$icalobj = new ZCiCal();
+```
+
+Add an event object:
+
+```php
+$eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode);
+```
+
+Add a start and end date to the event:
+
+```php
+// add start date
+$eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime("2020-01-01 12:00:00")));
+
+// add end date
+$eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime("2020-01-01 13:00:00")));
+```
+
+Write the object in iCalendar format using the export() function call:
+
+```php
+echo $icalobj->export();
+```
+
+This example will not validate since it is missing some required elements.
+Look at the simpleevent.php example for the minimum # of elements
+needed for a validated iCalendar file.
+
+To create a multi-event iCalendar file, simply create multiple event objects. For example:
+
+```php
+$icalobj = new ZCiCal();
+$eventobj1 = new ZCiCalNode("VEVENT", $icalobj->curnode);
+$eventobj1->addNode(new ZCiCalDataNode("SUMMARY:Event 1"));
+...
+$eventobj2 = new ZCiCalNode("VEVENT", $icalobj->curnode);
+$eventobj2->addNode(new ZCiCalDataNode("SUMMARY:Event 2"));
+...
+```
+
+To read an existing iCalendar file/feed, create the ZCiCal object with a string representing the contents of the iCalendar file:
+
+```php
+$icalobj = new ZCiCal($icalstring);
+```
+
+Large iCalendar files can be read in chunks to reduce the amount of memory needed to hold the iCalendar feed in memory. This example reads 500 events at a time:
+
+```php
+$icalobj = null;
+$eventcount = 0;
+$maxevents = 500;
+do
+{
+ $icalobj = newZCiCal($icalstring, $maxevents, $eventcount);
+ ...
+ $eventcount +=$maxevents;
+}
+while($icalobj->countEvents() >= $eventcount);
+```
+
+You can read the events from an imported (or created) iCalendar object in this manner:
+
+```php
+foreach($icalobj->tree->child as $node)
+{
+ if($node->getName() == "VEVENT")
+ {
+ foreach($node->data as $key => $value)
+ {
+ if($key == "SUMMARY")
+ {
+ echo "event title: " . $value->getValues() . "\n";
+ }
+ }
+ }
+}
+```
+
+## Known Limitations
+
+- Since the library utilizes objects to read and write iCalendar data, the
+size of the iCalendar data is limited to the amount of available memory on the machine.
+The ZCiCal() object supports reading a range of events to minimize memory
+space.
+- The library ignores timezone info when importing files, instead utilizing PHP's timezone
+library for calculations (timezones are supported when exporting files).
+Imported timezones need to be aliased to a [PHP supported timezone](http://php.net/manual/en/timezones.php).
+- At this time, the library does not support the "BYSETPOS" option in RRULE items.
+- At this time, the maximum date supported is 2036 to avoid date math issues
+with 32 bit systems.
+- Repeating events are limited to a maximum of 5,000 dates to avoid memory or infinite loop issues
+
diff --git a/src/lib/includes/date.php b/src/lib/includes/date.php
new file mode 100644
index 0000000..4f9e7e6
--- /dev/null
+++ b/src/lib/includes/date.php
@@ -0,0 +1,568 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+// No direct access
+defined('_ZAPCAL') or die( 'Restricted access' );
+
+/**
+ * Zap Calendar Date Helper Class
+ *
+ * Helper class for various date functions
+ */
+class ZDateHelper {
+
+ /**
+ * Find the number of days in a month
+ *
+ * @param int $month Month is between 1 and 12 inclusive
+ *
+ * @param int $year is between 1 and 32767 inclusive
+ *
+ * @return int
+ */
+ static function DayInMonth($month, $year) {
+ $daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+ if ($month != 2) return $daysInMonth[$month - 1];
+ return (checkdate($month, 29, $year)) ? 29 : 28;
+ }
+
+ /**
+ * Is given date today?
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isToday($date, $tzid = "UTC") {
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now);
+ }
+
+ /**
+ * Is given date before today?
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isBeforeToday($date, $tzid = "UTC"){
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) >
+ mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now));
+ }
+
+ /**
+ * Is given date after today?
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isAfterToday($date, $tzid = "UTC"){
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) <
+ mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now));
+ }
+
+ /**
+ * Is given date tomorrow?
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isTomorrow($date, $tzid = "UTC") {
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now + 60 * 60 * 24);
+ }
+
+ /**
+ * Is given date in the future?
+ *
+ * This routine differs from isAfterToday() in that isFuture() will
+ * return true for date-time values later in the same day.
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isFuture($date, $tzid = "UTC"){
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return $date > $now;
+ }
+
+ /**
+ * Is given date in the past?
+ *
+ * This routine differs from isBeforeToday() in that isPast() will
+ * return true for date-time values earlier in the same day.
+ *
+ * @param int $date date in Unix timestamp format
+ *
+ * @param int $tzid PHP recognized timezone (default is UTC)
+ *
+ * @return bool
+ */
+ static function isPast($date, $tzid = "UTC") {
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return $date < $now;
+ }
+
+ /**
+ * Return current Unix timestamp in local timezone
+ *
+ * @param string $tzid PHP recognized timezone
+ *
+ * @return int
+ */
+ static function now($tzid = "UTC"){
+ $dtz = new DateTimeZone($tzid);
+ $dt = new DateTime("now", $dtz);
+ $now = time() + $dtz->getOffset($dt);
+ return $now;
+ }
+
+ /**
+ * Is given date fall on a weekend?
+ *
+ * @param int $date Unix timestamp
+ *
+ * @return bool
+ */
+ static function isWeekend($date) {
+ $dow = gmdate('w',$date);
+ return $dow == 0 || $dow == 6;
+ }
+
+ /**
+ * Format Unix timestamp to SQL date-time
+ *
+ * @param int $t Unix timestamp
+ *
+ * @return string
+ */
+ static function toSqlDateTime($t = 0)
+ {
+ date_default_timezone_set('GMT');
+ if($t == 0)
+ return gmdate('Y-m-d H:i:s',self::now());
+ return gmdate('Y-m-d H:i:s', $t);
+ }
+
+ /**
+ * Format Unix timestamp to SQL date
+ *
+ * @param int $t Unix timestamp
+ *
+ * @return string
+ */
+ static function toSqlDate($t = 0)
+ {
+ date_default_timezone_set('GMT');
+ if($t == 0)
+ return gmdate('Y-m-d',self::now());
+ return gmdate('Y-m-d', $t);
+ }
+
+ /**
+ * Format iCal date-time string to Unix timestamp
+ *
+ * @param string $datetime in iCal time format ( YYYYMMDD or YYYYMMDDTHHMMSS or YYYYMMDDTHHMMSSZ )
+ *
+ * @return int Unix timestamp
+ */
+ static function fromiCaltoUnixDateTime($datetime) {
+ // first check format
+ $formats = array();
+ $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/";
+ $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]/";
+ $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z/";
+ $ok = false;
+ foreach($formats as $format){
+ if(preg_match($format,$datetime)){
+ $ok = true;
+ break;
+ }
+ }
+ if(!$ok)
+ return null;
+ $year = substr($datetime,0,4);
+ $month = substr($datetime,4,2);
+ $day = substr($datetime,6,2);
+ $hour = 0;
+ $minute = 0;
+ $second = 0;
+ if(strlen($datetime) > 8 && $datetime[8] == "T") {
+ $hour = substr($datetime,9,2);
+ $minute = substr($datetime,11,2);
+ $second = substr($datetime,13,2);
+ }
+ return gmmktime($hour, $minute, $second, $month, $day, $year);
+ }
+
+ /**
+ * Format Unix timestamp to iCal date-time string
+ *
+ * @param int $datetime Unix timestamp
+ *
+ * @return string
+ */
+ static function fromUnixDateTimetoiCal($datetime){
+ date_default_timezone_set('GMT');
+ return gmdate("Ymd\THis",$datetime);
+ }
+
+ /**
+ * Convert iCal duration string to # of seconds
+ *
+ * @param string $duration iCal duration string
+ *
+ * return int
+ */
+ static function iCalDurationtoSeconds($duration) {
+ $secs = 0;
+ if($duration[0] == "P") {
+ $duration = str_replace(array("H","M","S","T","D","W","P"),array("H,","M,","S,","","D,","W,",""),$duration);
+ $dur2 = explode(",",$duration);
+ foreach($dur2 as $dur){
+ $val=intval($dur);
+ if(strlen($dur) > 0){
+ switch($dur{strlen($dur) - 1}) {
+ case "H":
+ $secs += 60*60 * $val;
+ break;
+ case "M":
+ $secs += 60 * $val;
+ break;
+ case "S":
+ $secs += $val;
+ break;
+ case "D":
+ $secs += 60*60*24 * $val;
+ break;
+ case "W":
+ $secs += 60*60*24*7 * $val;
+ break;
+ }
+ }
+ }
+ }
+ return $secs;
+ }
+
+ /**
+ * Check if day falls within date range
+ *
+ * @param int $daystart start of day in Unix timestamp format
+ *
+ * @param int $begin Unix timestamp of starting date range
+ *
+ * @param int $end Unix timestamp of end date range
+ *
+ * @return bool
+ */
+ static function inDay($daystart, $begin, $end)
+ {
+ //$dayend = $daystart + 60*60*24 - 60;
+ // add 1 day to determine end of day
+ // don't use 24 hours, since twice a year DST Sundays are 23 hours and 25 hours in length
+ // adding 1 day takes this into account
+ $dayend = self::addDate($daystart, 0,0,0,0,1,0);
+
+ $end = max($begin, $end); // $end can't be less than $begin
+ $inday =
+ ($daystart <= $begin && $begin < $dayend)
+ ||($daystart < $end && $end < $dayend)
+ ||($begin <= $daystart && $end > $dayend)
+ ;
+ return $inday;
+ }
+
+ /**
+ * Convert SQL date or date-time to Unix timestamp
+ *
+ * @param string $datetime SQL date or date-time
+ *
+ * @return int Unix date-time timestamp
+ */
+ static function toUnixDate($datetime)
+ {
+ $year = substr($datetime,0,4);
+ $month = substr($datetime,5,2);
+ $day = substr($datetime,8,2);
+
+ return mktime(0, 0, 0, $month, $day, $year);
+ }
+
+ /**
+ * Convert SQL date or date-time to Unix date timestamp
+ *
+ * @param string $datetime SQL date or date-time
+ *
+ * @return int Unix timestamp
+ */
+ static function toUnixDateTime($datetime)
+ {
+ // convert to absolute dates if neccessary
+ $datetime = self::getAbsDate($datetime);
+ $year = substr($datetime,0,4);
+ $month = substr($datetime,5,2);
+ $day = substr($datetime,8,2);
+ $hour = 0;
+ $minute = 0;
+ $second = 0;
+ if(strlen($datetime) > 10) {
+ $hour = substr($datetime,11,2);
+ $minute = substr($datetime,14,2);
+ $second = substr($datetime,17,2);
+ }
+ return gmmktime($hour, $minute, $second, $month, $day, $year);
+ }
+
+ /**
+ * Date math: add or substract from current date to get a new date
+ *
+ * @param int $date date to add or subtract from
+ *
+ * @param int $hour add or subtract hours from date
+ *
+ * @param int $min add or subtract minutes from date
+ *
+ * @param int $sec add or subtract seconds from date
+ *
+ * @param int $month add or subtract months from date
+ *
+ * @param int $day add or subtract days from date
+ *
+ * @param int $year add or subtract years from date
+ *
+ * @param string $tzid PHP recognized timezone (default is UTC)
+ */
+ static function addDate($date, $hour, $min, $sec, $month, $day, $year, $tzid = "UTC") {
+ date_default_timezone_set($tzid);
+ $sqldate = self::toSQLDateTime($date);
+ $tdate = array();
+ $tdate["year"] = substr($sqldate,0,4);
+ $tdate["mon"] = substr($sqldate,5,2);
+ $tdate["mday"] = substr($sqldate,8,2);
+ $tdate["hours"] = substr($sqldate,11,2);
+ $tdate["minutes"] = substr($sqldate,14,2);
+ $tdate["seconds"] = substr($sqldate,17,2);
+ $newdate=mktime($tdate["hours"] + $hour, $tdate["minutes"] + $min, $tdate["seconds"] + $sec, $tdate["mon"] + $month, $tdate["mday"] + $day, $tdate["year"] + $year);
+ date_default_timezone_set("UTC");
+ //echo self::toSQLDateTime($date) . " => " . self::toSQLDateTime($newdate) . " ($hour:$min:$sec $month/$day/$year)
\n";
+ return $newdate;
+ }
+
+ /**
+ * Date math: get date from week and day in specifiec month
+ *
+ * This routine finds actual dates for the second Tuesday of the month, last Friday of the month, etc.
+ * For second Tuesday, use $week = 1, $wday = 2
+ * for last Friday, use $week = -1, $wday = 5
+ *
+ * @param int $date Unix timestamp
+ *
+ * @param int $week week number, 0 is first week, -1 is last
+ *
+ * @param int $wday day of week, 0 is Sunday, 6 is Saturday
+ *
+ * @param string $tzid PHP supported timezone
+ *
+ * @return int Unix timestamp
+ */
+ static function getDateFromDay($date, $week, $wday,$tzid="UTC") {
+ //echo "getDateFromDay(" . self::toSqlDateTime($date) . ",$week,$wday)
\n";
+ // determine first day in month
+ $tdate = getdate($date);
+ $monthbegin = gmmktime(0,0,0, $tdate["mon"],1,$tdate["year"]);
+ $monthend = self::addDate($monthbegin, 0,0,0,1,-1,0,$tzid); // add 1 month and subtract 1 day
+ $day = self::addDate($date,0,0,0,0,1 - $tdate["mday"],0,$tzid);
+ $month = array(array());
+ while($day <= $monthend) {
+ $tdate=getdate($day);
+ $month[$tdate["wday"]][]=$day;
+ //echo self::toSQLDateTime($day) . "
\n";
+ $day = self::addDate($day, 0,0,0,0,1,0,$tzid); // add 1 day
+ }
+ $dayinmonth=0;
+ if($week >= 0)
+ $dayinmonth = $month[$wday][$week];
+ else
+ $dayinmonth = $month[$wday][count($month[$wday]) - 1];
+ //echo "return " . self::toSQLDateTime($dayinmonth);
+ //exit;
+ return $dayinmonth;
+ }
+
+ /**
+ * Convert UTC date-time to local date-time
+ *
+ * @param string $sqldate SQL date-time string
+ *
+ * @param string $tzid PHP recognized timezone (default is "UTC")
+ *
+ * @return string SQL date-time string
+ */
+ static function toLocalDateTime($sqldate, $tzid = "UTC" ){
+ try
+ {
+ $timezone = new DateTimeZone($tzid);
+ }
+ catch(Exception $e)
+ {
+ // bad time zone specified
+ return $sqldate;
+ }
+ $udate = self::toUnixDateTime($sqldate);
+ $daydatetime = new DateTime("@" . $udate);
+ $tzoffset = $timezone->getOffset($daydatetime);
+ return self::toSqlDateTime($udate + $tzoffset);
+ }
+
+ /**
+ * Convert local date-time to UTC date-time
+ *
+ * @param string $sqldate SQL date-time string
+ *
+ * @param string $tzid PHP recognized timezone (default is "UTC")
+ *
+ * @return string SQL date-time string
+ */
+ static function toUTCDateTime($sqldate, $tzid = "UTC" ){
+
+ date_default_timezone_set("UTC");
+ try
+ {
+ $date = new DateTime($sqldate, $tzid);
+ }
+ catch(Exception $e)
+ {
+ // bad time zone specified
+ return $sqldate;
+ }
+ $offset = $date->getOffsetFromGMT();
+ if($offset >= 0)
+ $date->sub(new DateInterval("PT".$offset."S"));
+ else
+ $date->add(new DateInterval("PT".abs($offset)."S"));
+ return $date->toSql(true);
+ }
+
+ /**
+ * Convert from a relative date to an absolute date
+ *
+ * Examples of relative dates are "-2y" for 2 years ago, "18m"
+ * for 18 months after today. Relative date uses "y", "m" and "d" for
+ * year, month and day. Relative date can be combined into comma
+ * separated list, i.e., "-1y,-1d" for 1 year and 1 day ago.
+ *
+ * @param string $date relative date string (i.e. "1y" for 1 year from today)
+ *
+ * @param string $rdate reference date, or blank for current date (in SQL date-time format)
+ *
+ * @return string in SQL date-time format
+ */
+ static function getAbsDate($date,$rdate = ""){
+ if(str_replace(array("y","m","d","h","n"),"",strtolower($date)) != strtolower($date)){
+ date_default_timezone_set("UTC");
+ if($rdate == "")
+ $udate = time();
+ else
+ $udate = self::toUnixDateTime($rdate);
+ $values=explode(",",strtolower($date));
+ $y = 0;
+ $m = 0;
+ $d = 0;
+ $h = 0;
+ $n = 0;
+ foreach($values as $value){
+ $rtype = substr($value,strlen($value)-1);
+ $rvalue = intval(substr($value,0,strlen($value) - 1));
+ switch($rtype){
+ case 'y':
+ $y = $rvalue;
+ break;
+ case 'm':
+ $m = $rvalue;
+ break;
+ case 'd':
+ $d = $rvalue;
+ break;
+ case 'h':
+ $h = $rvalue;
+ break;
+ case 'n':
+ $n = $rvalue;
+ break;
+ }
+ // for "-" values, move to start of day , otherwise, move to end of day
+ if($rvalue[0] == '-')
+ $udate = mktime(0,0,0,date('m',$udate),date('d',$udate),date('Y',$udate));
+ else
+ $udate = mktime(0,-1,0,date('m',$udate),date('d',$udate)+1,date('Y',$udate));
+ $udate = self::addDate($udate,$h,$n,0,$m,$d,$y);
+ }
+ $date = self::toSqlDateTime($udate);
+ }
+ return $date;
+ }
+
+ /**
+ * Format Unix timestamp to iCal date-time format
+ *
+ * @param int $datetime Unix timestamp
+ *
+ * @return string iCal date-time string
+ */
+ static function toiCalDateTime($datetime = null){
+ date_default_timezone_set('UTC');
+ if($datetime == null)
+ $datetime = time();
+ return gmdate("Ymd\THis",$datetime);
+ }
+
+ /**
+ * Format Unix timestamp to iCal date format
+ *
+ * @param int $datetime Unix timestamp
+ *
+ * @return string iCal date-time string
+ */
+ static function toiCalDate($datetime = null){
+ date_default_timezone_set('UTC');
+ if($datetime == null)
+ $datetime = time();
+ return gmdate("Ymd",$datetime);
+ }
+}
diff --git a/src/lib/includes/framework.php b/src/lib/includes/framework.php
new file mode 100644
index 0000000..69df54e
--- /dev/null
+++ b/src/lib/includes/framework.php
@@ -0,0 +1,32 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+// No direct access
+defined('_ZAPCAL') or die( 'Restricted access' );
+
+/**
+ * set MAXYEAR to 2036 for 32 bit systems, can be higher for 64 bit systems
+ *
+ * @var integer
+ */
+define('_ZAPCAL_MAXYEAR', 2036);
+
+/**
+ * set MAXREVENTS to maximum # of repeating events
+ *
+ * @var integer
+ */
+define('_ZAPCAL_MAXREVENTS', 5000);
+
+require_once(_ZAPCAL_BASE . '/includes/date.php');
+require_once(_ZAPCAL_BASE . '/includes/recurringdate.php');
+require_once(_ZAPCAL_BASE . '/includes/ical.php');
+require_once(_ZAPCAL_BASE . '/includes/timezone.php');
diff --git a/src/lib/includes/ical.php b/src/lib/includes/ical.php
new file mode 100644
index 0000000..1b89244
--- /dev/null
+++ b/src/lib/includes/ical.php
@@ -0,0 +1,986 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+// No direct access
+defined('_ZAPCAL') or die( 'Restricted access' );
+
+/**
+ * Object for storing an unfolded iCalendar line
+ *
+ * The ZCiCalDataNode class contains data from an unfolded iCalendar line
+ *
+ */
+class ZCiCalDataNode {
+ /**
+ * The name of the node
+ *
+ * @var string
+ */
+ var $name = "";
+
+ /**
+ * Node parameters (before the colon ":")
+ *
+ * @var array
+ */
+ var $parameter=array();
+
+ /**
+ * Node values (after the colon ":")
+ *
+ * @var array
+ */
+ var $value=array();
+
+ /**
+ * Create an object from an unfolded iCalendar line
+ *
+ * @param string $line An unfolded iCalendar line
+ *
+ * @return void
+ *
+ */
+ function __construct( $line ) {
+ //echo "ZCiCalDataNode($line)
\n";
+ //separate line into parameters and value
+ // look for colon separating name or parameter and value
+ // first change any escaped colons temporarily to make it easier
+ $tline = str_replace("\\:", "`~", $line);
+ // see if first colon is inside a quoted string
+ $i = 0;
+ $datafind = false;
+ $inquotes = false;
+ while(!$datafind && ($i < strlen($tline))) {
+ //echo "$i: " . $tline[$i] . ", ord() = " . ord($tline{$i}) . "
\n";
+ if(!$inquotes && $tline[$i] == ':')
+ $datafind=true;
+ else{
+ $i += 1;
+ if(substr($tline,$i,1) == '"')
+ $inquotes = !$inquotes;
+ }
+ }
+ if($datafind){
+ $value = str_replace("`~","\\:",substr($line,$i+1));
+ // fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal)
+ $value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value);
+ $tvalue = str_replace("\\,", "`~", $value);
+ //echo "value: " . $tvalue . "
\n";
+ $tvalue = explode(",",$tvalue);
+ $value = str_replace("`~","\\,",$tvalue);
+ $this->value = $value;
+ }
+
+ $parameter = trim(substr($line,0,$i));
+
+ $parameter = str_replace("\\;", "`~", $parameter);
+ $parameters = explode(";", $parameter);
+ $parameters = str_replace("`~", "\\;", $parameters);
+ $this->name = array_shift($parameters);
+ foreach($parameters as $parameter){
+ $pos = strpos($parameter,"=");
+ if($pos > 0){
+ $param = substr($parameter,0,$pos);
+ $paramvalue = substr($parameter,$pos+1);
+ $tvalue = str_replace("\\,", "`~", $paramvalue);
+ //$tvalue = explode(",",$tvalue);
+ $paramvalue = str_replace("`~","\\,",$tvalue);
+ $this->parameter[strtolower($param)] = $paramvalue;
+ //$this->paramvalue[] = $paramvalue;
+ }
+ }
+ }
+
+/**
+ * getName()
+ *
+ * Return the name of the object
+ *
+ * @return string
+ */
+ function getName(){
+ return $this->name;
+ }
+
+/**
+ * Get $ith parameter from array
+ * @param int $i
+ *
+ * @return var
+ */
+ function getParameter($i){
+ return $this->parameter[$i];
+ }
+
+/**
+ * Get parameter array
+ *
+ * @return array
+ */
+ function getParameters(){
+ return $this->parameter;
+ }
+
+/**
+ * Get comma separated values
+ *
+ * @return string
+ */
+ function getValues(){
+ return implode(",",$this->value);
+ }
+}
+
+/**
+ * Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects)
+ *
+ * @property object $parentnode Parent of this node
+ *
+ * @property array $child Array of children for this node
+ *
+ * @property data $data Array of data for this node
+ *
+ * @property object $next Next sibling of this node
+ *
+ * @property object $prev Previous sibling of this node
+ */
+
+class ZCiCalNode {
+ /**
+ * The name of the node
+ *
+ * @var string
+ */
+ var $name="";
+
+ /**
+ * The parent of this node
+ *
+ * @var object
+ */
+ var $parentnode=null;
+
+ /**
+ * Array of children for this node
+ *
+ * @var array
+ */
+ var $child= array();
+
+ /**
+ * Array of $data for this node
+ *
+ * @var array
+ */
+ var $data= array();
+
+
+ /**
+ * Next sibling of this node
+ *
+ * @var object
+ */
+ var $next=null;
+
+ /**
+ * Previous sibling of this node
+ *
+ * @var object
+ */
+ var $prev=null;
+
+ /**
+ * Create ZCiCalNode
+ *
+ * @param string $_name Name of node
+ *
+ * @param object $_parent Parent node for this node
+ *
+ * @param bool $first Is this the first child for this parent?
+ */
+ function __construct( $_name, & $_parent, $first = false) {
+ $this->name = $_name;
+ $this->parentnode = $_parent;
+ if($_parent != null){
+ if(count($this->parentnode->child) > 0) {
+ if($first)
+ {
+ $first = & $this->parentnode->child[0];
+ $first->prev = & $this;
+ $this->next = & $first;
+ }
+ else
+ {
+ $prev =& $this->parentnode->child[count($this->parentnode->child)-1];
+ $prev->next =& $this;
+ $this->prev =& $prev;
+ }
+ }
+ if($first)
+ {
+ array_unshift($this->parentnode->child, $this);
+ }
+ else
+ {
+ $this->parentnode->child[] =& $this;
+ }
+ }
+ /*
+ echo "creating " . $this->getName();
+ if($_parent != null)
+ echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child);
+ echo "
";
+ */
+ }
+
+ /**
+ * Return the name of the object
+ *
+ * @return string
+ */
+ function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Add node to list
+ *
+ * @param object $node
+ *
+ */
+ function addNode($node) {
+ if(array_key_exists($node->getName(), $this->data))
+ {
+ if(!is_array($this->data[$node->getName()]))
+ {
+ $this->data[$node->getName()] = array($this->data[$node->getName()]);
+ }
+ $this->data[$node->getName()][] = $node;
+ }
+ else
+ {
+ $this->data[$node->getName()] = $node;
+ }
+ }
+
+ /**
+ * Get Attribute
+ *
+ * @param int $i array id of attribute to get
+ *
+ * @return string
+ */
+ function getAttrib($i) {
+ return $this->attrib[$i];
+ }
+
+ /**
+ * Set Attribute
+ *
+ * @param string $value value of attribute to set
+ *
+ */
+ function setAttrib($value) {
+ $this->attrib[] = $value;
+ }
+
+ /**
+ * Get the parent object of this object
+ *
+ * @return object parent of this object
+ */
+ function &getParent() {
+ return $this->parentnode;
+ }
+
+ /**
+ * Get the first child of this object
+ *
+ * @return object The first child
+ */
+ function &getFirstChild(){
+ static $nullguard = null;
+ if(count($this->child) > 0) {
+ //echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "
";
+ return $this->child[0];
+ }
+ else
+ return $nullguard;
+ }
+
+ /**
+ * Print object tree in HTML for debugging purposes
+ *
+ * @param object $node select part of tree to print, or leave blank for full tree
+ *
+ * @param int $level Level of recursion (usually leave this blank)
+ *
+ * @return string - HTML formatted display of object tree
+ */
+ function printTree(& $node=null, $level=1){
+ $level += 1;
+ $html = "";
+ if($node == null)
+ $node = $this->parentnode;
+ if($level > 5)
+ {
+ die("levels nested too deep
\n");
+ //return;
+ }
+ for($i = 0 ; $i < $level; $i ++)
+ $html .= "+";
+ $html .= $node->getName() . "
\n";
+ foreach ($node->child as $c){
+ $html .= $node->printTree($c,$level);
+ }
+ $level -= 1;
+ return $html;
+ }
+
+ /**
+ * export tree to icalendar format
+ *
+ * @param object $node Top level node to export
+ *
+ * @param int $level Level of recursion (usually leave this blank)
+ *
+ * @return string iCalendar formatted output
+ */
+ function export(& $node=null, $level=0){
+ $txtstr = "";
+ if($node == null)
+ $node = $this;
+ if($level > 5)
+ {
+ //die("levels nested too deep
\n");
+ throw new Exception("levels nested too deep");
+ }
+ $txtstr .= "BEGIN:" . $node->getName() . "\r\n";
+ if(property_exists($node,"data"))
+ foreach ($node->data as $d){
+ if(is_array($d))
+ {
+ foreach ($d as $c)
+ {
+ //$txtstr .= $node->export($c,$level + 1);
+ $p = "";
+ $params = @$c->getParameters();
+ if(count($params) > 0)
+ {
+ foreach($params as $key => $value){
+ $p .= ";" . strtoupper($key) . "=" . $value;
+ }
+ }
+ $txtstr .= $this->printDataLine($c, $p);
+ }
+ }
+ else
+ {
+ $p = "";
+ $params = @$d->getParameters();
+ if(count($params) > 0)
+ {
+ foreach($params as $key => $value){
+ $p .= ";" . strtoupper($key) . "=" . $value;
+ }
+ }
+ $txtstr .= $this->printDataLine($d, $p);
+ /*
+ $values = $d->getValues();
+ // don't think we need this, Sunbird does not like it in the EXDATE field
+ //$values = str_replace(",", "\\,", $values);
+
+ $line = $d->getName() . $p . ":" . $values;
+ $line = str_replace(array("
","
","
","
0) {
+ $linewidth = ($linecount == 0? 75 : 74);
+ $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line));
+ if($linecount > 0)
+ $txtstr .= " ";
+ $txtstr .= substr($line,0,$linesize) . "\r\n";
+ $linecount += 1;
+ $line = substr($line,$linewidth);
+ }
+ */
+ }
+ //echo $line . "\n";
+ }
+ if(property_exists($node,"child"))
+ foreach ($node->child as $c){
+ $txtstr .= $node->export($c,$level + 1);
+ }
+ $txtstr .= "END:" . $node->getName() . "\r\n";
+ return $txtstr;
+ }
+
+ /**
+ * print an attribute line
+
+ * @param object $d attributes
+ * @param object $p properties
+ *
+ */
+ function printDataLine($d, $p)
+ {
+ $txtstr = "";
+
+ $values = $d->getValues();
+ // don't think we need this, Sunbird does not like it in the EXDATE field
+ //$values = str_replace(",", "\\,", $values);
+
+ $line = $d->getName() . $p . ":" . $values;
+ $line = str_replace(array("
","
","
","
0) {
+ $linewidth = ($linecount == 0? 75 : 74);
+ $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line));
+ if($linecount > 0)
+ $txtstr .= " ";
+ $txtstr .= substr($line,0,$linesize) . "\r\n";
+ $linecount += 1;
+ $line = substr($line,$linewidth);
+ }
+ return $txtstr;
+ }
+}
+
+/**
+ *
+ * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes.
+ *
+*/
+class ZCiCal {
+ /**
+ * The root node of the object tree
+ *
+ * @var object
+ */
+ var $tree=null;
+ /**
+ * The most recently created node in the tree
+ *
+ * @var object
+ */
+ var $curnode=null;
+
+/**
+ * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes.
+ *
+ * use maxevents and startevent to read events in multiple passes (to save memory)
+ *
+ * @param string $data icalendar feed string (empty if creating new feed)
+ *
+ * @param int $maxevents maximum # of events to read
+ *
+ * @param int $startevent starting event to read
+ *
+ * @return void
+ *
+*
+*/
+function __construct($data = "", $maxevents = 1000000, $startevent = 0) {
+
+ if($data != ""){
+ // unfold lines
+ // first change all eol chars to "\n"
+ $data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data);
+ // now unfold lines
+ //$data = str_replace(array("\n ", "\n "),"!?", $data);
+ $data = str_replace(array("\n ", "\n "),"", $data);
+ // replace special iCal chars
+ $data = str_replace(array("\\\\","\,"),array("\\",","), $data);
+
+ // parse each line
+ $lines = explode("\n", $data);
+
+ $linecount = 0;
+ $eventcount = 0;
+ $eventpos = 0;
+ foreach($lines as $line) {
+ //$line = str_replace("!?", "\n", $line); // add nl back into descriptions
+ // echo ($linecount + 1) . ": " . $line . "
";
+ if(substr($line,0,6) == "BEGIN:") {
+ // start new object
+ $name = substr($line,6);
+ if($name == "VEVENT")
+ {
+ if($eventcount < $maxevents && $eventpos >= $startevent)
+ {
+ $this->curnode = new ZCiCalNode($name, $this->curnode);
+ if($this->tree == null)
+ $this->tree = $this->curnode;
+ }
+ }
+ else
+ {
+ $this->curnode = new ZCiCalNode($name, $this->curnode);
+ if($this->tree == null)
+ $this->tree = $this->curnode;
+ }
+ //echo "new node: " . $this->curnode->name . "
\n";
+ /*
+ if($this->curnode->getParent() != null)
+ echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "
";
+ else
+ echo "parent of " . $this->curnode->getName() . " is null
";
+ */
+ }
+ else if(substr($line,0,4) == "END:") {
+ $name = substr($line,4);
+ if($name == "VEVENT")
+ {
+ if($eventcount < $maxevents && $eventpos >= $startevent)
+ {
+ $eventcount++;
+ if($this->curnode->getName() != $name) {
+ //panic, mismatch in iCal structure
+ //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead");
+ throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead");
+ }
+ if($this->curnode->getParent() != null) {
+ //echo "moving up from " . $this->curnode->getName() ;
+ $this->curnode = & $this->curnode->getParent();
+ //echo " to " . $this->curnode->getName() . "
";
+ //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
";
+ }
+ }
+ $eventpos++;
+ }
+ else
+ {
+ if($this->curnode->getName() != $name) {
+ //panic, mismatch in iCal structure
+ //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead");
+ throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead");
+ }
+ if($this->curnode->getParent() != null) {
+ //echo "moving up from " . $this->curnode->getName() ;
+ $this->curnode = & $this->curnode->getParent();
+ //echo " to " . $this->curnode->getName() . "
";
+ //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
";
+ }
+ }
+ }
+ else {
+ $datanode = new ZCiCalDataNode($line);
+ if($this->curnode->getName() == "VEVENT")
+ {
+ if($eventcount < $maxevents && $eventpos >= $startevent)
+ {
+ if($datanode->getName() == "EXDATE")
+ {
+ if(!array_key_exists($datanode->getName(),$this->curnode->data))
+ {
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ }
+ else
+ {
+ $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0];
+ }
+ }
+ else
+ {
+ if(!array_key_exists($datanode->getName(),$this->curnode->data))
+ {
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ }
+ else
+ {
+ $tnode = $this->curnode->data[$datanode->getName()];
+ $this->curnode->data[$datanode->getName()] = array();
+ $this->curnode->data[$datanode->getName()][] = $tnode;
+ $this->curnode->data[$datanode->getName()][] = $datanode;
+ }
+ }
+ }
+ }
+ else
+ {
+ if($datanode->getName() == "EXDATE")
+ {
+ if(!array_key_exists($datanode->getName(),$this->curnode->data))
+ {
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ }
+ else
+ {
+ $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0];
+ }
+ }
+ else
+ {
+ if(!array_key_exists($datanode->getName(),$this->curnode->data))
+ {
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ }
+ else
+ {
+ $tnode = $this->curnode->data[$datanode->getName()];
+ $this->curnode->data[$datanode->getName()] = array();
+ $this->curnode->data[$datanode->getName()][] = $tnode;
+ $this->curnode->data[$datanode->getName()][] = $datanode;
+ }
+ }
+ }
+ }
+ $linecount++;
+ }
+ }
+ else {
+ $name = "VCALENDAR";
+ $this->curnode = new ZCiCalNode($name, $this->curnode);
+ $this->tree = $this->curnode;
+ $datanode = new ZCiCalDataNode("VERSION:2.0");
+ $this->curnode->data[$datanode->getName()] = $datanode;
+
+ $datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//ZapCalLib 1.0//EN");
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ $datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN");
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ $datanode = new ZCiCalDataNode("METHOD:PUBLISH");
+ $this->curnode->data[$datanode->getName()] = $datanode;
+ }
+}
+
+/**
+ * CountEvents()
+ *
+ * Return the # of VEVENTs in the object
+ *
+ * @return int
+ */
+
+function countEvents() {
+ $count = 0;
+ if(isset($this->tree->child)){
+ foreach($this->tree->child as $child){
+ if($child->getName() == "VEVENT")
+ $count++;
+ }
+ }
+ return $count;
+}
+
+/**
+ * CountVenues()
+ *
+ * Return the # of VVENUEs in the object
+ *
+ * @return int
+ */
+
+function countVenues() {
+ $count = 0;
+ if(isset($this->tree->child)){
+ foreach($this->tree->child as $child){
+ if($child->getName() == "VVENUE")
+ $count++;
+ }
+ }
+ return $count;
+}
+
+/**
+ * Export object to string
+ *
+ * This function exports all objects to an iCalendar string
+ *
+ * @return string an iCalendar formatted string
+ */
+
+function export() {
+ return $this->tree->export($this->tree);
+}
+
+/**
+ * Get first event in object list
+ * Use getNextEvent() to navigate through list
+ *
+ * @return object The first event, or null
+ */
+function &getFirstEvent() {
+ static $nullguard = null;
+ if ($this->countEvents() > 0){
+ $child = $this->tree->child[0];
+ $event=false;
+ while(!$event && $child != null){
+ if($child->getName() == "VEVENT")
+ $event = true;
+ else
+ $child = $child->next;
+ }
+ return $child;
+ }
+ else
+ return $nullguard;
+}
+
+/**
+ * Get next event in object list
+ *
+ * @param object $event The current event object
+ *
+ * @return object Returns the next event or null if past last event
+ */
+function &getNextEvent($event){
+ do{
+ $event = $event->next;
+ } while($event != null && $event->getName() != "VEVENT");
+ return $event;
+}
+
+/**
+ * Get first venue in object list
+ * Use getNextVenue() to navigate through list
+ *
+ * @return object The first venue, or null
+ */
+function &getFirstVenue() {
+ static $nullguard = null;
+ if ($this->countVenues() > 0){
+ $child = $this->tree->child[0];
+ $event=false;
+ while(!$event && $child != null){
+ if($child->getName() == "VVENUE")
+ $event = true;
+ else
+ $child = $child->next;
+ }
+ return $child;
+ }
+ else
+ return $nullguard;
+}
+
+/**
+ * Get next venue in object list
+ *
+ * @param object $venue The current venue object
+ *
+ * @return object Returns the next venue or null if past last venue
+ */
+function &getNextVenue($venue){
+ do{
+ $venue = $venue->next;
+ } while($venue != null && $venue->getName() != "VVENUE");
+ return $venue;
+}
+
+/**
+ * Get first child in object list
+ * Use getNextSibling() and getPreviousSibling() to navigate through list
+ *
+ * @param object $thisnode The parent object
+ *
+ * @return object The child object
+ */
+function &getFirstChild(& $thisnode){
+ $nullvalue = null;
+ if(count($thisnode->child) > 0) {
+ //echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "
";
+ return $thisnode->child[0];
+ }
+ else
+ return $nullvalue;
+}
+
+/**
+ * Get next sibling in object list
+ *
+ * @param object $thisnode The current object
+ *
+ * @return object Returns the next sibling
+ */
+function &getNextSibling(& $thisnode){
+ return $thisnode->next;
+}
+
+/**
+ * Get previous sibling in object list
+ *
+ * @param object $thisnode The current object
+ *
+ * @return object Returns the previous sibling
+ */
+function &getPrevSibling(& $thisnode){
+ return $thisnode->prev;
+}
+
+/**
+ * Read date/time in iCal formatted string
+ *
+ * @param string iCal formated date/time string
+ *
+ * @return int Unix timestamp
+ * @deprecated Use ZDateHelper::toUnixDateTime() instead
+ */
+
+function toUnixDateTime($datetime){
+ $year = substr($datetime,0,4);
+ $month = substr($datetime,4,2);
+ $day = substr($datetime,6,2);
+ $hour = 0;
+ $minute = 0;
+ $second = 0;
+ if(strlen($datetime) > 8 && $datetime[8] == "T") {
+ $hour = substr($datetime,9,2);
+ $minute = substr($datetime,11,2);
+ $second = substr($datetime,13,2);
+ }
+ $d1 = mktime($hour, $minute, $second, $month, $day, $year);
+
+}
+
+/**
+ * fromUnixDateTime()
+ *
+ * Take Unix timestamp and format to iCal date/time string
+ *
+ * @param int $datetime Unix timestamp, leave blank for current date/time
+ *
+ * @return string formatted iCal date/time string
+ * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead
+ */
+
+static function fromUnixDateTime($datetime = null){
+ date_default_timezone_set('UTC');
+ if($datetime == null)
+ $datetime = time();
+ return date("Ymd\THis",$datetime);
+}
+
+
+/**
+ * fromUnixDate()
+ *
+ * Take Unix timestamp and format to iCal date string
+ *
+ * @param int $datetime Unix timestamp, leave blank for current date/time
+ *
+ * @return string formatted iCal date string
+ * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead
+ */
+
+static function fromUnixDate($datetime = null){
+ date_default_timezone_set('UTC');
+ if($datetime == null)
+ $datetime = time();
+ return date("Ymd",$datetime);
+}
+
+/**
+ * Format into iCal time format from SQL date or SQL date-time format
+ *
+ * @param string $datetime SQL date or SQL date-time string
+ *
+ * @return string iCal formatted string
+ * @deprecated Use ZDateHelper::fromSqlDateTime() instead
+ */
+static function fromSqlDateTime($datetime = ""){
+ if($datetime == "")
+ $datetime = ZDateHelper::toSqlDateTime();
+ if(strlen($datetime) > 10)
+ return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2),
+ substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2));
+ else
+ return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2));
+}
+
+/**
+ * Format iCal time format to either SQL date or SQL date-time format
+ *
+ * @param string $datetime icalendar formatted date or date-time
+ * @return string SQL date or SQL date-time string
+ * @deprecated Use ZDateHelper::toSqlDateTime() instead
+ */
+static function toSqlDateTime($datetime = ""){
+ if($datetime == "")
+ return ZDateHelper::toSqlDateTime();
+ if(strlen($datetime) > 10)
+ return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2),
+ substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2));
+ else
+ return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2));
+}
+
+/**
+ * Pull timezone data from node and put in array
+ *
+ * Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule
+ *
+ * @param array $node timezone object
+ *
+ * @return array
+ */
+static function getTZValues($node){
+ $tzvalues = array();
+
+ $tnode = @$node->data['TZOFFSETFROM'];
+ if($tnode != null){
+ $tzvalues["tzoffsetfrom"] = $tnode->getValues();
+ }
+
+ $tnode = @$node->data['TZOFFSETTO'];
+ if($tnode != null){
+ $tzvalues["tzoffsetto"] = $tnode->getValues();
+ }
+
+ $tnode = @$node->data['TZNAME'];
+ if($tnode != null){
+ $tzvalues["tzname"] = $tnode->getValues();
+ }
+ else
+ $tzvalues["tzname"] = "";
+
+ $tnode = @$node->data['DTSTART'];
+ if($tnode != null){
+ $tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues());
+ }
+
+ $tnode = @$node->data['RRULE'];
+ if($tnode != null){
+ $tzvalues["rrule"] = $tnode->getValues();
+ //echo "rule: " . $tzvalues["rrule"] . "
\n";
+ }
+ else{
+ // no rule specified, let's create one from based on the date
+ $date = getdate($tzvalues["dtstart"]);
+ $month = $date["mon"];
+ $day = $date["mday"];
+ $tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day";
+ }
+
+ return $tzvalues;
+}
+
+/**
+ * Escape slashes, commas and semicolons in strings
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+static function formatContent($content)
+{
+ $content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content);
+ return $content;
+}
+
+}
+
+?>
diff --git a/src/lib/includes/index.html b/src/lib/includes/index.html
new file mode 100644
index 0000000..53a7f24
--- /dev/null
+++ b/src/lib/includes/index.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/lib/includes/recurringdate.php b/src/lib/includes/recurringdate.php
new file mode 100644
index 0000000..9f6920f
--- /dev/null
+++ b/src/lib/includes/recurringdate.php
@@ -0,0 +1,796 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+// No direct access
+defined('_ZAPCAL') or die( 'Restricted access' );
+
+/**
+ * Zap Calendar Recurring Date Helper Class
+ *
+ * Class to expand recurring rule to a list of dates
+ */
+class ZCRecurringDate {
+ /**
+ * rules string
+ *
+ * @var string
+ */
+ var $rules = "";
+
+ /**
+ * start date in Unix Timestamp format (local timezone)
+ *
+ * @var integer
+ */
+ var $startdate = null;
+
+ /**
+ * repeating frequency type (i.e. "y" for yearly, "m" for monthly)
+ *
+ * @var string
+ */
+ var $freq = null;
+
+ /**
+ * timezone of event (using PHP timezones)
+ *
+ * @var string
+ */
+ var $tzid = null;
+
+ /**
+ * repeat mode ('c': count, 'u': until)
+ *
+ * @var string
+ */
+ var $repeatmode=null;
+
+ /**
+ * repeat until date (in UTC Unix Timestamp format)
+ *
+ * @var integer
+ */
+ var $until=null;
+
+ /**
+ * repeat count when repeat mode is 'c'
+ *
+ * @var integer
+ */
+ var $count=0;
+
+ /**
+ * array of repeat by seconds values
+ *
+ * @var array
+ */
+ var $bysecond=array();
+
+ /**
+ * array of repeat by minutes values
+ *
+ * @var array
+ */
+ var $byminute=array();
+
+ /**
+ * array of repeat by hour values
+ *
+ * @var array
+ */
+ var $byhour=array();
+
+ /**
+ * array of repeat by day values
+ *
+ * @var array
+ */
+ var $byday=array();
+
+ /**
+ * array of repeat by month day values
+ *
+ * @var array
+ */
+ var $bymonthday=array();
+
+ /**
+ * array of repeat by month values
+ *
+ * @var array
+ */
+ var $bymonth=array();
+
+ /**
+ * array of repeat by year values
+ *
+ * @var array
+ */
+ var $byyear=array();
+
+ /**
+ * array of repeat by setpos values
+ *
+ * @var array
+ */
+ var $bysetpos=array();
+
+ /**
+ * inteval of repeating event (i.e. every 2 weeks, every 6 months)
+ *
+ * @var integer
+ */
+ var $interval = 1;
+
+ /**
+ * debug level (for testing only)
+ *
+ * @var integer
+ */
+ var $debug = 0;
+
+ /**
+ * error string (future use)
+ *
+ * @var string
+ */
+ var $error;
+
+ /**
+ * array of exception dates in Unix Timestamp format (UTC dates)
+ *
+ * @var array
+ */
+ var $exdates=array();
+
+/**
+ * Expand recurring rule to a list of dates
+ *
+ * @param string $rules iCalendar rules string
+ * @param integer $startdate start date in Unix Timestamp format
+ * @param array $exdates array of exception dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ */
+ function __construct($rules, $startdate, $exdates = array(),$tzid = "UTC"){
+ if(strlen($rules) > 0){
+ //move exdates to event timezone for comparing with event date
+ for($i = 0; $i < count($exdates); $i++)
+ {
+ $exdates[$i] = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($exdates[$i]),$tzid));
+ }
+
+ $rules=str_replace("\'","",$rules);
+ $this->rules = $rules;
+ if($startdate == null){
+ // if not specified, use start date of beginning of last year
+ $tdate=getdate();
+ $startdate=mktime(0,0,0,1,1,$tdate["year"] - 1);
+ }
+ $this->startdate = $startdate;
+ $this->tzid = $tzid;
+ $this->exdates = $exdates;
+
+ $rules=explode(";", $rules);
+ $ruletype = "";
+ foreach($rules as $rule){
+ $item=explode("=",$rule);
+ //echo $item[0] . "=" . $item[1] . "
\n";
+ switch($item[0]){
+ case "FREQ":
+ switch($item[1]){
+ case "YEARLY":
+ $this->freq="y";
+ break;
+ case "MONTHLY":
+ $this->freq="m";
+ break;
+ case "WEEKLY":
+ $this->freq="w";
+ break;
+ case "DAILY":
+ $this->freq="d";
+ break;
+ case "HOURLY":
+ $this->freq="h";
+ break;
+ case "MINUTELY":
+ $this->freq="i";
+ break;
+ case "SECONDLY":
+ $this->freq="s";
+ break;
+ }
+ break;
+ case "INTERVAL":
+ $this->interval = $item[1];
+ break;
+ case "BYSECOND":
+ $this->bysecond = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYMINUTE":
+ $this->byminute = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYHOUR":
+ $this->byhour = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYDAY":
+ $this->byday = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYMONTHDAY":
+ $this->bymonthday = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYMONTH":
+ $this->bymonth = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "BYYEAR":
+ $this->byyear = explode(",",$item[1]);
+ $ruletype = $item[0];
+ break;
+ case "COUNT":
+ $this->count = intval($item[1]);
+ $this->repeatmode = "c";
+ break;
+ case "BYSETPOS":
+ $this->bysetpos = explode(",",$item[1]);
+ break;
+ case "UNTIL":
+ $this->until = ZDateHelper::fromiCaltoUnixDateTime($item[1]);
+ $this->repeatmode = "u";
+ break;
+ }
+ }
+ if(count($this->bysetpos) > 0){
+ switch($ruletype){
+ case "BYYEAR":
+ $this->byyear = $this->bySetPos($this->byyear,$this->bysetpos);
+ break;
+ case "BYMONTH":
+ $this->bymonth = $this->bySetPos($this->bymonth,$this->bysetpos);
+ break;
+ case "BYMONTHDAY":
+ $this->bymonthday = $this->bySetPos($this->bymonthday,$this->bysetpos);
+ break;
+ case "BYDAY":
+ $this->byday = $this->bySetPos($this->byday,$this->bysetpos);
+ break;
+ case "BYHOUR":
+ $this->byhour = $this->bySetPos($this->byhour,$this->bysetpos);
+ break;
+ case "BYMINUTE":
+ $this->byminute = $this->bySetPos($this->byminute,$this->bysetpos);
+ break;
+ case "BYSECOND":
+ $this->bysecond = $this->bySetPos($this->bysecond,$this->bysetpos);
+ break;
+ }
+ }
+ }
+ }
+
+/**
+ * bysetpos rule support
+ *
+ * @param array $bytype
+ * @param array $bysetpos
+ *
+ * @return array
+ */
+ function bySetPos($bytype, $bysetpos){
+ $result = array();
+ for($i=0; $i < count($bysetpos); $i++){
+ for($j=0; $j < count($bytype); $j++){
+ $result[] = $bysetpos[$i] . $bytype[$j];
+ }
+ }
+ return $result;
+ }
+
+/**
+ * save error
+ *
+ * @param string $msg
+ */
+ function setError($msg){
+ $this->error = $msg;
+ }
+
+/**
+ * get error message
+ *
+ * @return string error message
+ */
+ function getError(){
+ return $this->error;
+ }
+
+/**
+ * set debug level (0: none, 1: minimal, 2: more output)
+ *
+ * @param integer $level
+ *
+ */
+ function setDebug($level)
+ {
+ $this->debug = $level;
+ }
+
+/**
+ * display debug message
+ *
+ * @param integer $level
+ * @param string $msg
+ */
+ function debug($level, $msg){
+ if($this->debug >= $level)
+ echo $msg . "
\n";
+ }
+
+/**
+ * Get repeating dates by year
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byYear($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byYear(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ if(count($this->byyear) > 0){
+ foreach($this->byyear as $year){
+ $t = getdate($startdate);
+ $wdate = mktime($t[hours],$t[minutes],$t[seconds],$t[month],$t[mday],$year);
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byMonth($wdate, $enddate, $rdates, $tzid);
+ if($count == 0) {
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates))
+ $count = $this->byMonth($startdate, $enddate, $rdates, $tzid);
+ self::debug(1,"byYear() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Get repeating dates by month
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byMonth($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byMonth(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ if(count($this->bymonth) > 0){
+ foreach($this->bymonth as $month){
+ $t = getdate($startdate);
+ $wdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$month,$t["mday"],$t["year"]);
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byMonthDay($wdate, $enddate, $rdates, $tzid);
+ if($count == 0) {
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates))
+ $count = $this->byMonthDay($startdate, $enddate, $rdates, $tzid);
+ self::debug(1,"byMonth() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Get repeating dates by month day
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byMonthDay($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byMonthDay(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate));
+ if(count($this->bymonthday) > 0){
+ foreach($this->bymonthday as $day){
+ $day = intval($day);
+ $t = getdate($startdate);
+ $wdate = mktime($t['hours'],$t['minutes'],$t['seconds'],$t['mon'],$day,$t['year']);
+ self::debug(2,"mktime(" . $t['hours'] . ", " . $t['minutes']
+ . ", " . $t['mon'] . ", " . $day . ", " . $t['year'] . ") returned $wdate");
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byDay($wdate, $enddate, $rdates, $tzid);
+ if($count == 0) {
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates)) {
+ self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate));
+ $count = $this->byDay($startdate, $enddate, $rdates, $tzid);
+ }
+ self::debug(1,"byMonthDay() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Get repeating dates by day
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byDay($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byDay(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $days = array(
+ "SU" => 0,
+ "MO" => 1,
+ "TU" => 2,
+ "WE" => 3,
+ "TH" => 4,
+ "FR" => 5,
+ "SA" => 6);
+ $idays = array(
+ 0 => "SU",
+ 1 => "MO",
+ 2 => "TU",
+ 3 => "WE",
+ 4 => "TH",
+ 5 => "FR",
+ 6 => "SA");
+
+ $count = 0;
+ if(count($this->byday) > 0){
+ if(empty($this->byday[0]))
+ {
+ $this->byday[0] = $idays[date("w",$startdate)];
+ }
+ foreach($this->byday as $tday){
+ $t = getdate($startdate);
+ $day = substr($tday,strlen($tday) - 2);
+ if(strlen($day) < 2)
+ {
+ // missing start day, use current date for DOW
+ $day = $idays[date("w",$startdate)];
+ }
+ if(strlen($tday) > 2) {
+ $imin = 1;
+ $imax = 5; // max # of occurances in a month
+ if(strlen($tday) > 2)
+ $imin = $imax = substr($tday,0,strlen($tday) - 2);
+ self::debug(2,"imin: $imin, imax: $imax, tday: $tday, day: $day, daynum: {$days[$day]}");
+ for($i = $imin; $i <= $imax; $i++){
+ $wdate = ZDateHelper::getDateFromDay($startdate,$i-1,$days[$day],$tzid);
+ self::debug(2,"getDateFromDay(" . ZDateHelper::toSqlDateTime($startdate)
+ . ",$i,{$days[$day]}) returned " . ZDateHelper::toSqlDateTime($wdate));
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byHour($wdate, $enddate, $rdates);
+ if($count == 0){
+ $rdates[] = $wdate;
+ $count++;
+ //break;
+ }
+ }
+ }
+ }
+ else {
+ // day of week version
+ $startdate_dow = date("w",$startdate);
+ $datedelta = $days[$day] - $startdate_dow;
+ self::debug(2, "start_dow: $startdate_dow, datedelta: $datedelta");
+ if($datedelta >= 0)
+ {
+ $wdate = ZDateHelper::addDate($startdate,0,0,0,0,$datedelta,0,$this->tzid);
+ self::debug(2, "wdate: " . ZDateHelper::toSqlDateTime($wdate));
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byHour($wdate, $enddate, $rdates);
+ if($count == 0){
+ $rdates[] = $wdate;
+ $count++;
+ self::debug(2,"adding date " . ZDateHelper::toSqlDateTime($wdate) );
+ }
+ }
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates))
+ $count = $this->byHour($startdate, $enddate, $rdates);
+ self::debug(1,"byDay() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Get repeating dates by hour
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byHour($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byHour(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ if(count($this->byhour) > 0){
+ foreach($this->byhour as $hour){
+ $t = getdate($startdate);
+ $wdate = mktime($hour,$t["minutes"],$t["seconds"],$t["mon"],$t["mday"],$t["year"]);
+ self::debug(2,"checking date/time " . ZDateHelper::toSqlDateTime($wdate));
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->byMinute($wdate, $enddate, $rdates);
+ if($count == 0) {
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates))
+ $count = $this->byMinute($startdate, $enddate, $rdates);
+ self::debug(1,"byHour() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Get repeating dates by minute
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function byMinute($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"byMinute(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ if(count($this->byminute) > 0){
+ foreach($this->byminute as $minute){
+ $t = getdate($startdate);
+ $wdate = mktime($t["hours"],$minute,$t["seconds"],$t["mon"],$t["mday"],$t["year"]);
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $count = $this->bySecond($wdate, $enddate, $rdates);
+ if($count == 0) {
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ }
+ else if(!$this->maxDates($rdates))
+ $count = $this->bySecond($startdate, $enddate, $rdates);
+ self::debug(1,"byMinute() returned " . $count );
+ return $count;
+ }
+/**
+ * Get repeating dates by second
+ *
+ * @param integer $startdate start date of repeating events, in Unix timestamp format
+ * @param integer $enddate end date of repeating events, in Unix timestamp format
+ * @param array $rdates array to contain expanded repeating dates
+ * @param string $tzid timezone of event (using PHP timezones)
+ *
+ * @return integer count of dates
+ */
+ private function bySecond($startdate, $enddate, &$rdates, $tzid="UTC"){
+ self::debug(1,"bySecond(" . ZDateHelper::toSqlDateTime($startdate) . ","
+ . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)");
+ $count = 0;
+ if(count($this->bysecond) > 0){
+ foreach($this->bysecond as $second){
+ $t = getdate($startdate);
+ $wdate = mktime($t["hours"],$t["minutes"],$second,$t["mon"],$t["mday"],$t["year"]);
+ if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){
+ $rdates[] = $wdate;
+ $count++;
+ }
+ }
+ }
+ self::debug(1,"bySecond() returned " . $count );
+ return $count;
+ }
+
+/**
+ * Determine if the loop has reached the end date
+ *
+ * @param array $rdates array of repeating dates
+ *
+ * @return boolean
+ */
+ private function maxDates($rdates){
+ if($this->repeatmode == "c" && count($rdates) >= $this->count)
+ return true; // exceeded count
+ else if(count($rdates) > 0 && $this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){
+ return true; //past date
+ }
+ return false;
+ }
+
+/**
+ * Get array of dates from recurring rule
+ *
+ * @param $maxdate integer maximum date to appear in repeating dates in Unix timestamp format
+ *
+ * @return array
+ */
+ public function getDates($maxdate = null){
+ //$this->debug = 2;
+ self::debug(1,"getDates()");
+ $nextdate = $enddate = $this->startdate;
+ $rdates = array();
+ $done = false;
+ $eventcount = 0;
+ $loopcount = 0;
+ self::debug(2,"freq: " . $this->freq . ", interval: " . $this->interval);
+ while(!$done){
+ self::debug(1,"*** Frequency ({$this->freq}) loop pass $loopcount ***");
+ switch($this->freq){
+ case "y":
+ if($eventcount > 0)
+ {
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,0,$this->interval,$this->tzid);
+ self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate));
+ if(!empty($this->byday)){
+ $t = getdate($nextdate);
+ $nextdate = gmmktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]);
+ }
+ self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")");
+ }
+ $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,0,1);
+ break;
+ case "m":
+ if($eventcount > 0)
+ {
+
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid);
+ self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate));
+ }
+ if(count($this->byday) > 0)
+ {
+ $t = getdate($nextdate);
+ if($t["mday"] > 28)
+ {
+ //check for short months when using month by day, make sure we do not overshoot the counter and skip a month
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid);
+ $t2 = getdate($nextdate);
+ if($t2["mday"] < $t["mday"])
+ {
+ // oops, skipped a month, backup to previous month
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$t2["mday"] - $t["mday"],0,$this->tzid);
+ }
+ }
+ $t = getdate($nextdate);
+ $nextdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]);
+ }
+ self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")");
+ $enddate=ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0);
+ break;
+ case "w":
+ if($eventcount == 0)
+ $nextdate=$nextdate;
+ else {
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0,$this->tzid);
+ if(count($this->byday) > 0){
+ $dow = date("w", $nextdate);
+ // move to beginning of week (Sunday)
+ $bow = 0;
+ $diff = $bow - $dow;
+ if($diff > 0)
+ $diff = $diff - 7;
+ $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$diff,0);
+ }
+ self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")");
+ }
+ $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0);
+ break;
+ case "d":
+ $nextdate=($eventcount==0?$nextdate:
+ ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval,0,$this->tzid));
+ $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,1,0);
+ break;
+ }
+
+ $count = $this->byYear($nextdate,$enddate,$rdates,$this->tzid);
+ $eventcount += $count;
+ if($maxdate > 0 && $maxdate < $nextdate)
+ {
+ array_pop($rdates);
+ $done = true;
+ }
+ else if($count == 0 && !$this->maxDates($rdates)){
+ $rdates[] = $nextdate;
+ $eventcount++;
+ }
+ if($this->maxDates($rdates))
+ $done = true;
+
+ $year = date("Y", $nextdate);
+ if($year > _ZAPCAL_MAXYEAR)
+ {
+ $done = true;
+ }
+ $loopcount++;
+ if($loopcount > _ZAPCAL_MAXYEAR){
+ $done = true;
+ throw new Exception("Infinite loop detected in getDates()");
+ }
+ }
+ if($this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){
+ // erase last item
+ array_pop($rdates);
+ }
+ $count1 = count($rdates);
+ $rdates = array_unique($rdates);
+ $count2 = count($rdates);
+ $dups = $count1 - $count2;
+ $excount = 0;
+
+ foreach($this->exdates as $exdate)
+ {
+ if($pos = array_search($exdate,$rdates))
+ {
+ array_splice($rdates,$pos,1);
+ $excount++;
+ }
+ }
+ self::debug(1,"getDates() returned " . count($rdates) . " dates, removing $dups duplicates, $excount exceptions");
+
+
+ if($this->debug >= 2)
+ {
+ self::debug(2,"Recurring Dates:");
+ foreach($rdates as $rdate)
+ {
+ $d = getdate($rdate);
+ self::debug(2,ZDateHelper::toSQLDateTime($rdate) . " " . $d["wday"] );
+ }
+ self::debug(2,"Exception Dates:");
+ foreach($this->exdates as $exdate)
+ {
+ self::debug(2, ZDateHelper::toSQLDateTime($exdate));
+ }
+ //exit;
+ }
+
+ return $rdates;
+ }
+}
diff --git a/src/lib/includes/timezone.php b/src/lib/includes/timezone.php
new file mode 100644
index 0000000..5369f05
--- /dev/null
+++ b/src/lib/includes/timezone.php
@@ -0,0 +1,142 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+// No direct access
+defined('_ZAPCAL') or die( 'Restricted access' );
+
+/**
+ * Zap Calendar Time Zone Helper Class
+ *
+ * Class to help create timezone section of iCalendar file
+ *
+ * @copyright Copyright (C) 2006 - 2016 by Dan Cogliano
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+class ZCTimeZoneHelper {
+
+ /**
+ * getTZNode creates VTIMEZONE section in an iCalendar file
+ *
+ * @param @startyear int start year of date range
+ *
+ * @param @endyear int end year of date range
+ *
+ * @param $tzid string PHP timezone, use underscore for multiple words (i.e. "New_York" for "New York")
+ *
+ * @param $parentnode object iCalendar object where VTIMEZONE will be created
+ *
+ * @return object return VTIMEZONE object
+ */
+ static function getTZNode($startyear, $endyear, $tzid, $parentnode)
+ {
+ $tzmins = array();
+ $tzmaxs = array();
+ if(!array_key_exists($tzid,$tzmins) || $tzmins[$tzid] > $startyear)
+ {
+ $tzmins[$tzid] = $startyear;
+ }
+ if(!array_key_exists($tzid,$tzmaxs) || $tzmaxs[$tzid] < $endyear)
+ {
+ $tzmaxs[$tzid] = $endyear;
+ }
+
+ foreach(array_keys($tzmins) as $tzid)
+ {
+ $tmin = $tzmins[$tzid] - 1;
+ if(array_key_exists($tzid,$tzmaxs))
+ {
+ $tmax = $tzmaxs[$tzid] + 1;
+ }
+ else
+ {
+ $tmax = $tzmins[$tzid] + 1;
+ }
+ $tstart = gmmktime(0,0,0,1,1,$tmin);
+ $tend = gmmktime(23,59,59,12,31,$tmax);
+ $tz = new DateTimeZone($tzid);
+ $transitions = $tz->getTransitions($tstart,$tend);
+ $tzobj = new ZCiCalNode("VTIMEZONE", $parentnode, true);
+ $datanode = new ZCiCalDataNode("TZID:" . str_replace("_"," ",$tzid));
+ $tzobj->data[$datanode->getName()] = $datanode;
+ $count = 0;
+ $lasttransition = null;
+ if(count($transitions) == 1)
+ {
+ // not enough transitions found, probably UTC
+ // lets add fake transition at end for those systems that need it (i.e. Outlook)
+
+ $t2 = array();
+ $t2["isdst"] = $transitions[0]["isdst"];
+ $t2["offset"] = $transitions[0]["offset"];
+ $t2["ts"] = $tstart;
+ $t2["abbr"] = $transitions[0]["abbr"];
+ $transitions[] = $t2;
+ }
+ foreach($transitions as $transition)
+ {
+ $count++;
+ if($count == 1)
+ {
+ $lasttransition = $transition;
+ continue; // skip first item
+ }
+ if($transition["isdst"] == 1)
+ {
+ $tobj = new ZCiCalNode("DAYLIGHT", $tzobj);
+ }
+ else
+ {
+ $tobj = new ZCiCalNode("STANDARD", $tzobj);
+ }
+ //$tzobj->data[$tobj->getName()] == $tobj;
+
+ // convert timestamp to local time zone
+ $ts = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($transition["ts"]),$tzid));
+ $datanode = new ZCiCalDataNode("DTSTART:".ZDateHelper::toICalDateTime($ts));
+ $tobj->data[$datanode->getName()] = $datanode;
+ //echo $ts . " => " . ZDateHelper::toICalDateTime($ts) . "
\n"; exit;
+ $toffset = $lasttransition["offset"];
+ $thours = intval($toffset/60/60);
+ $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60;
+ if($thours < 0)
+ {
+ $offset = sprintf("%03d%02d",$thours,$tmins);
+ }
+ else
+ {
+ $offset = sprintf("+%02d%02d",$thours,$tmins);
+ }
+ $datanode = new ZCiCalDataNode("TZOFFSETFROM:".$offset);
+ $tobj->data[$datanode->getName()] = $datanode;
+
+ $toffset = $transition["offset"];
+ $thours = intval($toffset/60/60);
+ $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60;
+ if($thours < 0)
+ {
+ $offset = sprintf("%03d%02d",$thours,$tmins);
+ }
+ else
+ {
+ $offset = sprintf("+%02d%02d",$thours,$tmins);
+ }
+ $datanode = new ZCiCalDataNode("TZOFFSETTO:".$offset);
+ $tobj->data[$datanode->getName()] = $datanode;
+
+ $datanode = new ZCiCalDataNode("TZNAME:".$transition["abbr"]);
+ $tobj->data[$datanode->getName()] = $datanode;
+
+ $lasttransition = $transition;
+ }
+ }
+ return $tzobj;
+ }
+}
diff --git a/src/lib/zapcallib.php b/src/lib/zapcallib.php
new file mode 100644
index 0000000..bbad6f0
--- /dev/null
+++ b/src/lib/zapcallib.php
@@ -0,0 +1,28 @@
+
+ * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano
+ * @license GNU GPLv3
+ * @link http://icalendar.org/php-library.html
+ */
+
+/**
+ * used by ZapCalLib
+ * @var integer
+ */
+define('_ZAPCAL',1);
+
+if(!defined('_ZAPCAL_BASE'))
+{
+ /**
+ * the base folder of the library
+ * @var string
+ */
+ define('_ZAPCAL_BASE',__DIR__);
+}
+
+require_once(_ZAPCAL_BASE . '/includes/framework.php');
+