├── README.md ├── examples ├── abrahamlincoln.ics ├── parseicalendar.php ├── recurringdate.php ├── simpleevent.php └── timezoneevent.php ├── includes ├── date.php ├── framework.php ├── ical.php ├── index.html ├── recurringdate.php └── timezone.php └── zapcallib.php /README.md: -------------------------------------------------------------------------------- 1 | # Zap Calendar iCalendar Library 2 | 3 | (https://github.com/zcontent/icalendar) 4 | 5 | The Zap Calendar iCalendar Library is a PHP library for supporting the iCalendar (RFC 5545) standard. 6 | 7 | This PHP library is for reading and writing iCalendar formatted feeds and 8 | files. Features of the library include: 9 | 10 | - Read AND write support for iCalendar files 11 | - Object based creation and manipulation of iCalendar files 12 | - Supports expansion of RRULE to a list of repeating dates 13 | - Supports adding timezone info to iCalendar file 14 | 15 | All iCalendar data is stored in a PHP object tree. 16 | This allows any property to be added to the iCalendar feed without 17 | requiring specialized library function calls. 18 | With power comes responsibility. Missing or invalid properties can cause 19 | the resulting iCalendar file to be invalid. Visit [iCalendar.org](http://icalendar.org) to view valid 20 | properties and test your feed using the site's [iCalendar validator tool](http://icalendar.org/validator.html). 21 | 22 | Library API documentation can be found at http://icalendar.org/zapcallibdocs 23 | 24 | See the examples folder for programs that read and write iCalendar 25 | files. At its simpliest, you need to include the library at the top of your program: 26 | 27 | ```php 28 | require_once($path_to_library . "/zapcallib.php"); 29 | ``` 30 | 31 | Create an ical object using the ZCiCal object: 32 | 33 | ```php 34 | $icalobj = new ZCiCal(); 35 | ``` 36 | 37 | Add an event object: 38 | 39 | ```php 40 | $eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode); 41 | ``` 42 | 43 | Add a start and end date to the event: 44 | 45 | ```php 46 | // add start date 47 | $eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime("2020-01-01 12:00:00"))); 48 | 49 | // add end date 50 | $eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime("2020-01-01 13:00:00"))); 51 | ``` 52 | 53 | Write the object in iCalendar format using the export() function call: 54 | 55 | ```php 56 | echo $icalobj->export(); 57 | ``` 58 | 59 | This example will not validate since it is missing some required elements. 60 | Look at the simpleevent.php example for the minimum # of elements 61 | needed for a validated iCalendar file. 62 | 63 | To create a multi-event iCalendar file, simply create multiple event objects. For example: 64 | 65 | ```php 66 | $icalobj = new ZCiCal(); 67 | $eventobj1 = new ZCiCalNode("VEVENT", $icalobj->curnode); 68 | $eventobj1->addNode(new ZCiCalDataNode("SUMMARY:Event 1")); 69 | ... 70 | $eventobj2 = new ZCiCalNode("VEVENT", $icalobj->curnode); 71 | $eventobj2->addNode(new ZCiCalDataNode("SUMMARY:Event 2")); 72 | ... 73 | ``` 74 | 75 | To read an existing iCalendar file/feed, create the ZCiCal object with a string representing the contents of the iCalendar file: 76 | 77 | ```php 78 | $icalobj = new ZCiCal($icalstring); 79 | ``` 80 | 81 | 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: 82 | 83 | ```php 84 | $icalobj = null; 85 | $eventcount = 0; 86 | $maxevents = 500; 87 | do 88 | { 89 | $icalobj = newZCiCal($icalstring, $maxevents, $eventcount); 90 | ... 91 | $eventcount +=$maxevents; 92 | } 93 | while($icalobj->countEvents() >= $eventcount); 94 | ``` 95 | 96 | You can read the events from an imported (or created) iCalendar object in this manner: 97 | 98 | ```php 99 | foreach($icalobj->tree->child as $node) 100 | { 101 | if($node->getName() == "VEVENT") 102 | { 103 | foreach($node->data as $key => $value) 104 | { 105 | if($key == "SUMMARY") 106 | { 107 | echo "event title: " . $value->getValues() . "\n"; 108 | } 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | ## Known Limitations 115 | 116 | - Since the library utilizes objects to read and write iCalendar data, the 117 | size of the iCalendar data is limited to the amount of available memory on the machine. 118 | The ZCiCal() object supports reading a range of events to minimize memory 119 | space. 120 | - The library ignores timezone info when importing files, instead utilizing PHP's timezone 121 | library for calculations (timezones are supported when exporting files). 122 | Imported timezones need to be aliased to a [PHP supported timezone](http://php.net/manual/en/timezones.php). 123 | - At this time, the library does not support the "BYSETPOS" option in RRULE items. 124 | - At this time, the maximum date supported is 2036 to avoid date math issues 125 | with 32 bit systems. 126 | - Repeating events are limited to a maximum of 5,000 dates to avoid memory or infinite loop issues 127 | 128 | -------------------------------------------------------------------------------- /examples/abrahamlincoln.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//ZContent.net//Zap Calendar 1.0//EN 4 | CALSCALE:GREGORIAN 5 | METHOD:PUBLISH 6 | BEGIN:VEVENT 7 | SUMMARY:Abraham Lincoln 8 | UID:2008-04-28-04-15-56-62-@americanhistorycalendar.com 9 | SEQUENCE:0 10 | STATUS:CONFIRMED 11 | TRANSP:TRANSPARENT 12 | RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=12 13 | DTSTART:20080212 14 | DTEND:20080213 15 | DTSTAMP:20150421T141403 16 | CATEGORIES:U.S. Presidents,Civil War People 17 | LOCATION:Hodgenville\, Kentucky 18 | GEO:37.5739497;-85.7399606 19 | DESCRIPTION:Born February 12\, 1809\nSixteenth President (1861-1865)\n\n\n 20 | \nhttp://AmericanHistoryCalendar.com 21 | URL:http://americanhistorycalendar.com/peoplecalendar/1,328-abraham-lincol 22 | n 23 | END:VEVENT 24 | END:VCALENDAR 25 | -------------------------------------------------------------------------------- /examples/parseicalendar.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | /** 13 | * Parse iCalendar Example 14 | * 15 | * Enter an ics filename or URL on the command line, 16 | * or leave blank to parse the default file. 17 | * 18 | */ 19 | 20 | require_once("../zapcallib.php"); 21 | 22 | $icalfile = count($argv) > 1 ? $argv[1] : "abrahamlincoln.ics"; 23 | $icalfeed = file_get_contents($icalfile); 24 | 25 | // create the ical object 26 | $icalobj = new ZCiCal($icalfeed); 27 | 28 | echo "Number of events found: " . $icalobj->countEvents() . "\n"; 29 | 30 | $ecount = 0; 31 | 32 | // read back icalendar data that was just parsed 33 | if(isset($icalobj->tree->child)) 34 | { 35 | foreach($icalobj->tree->child as $node) 36 | { 37 | if($node->getName() == "VEVENT") 38 | { 39 | $ecount++; 40 | echo "Event $ecount:\n"; 41 | foreach($node->data as $key => $value) 42 | { 43 | if(is_array($value)) 44 | { 45 | for($i = 0; $i < count($value); $i++) 46 | { 47 | $p = $value[$i]->getParameters(); 48 | echo " $key: " . $value[$i]->getValues() . "\n"; 49 | } 50 | } 51 | else 52 | { 53 | echo " $key: " . $value->getValues() . "\n"; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/recurringdate.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | /** 13 | * Recurring Date Example 14 | * 15 | * Recurring date examples with RRULE property 16 | * 17 | */ 18 | 19 | require_once("../zapcallib.php"); 20 | 21 | $examples = 22 | array( 23 | array( 24 | "name" => "Abraham Lincon's birthday", 25 | "date" => "2015-02-12", 26 | "rule" => "FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=12" 27 | ), 28 | 29 | array( 30 | "name" => "Start of U.S. Supreme Court Session (1st Monday in October)", 31 | "date" => "2015-10-01", 32 | "rule" => "FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=1MO" 33 | ) 34 | ); 35 | 36 | // Use maxdate to limit # of infinitely repeating events 37 | $maxdate = strtotime("2021-01-01"); 38 | 39 | foreach($examples as $example) 40 | { 41 | echo $example["name"] . ":\n"; 42 | $rd = new ZCRecurringDate($example["rule"],strtotime($example["date"])); 43 | $dates = $rd->getDates($maxdate); 44 | foreach($dates as $d) 45 | { 46 | echo " " . date('l, F j, Y ',$d) . "\n"; 47 | } 48 | echo "\n"; 49 | } 50 | -------------------------------------------------------------------------------- /examples/simpleevent.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | /** 13 | * Simple Event Example 14 | * 15 | * Create a simple iCalendar event 16 | * No time zone specified, so this event will be in UTC time zone 17 | * 18 | */ 19 | 20 | require_once("../zapcallib.php"); 21 | 22 | $title = "Simple Event"; 23 | // date/time is in SQL datetime format 24 | $event_start = "2020-01-01 12:00:00"; 25 | $event_end = "2020-01-01 13:00:00"; 26 | 27 | // create the ical object 28 | $icalobj = new ZCiCal(); 29 | 30 | // create the event within the ical object 31 | $eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode); 32 | 33 | // add title 34 | $eventobj->addNode(new ZCiCalDataNode("SUMMARY:" . $title)); 35 | 36 | // add start date 37 | $eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime($event_start))); 38 | 39 | // add end date 40 | $eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime($event_end))); 41 | 42 | // UID is a required item in VEVENT, create unique string for this event 43 | // Adding your domain to the end is a good way of creating uniqueness 44 | $uid = date('Y-m-d-H-i-s') . "@demo.icalendar.org"; 45 | $eventobj->addNode(new ZCiCalDataNode("UID:" . $uid)); 46 | 47 | // DTSTAMP is a required item in VEVENT 48 | $eventobj->addNode(new ZCiCalDataNode("DTSTAMP:" . ZCiCal::fromSqlDateTime())); 49 | 50 | // Add description 51 | $eventobj->addNode(new ZCiCalDataNode("Description:" . ZCiCal::formatContent( 52 | "This is a simple event, using the Zap Calendar PHP library. " . 53 | "Visit http://icalendar.org to validate icalendar files."))); 54 | 55 | // write iCalendar feed to stdout 56 | echo $icalobj->export(); 57 | 58 | -------------------------------------------------------------------------------- /examples/timezoneevent.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | /** 13 | * Create Event Example With Local Timezone 14 | * 15 | */ 16 | 17 | require_once("../zapcallib.php"); 18 | 19 | $title = "Event in New York timezone"; 20 | // date/time is in SQL datetime format 21 | $event_start = "2020-01-01 12:00:00"; 22 | $event_end = "2020-01-01 13:00:00"; 23 | // timezone must be a supported PHP timezone 24 | // (see http://php.net/manual/en/timezones.php ) 25 | // Note: multi-word timezones must use underscore "_" separator 26 | $tzid = "America/New_York"; 27 | 28 | // create the ical object 29 | $icalobj = new ZCiCal(); 30 | 31 | // Add timezone data 32 | ZCTimeZoneHelper::getTZNode(substr($event_start,0,4),substr($event_end,0,4),$tzid, $icalobj->curnode); 33 | 34 | // create the event within the ical object 35 | $eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode); 36 | 37 | // add title 38 | $eventobj->addNode(new ZCiCalDataNode("SUMMARY:" . $title)); 39 | 40 | // add start date 41 | $eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime($event_start))); 42 | 43 | // add end date 44 | $eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime($event_end))); 45 | 46 | // UID is a required item in VEVENT, create unique string for this event 47 | // Adding your domain to the end is a good way of creating uniqueness 48 | $uid = date('Y-m-d-H-i-s') . "@demo.icalendar.org"; 49 | $eventobj->addNode(new ZCiCalDataNode("UID:" . $uid)); 50 | 51 | // DTSTAMP is a required item in VEVENT 52 | $eventobj->addNode(new ZCiCalDataNode("DTSTAMP:" . ZCiCal::fromSqlDateTime())); 53 | 54 | // write iCalendar feed to stdout 55 | echo $icalobj->export(); 56 | 57 | -------------------------------------------------------------------------------- /includes/date.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | // No direct access 13 | defined('_ZAPCAL') or die( 'Restricted access' ); 14 | 15 | /** 16 | * Zap Calendar Date Helper Class 17 | * 18 | * Helper class for various date functions 19 | */ 20 | class ZDateHelper { 21 | 22 | /** 23 | * Find the number of days in a month 24 | * 25 | * @param int $month Month is between 1 and 12 inclusive 26 | * 27 | * @param int $year is between 1 and 32767 inclusive 28 | * 29 | * @return int 30 | */ 31 | static function DayInMonth($month, $year) { 32 | $daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 33 | if ($month != 2) return $daysInMonth[$month - 1]; 34 | return (checkdate($month, 29, $year)) ? 29 : 28; 35 | } 36 | 37 | /** 38 | * Is given date today? 39 | * 40 | * @param int $date date in Unix timestamp format 41 | * 42 | * @param int $tzid PHP recognized timezone (default is UTC) 43 | * 44 | * @return bool 45 | */ 46 | static function isToday($date, $tzid = "UTC") { 47 | $dtz = new DateTimeZone($tzid); 48 | $dt = new DateTime("now", $dtz); 49 | $now = time() + $dtz->getOffset($dt); 50 | return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now); 51 | } 52 | 53 | /** 54 | * Is given date before today? 55 | * 56 | * @param int $date date in Unix timestamp format 57 | * 58 | * @param int $tzid PHP recognized timezone (default is UTC) 59 | * 60 | * @return bool 61 | */ 62 | static function isBeforeToday($date, $tzid = "UTC"){ 63 | $dtz = new DateTimeZone($tzid); 64 | $dt = new DateTime("now", $dtz); 65 | $now = time() + $dtz->getOffset($dt); 66 | return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) > 67 | mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); 68 | } 69 | 70 | /** 71 | * Is given date after today? 72 | * 73 | * @param int $date date in Unix timestamp format 74 | * 75 | * @param int $tzid PHP recognized timezone (default is UTC) 76 | * 77 | * @return bool 78 | */ 79 | static function isAfterToday($date, $tzid = "UTC"){ 80 | $dtz = new DateTimeZone($tzid); 81 | $dt = new DateTime("now", $dtz); 82 | $now = time() + $dtz->getOffset($dt); 83 | return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) < 84 | mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); 85 | } 86 | 87 | /** 88 | * Is given date tomorrow? 89 | * 90 | * @param int $date date in Unix timestamp format 91 | * 92 | * @param int $tzid PHP recognized timezone (default is UTC) 93 | * 94 | * @return bool 95 | */ 96 | static function isTomorrow($date, $tzid = "UTC") { 97 | $dtz = new DateTimeZone($tzid); 98 | $dt = new DateTime("now", $dtz); 99 | $now = time() + $dtz->getOffset($dt); 100 | return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now + 60 * 60 * 24); 101 | } 102 | 103 | /** 104 | * Is given date in the future? 105 | * 106 | * This routine differs from isAfterToday() in that isFuture() will 107 | * return true for date-time values later in the same day. 108 | * 109 | * @param int $date date in Unix timestamp format 110 | * 111 | * @param int $tzid PHP recognized timezone (default is UTC) 112 | * 113 | * @return bool 114 | */ 115 | static function isFuture($date, $tzid = "UTC"){ 116 | $dtz = new DateTimeZone($tzid); 117 | $dt = new DateTime("now", $dtz); 118 | $now = time() + $dtz->getOffset($dt); 119 | return $date > $now; 120 | } 121 | 122 | /** 123 | * Is given date in the past? 124 | * 125 | * This routine differs from isBeforeToday() in that isPast() will 126 | * return true for date-time values earlier in the same day. 127 | * 128 | * @param int $date date in Unix timestamp format 129 | * 130 | * @param int $tzid PHP recognized timezone (default is UTC) 131 | * 132 | * @return bool 133 | */ 134 | static function isPast($date, $tzid = "UTC") { 135 | $dtz = new DateTimeZone($tzid); 136 | $dt = new DateTime("now", $dtz); 137 | $now = time() + $dtz->getOffset($dt); 138 | return $date < $now; 139 | } 140 | 141 | /** 142 | * Return current Unix timestamp in local timezone 143 | * 144 | * @param string $tzid PHP recognized timezone 145 | * 146 | * @return int 147 | */ 148 | static function now($tzid = "UTC"){ 149 | $dtz = new DateTimeZone($tzid); 150 | $dt = new DateTime("now", $dtz); 151 | $now = time() + $dtz->getOffset($dt); 152 | return $now; 153 | } 154 | 155 | /** 156 | * Is given date fall on a weekend? 157 | * 158 | * @param int $date Unix timestamp 159 | * 160 | * @return bool 161 | */ 162 | static function isWeekend($date) { 163 | $dow = gmdate('w',$date); 164 | return $dow == 0 || $dow == 6; 165 | } 166 | 167 | /** 168 | * Format Unix timestamp to SQL date-time 169 | * 170 | * @param int $t Unix timestamp 171 | * 172 | * @return string 173 | */ 174 | static function toSqlDateTime($t = 0) 175 | { 176 | date_default_timezone_set('GMT'); 177 | if($t == 0) 178 | return gmdate('Y-m-d H:i:s',self::now()); 179 | return gmdate('Y-m-d H:i:s', $t); 180 | } 181 | 182 | /** 183 | * Format Unix timestamp to SQL date 184 | * 185 | * @param int $t Unix timestamp 186 | * 187 | * @return string 188 | */ 189 | static function toSqlDate($t = 0) 190 | { 191 | date_default_timezone_set('GMT'); 192 | if($t == 0) 193 | return gmdate('Y-m-d',self::now()); 194 | return gmdate('Y-m-d', $t); 195 | } 196 | 197 | /** 198 | * Format iCal date-time string to Unix timestamp 199 | * 200 | * @param string $datetime in iCal time format ( YYYYMMDD or YYYYMMDDTHHMMSS or YYYYMMDDTHHMMSSZ ) 201 | * 202 | * @return int Unix timestamp 203 | */ 204 | static function fromiCaltoUnixDateTime($datetime) { 205 | // first check format 206 | $formats = array(); 207 | $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/"; 208 | $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]/"; 209 | $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/"; 210 | $ok = false; 211 | foreach($formats as $format){ 212 | if(preg_match($format,$datetime)){ 213 | $ok = true; 214 | break; 215 | } 216 | } 217 | if(!$ok) 218 | return null; 219 | $year = substr($datetime,0,4); 220 | $month = substr($datetime,4,2); 221 | $day = substr($datetime,6,2); 222 | $hour = 0; 223 | $minute = 0; 224 | $second = 0; 225 | if(strlen($datetime) > 8 && $datetime[8] == "T") { 226 | $hour = substr($datetime,9,2); 227 | $minute = substr($datetime,11,2); 228 | $second = substr($datetime,13,2); 229 | } 230 | return gmmktime($hour, $minute, $second, $month, $day, $year); 231 | } 232 | 233 | /** 234 | * Format Unix timestamp to iCal date-time string 235 | * 236 | * @param int $datetime Unix timestamp 237 | * 238 | * @return string 239 | */ 240 | static function fromUnixDateTimetoiCal($datetime){ 241 | date_default_timezone_set('GMT'); 242 | return gmdate("Ymd\THis",$datetime); 243 | } 244 | 245 | /** 246 | * Convert iCal duration string to # of seconds 247 | * 248 | * @param string $duration iCal duration string 249 | * 250 | * return int 251 | */ 252 | static function iCalDurationtoSeconds($duration) { 253 | $secs = 0; 254 | if($duration[0] == "P") { 255 | $duration = str_replace(array("H","M","S","T","D","W","P"),array("H,","M,","S,","","D,","W,",""),$duration); 256 | $dur2 = explode(",",$duration); 257 | foreach($dur2 as $dur){ 258 | $val=intval($dur); 259 | if(strlen($dur) > 0){ 260 | switch($dur[strlen($dur) - 1]) { 261 | case "H": 262 | $secs += 60*60 * $val; 263 | break; 264 | case "M": 265 | $secs += 60 * $val; 266 | break; 267 | case "S": 268 | $secs += $val; 269 | break; 270 | case "D": 271 | $secs += 60*60*24 * $val; 272 | break; 273 | case "W": 274 | $secs += 60*60*24*7 * $val; 275 | break; 276 | } 277 | } 278 | } 279 | } 280 | return $secs; 281 | } 282 | 283 | /** 284 | * Check if day falls within date range 285 | * 286 | * @param int $daystart start of day in Unix timestamp format 287 | * 288 | * @param int $begin Unix timestamp of starting date range 289 | * 290 | * @param int $end Unix timestamp of end date range 291 | * 292 | * @return bool 293 | */ 294 | static function inDay($daystart, $begin, $end) 295 | { 296 | //$dayend = $daystart + 60*60*24 - 60; 297 | // add 1 day to determine end of day 298 | // don't use 24 hours, since twice a year DST Sundays are 23 hours and 25 hours in length 299 | // adding 1 day takes this into account 300 | $dayend = self::addDate($daystart, 0,0,0,0,1,0); 301 | 302 | $end = max($begin, $end); // $end can't be less than $begin 303 | $inday = 304 | ($daystart <= $begin && $begin < $dayend) 305 | ||($daystart < $end && $end < $dayend) 306 | ||($begin <= $daystart && $end > $dayend) 307 | ; 308 | return $inday; 309 | } 310 | 311 | /** 312 | * Convert SQL date or date-time to Unix timestamp 313 | * 314 | * @param string $datetime SQL date or date-time 315 | * 316 | * @return int Unix date-time timestamp 317 | */ 318 | static function toUnixDate($datetime) 319 | { 320 | $year = substr($datetime,0,4); 321 | $month = substr($datetime,5,2); 322 | $day = substr($datetime,8,2); 323 | 324 | return mktime(0, 0, 0, $month, $day, $year); 325 | } 326 | 327 | /** 328 | * Convert SQL date or date-time to Unix date timestamp 329 | * 330 | * @param string $datetime SQL date or date-time 331 | * 332 | * @return int Unix timestamp 333 | */ 334 | static function toUnixDateTime($datetime) 335 | { 336 | // convert to absolute dates if neccessary 337 | $datetime = self::getAbsDate($datetime); 338 | $year = substr($datetime,0,4); 339 | $month = substr($datetime,5,2); 340 | $day = substr($datetime,8,2); 341 | $hour = 0; 342 | $minute = 0; 343 | $second = 0; 344 | if(strlen($datetime) > 10) { 345 | $hour = substr($datetime,11,2); 346 | $minute = substr($datetime,14,2); 347 | $second = substr($datetime,17,2); 348 | } 349 | return gmmktime($hour, $minute, $second, $month, $day, $year); 350 | } 351 | 352 | /** 353 | * Date math: add or substract from current date to get a new date 354 | * 355 | * @param int $date date to add or subtract from 356 | * 357 | * @param int $hour add or subtract hours from date 358 | * 359 | * @param int $min add or subtract minutes from date 360 | * 361 | * @param int $sec add or subtract seconds from date 362 | * 363 | * @param int $month add or subtract months from date 364 | * 365 | * @param int $day add or subtract days from date 366 | * 367 | * @param int $year add or subtract years from date 368 | * 369 | * @param string $tzid PHP recognized timezone (default is UTC) 370 | */ 371 | static function addDate($date, $hour, $min, $sec, $month, $day, $year, $tzid = "UTC") { 372 | date_default_timezone_set($tzid); 373 | $sqldate = self::toSQLDateTime($date); 374 | $tdate = array(); 375 | $tdate["year"] = substr($sqldate,0,4); 376 | $tdate["mon"] = substr($sqldate,5,2); 377 | $tdate["mday"] = substr($sqldate,8,2); 378 | $tdate["hours"] = substr($sqldate,11,2); 379 | $tdate["minutes"] = substr($sqldate,14,2); 380 | $tdate["seconds"] = substr($sqldate,17,2); 381 | $newdate=mktime($tdate["hours"] + $hour, $tdate["minutes"] + $min, $tdate["seconds"] + $sec, $tdate["mon"] + $month, $tdate["mday"] + $day, $tdate["year"] + $year); 382 | date_default_timezone_set("UTC"); 383 | //echo self::toSQLDateTime($date) . " => " . self::toSQLDateTime($newdate) . " ($hour:$min:$sec $month/$day/$year)
\n"; 384 | return $newdate; 385 | } 386 | 387 | /** 388 | * Date math: get date from week and day in specifiec month 389 | * 390 | * This routine finds actual dates for the second Tuesday of the month, last Friday of the month, etc. 391 | * For second Tuesday, use $week = 1, $wday = 2 392 | * for last Friday, use $week = -1, $wday = 5 393 | * 394 | * @param int $date Unix timestamp 395 | * 396 | * @param int $week week number, 0 is first week, -1 is last 397 | * 398 | * @param int $wday day of week, 0 is Sunday, 6 is Saturday 399 | * 400 | * @param string $tzid PHP supported timezone 401 | * 402 | * @return int Unix timestamp 403 | */ 404 | static function getDateFromDay($date, $week, $wday,$tzid="UTC") { 405 | //echo "getDateFromDay(" . self::toSqlDateTime($date) . ",$week,$wday)
\n"; 406 | // determine first day in month 407 | $tdate = getdate($date); 408 | $monthbegin = gmmktime(0,0,0, $tdate["mon"],1,$tdate["year"]); 409 | $monthend = self::addDate($monthbegin, 0,0,0,1,-1,0,$tzid); // add 1 month and subtract 1 day 410 | $day = self::addDate($date,0,0,0,0,1 - $tdate["mday"],0,$tzid); 411 | $month = array(array()); 412 | while($day <= $monthend) { 413 | $tdate=getdate($day); 414 | $month[$tdate["wday"]][]=$day; 415 | //echo self::toSQLDateTime($day) . "
\n"; 416 | $day = self::addDate($day, 0,0,0,0,1,0,$tzid); // add 1 day 417 | } 418 | $dayinmonth=0; 419 | if($week >= 0) 420 | $dayinmonth = $month[$wday][$week]; 421 | else 422 | $dayinmonth = $month[$wday][count($month[$wday]) - 1]; 423 | //echo "return " . self::toSQLDateTime($dayinmonth); 424 | //exit; 425 | return $dayinmonth; 426 | } 427 | 428 | /** 429 | * Convert UTC date-time to local date-time 430 | * 431 | * @param string $sqldate SQL date-time string 432 | * 433 | * @param string $tzid PHP recognized timezone (default is "UTC") 434 | * 435 | * @return string SQL date-time string 436 | */ 437 | static function toLocalDateTime($sqldate, $tzid = "UTC" ){ 438 | try 439 | { 440 | $timezone = new DateTimeZone($tzid); 441 | } 442 | catch(Exception $e) 443 | { 444 | // bad time zone specified 445 | return $sqldate; 446 | } 447 | $udate = self::toUnixDateTime($sqldate); 448 | $daydatetime = new DateTime("@" . $udate); 449 | $tzoffset = $timezone->getOffset($daydatetime); 450 | return self::toSqlDateTime($udate + $tzoffset); 451 | } 452 | 453 | /** 454 | * Convert local date-time to UTC date-time 455 | * 456 | * @param string $sqldate SQL date-time string 457 | * 458 | * @param string $tzid PHP recognized timezone (default is "UTC") 459 | * 460 | * @return string SQL date-time string 461 | */ 462 | static function toUTCDateTime($sqldate, $tzid = "UTC" ){ 463 | 464 | date_default_timezone_set("UTC"); 465 | try 466 | { 467 | $date = new DateTime($sqldate, $tzid); 468 | } 469 | catch(Exception $e) 470 | { 471 | // bad time zone specified 472 | return $sqldate; 473 | } 474 | $offset = $date->getOffsetFromGMT(); 475 | if($offset >= 0) 476 | $date->sub(new DateInterval("PT".$offset."S")); 477 | else 478 | $date->add(new DateInterval("PT".abs($offset)."S")); 479 | return $date->toSql(true); 480 | } 481 | 482 | /** 483 | * Convert from a relative date to an absolute date 484 | * 485 | * Examples of relative dates are "-2y" for 2 years ago, "18m" 486 | * for 18 months after today. Relative date uses "y", "m" and "d" for 487 | * year, month and day. Relative date can be combined into comma 488 | * separated list, i.e., "-1y,-1d" for 1 year and 1 day ago. 489 | * 490 | * @param string $date relative date string (i.e. "1y" for 1 year from today) 491 | * 492 | * @param string $rdate reference date, or blank for current date (in SQL date-time format) 493 | * 494 | * @return string in SQL date-time format 495 | */ 496 | static function getAbsDate($date,$rdate = ""){ 497 | if(str_replace(array("y","m","d","h","n"),"",strtolower($date)) != strtolower($date)){ 498 | date_default_timezone_set("UTC"); 499 | if($rdate == "") 500 | $udate = time(); 501 | else 502 | $udate = self::toUnixDateTime($rdate); 503 | $values=explode(",",strtolower($date)); 504 | $y = 0; 505 | $m = 0; 506 | $d = 0; 507 | $h = 0; 508 | $n = 0; 509 | foreach($values as $value){ 510 | $rtype = substr($value,strlen($value)-1); 511 | $rvalue = intval(substr($value,0,strlen($value) - 1)); 512 | switch($rtype){ 513 | case 'y': 514 | $y = $rvalue; 515 | break; 516 | case 'm': 517 | $m = $rvalue; 518 | break; 519 | case 'd': 520 | $d = $rvalue; 521 | break; 522 | case 'h': 523 | $h = $rvalue; 524 | break; 525 | case 'n': 526 | $n = $rvalue; 527 | break; 528 | } 529 | // for "-" values, move to start of day , otherwise, move to end of day 530 | if($rvalue[0] == '-') 531 | $udate = mktime(0,0,0,date('m',$udate),date('d',$udate),date('Y',$udate)); 532 | else 533 | $udate = mktime(0,-1,0,date('m',$udate),date('d',$udate)+1,date('Y',$udate)); 534 | $udate = self::addDate($udate,$h,$n,0,$m,$d,$y); 535 | } 536 | $date = self::toSqlDateTime($udate); 537 | } 538 | return $date; 539 | } 540 | 541 | /** 542 | * Format Unix timestamp to iCal date-time format 543 | * 544 | * @param int $datetime Unix timestamp 545 | * 546 | * @return string iCal date-time string 547 | */ 548 | static function toiCalDateTime($datetime = null){ 549 | date_default_timezone_set('UTC'); 550 | if($datetime == null) 551 | $datetime = time(); 552 | return gmdate("Ymd\THis",$datetime); 553 | } 554 | 555 | /** 556 | * Format Unix timestamp to iCal date format 557 | * 558 | * @param int $datetime Unix timestamp 559 | * 560 | * @return string iCal date-time string 561 | */ 562 | static function toiCalDate($datetime = null){ 563 | date_default_timezone_set('UTC'); 564 | if($datetime == null) 565 | $datetime = time(); 566 | return gmdate("Ymd",$datetime); 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /includes/framework.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | // No direct access 13 | defined('_ZAPCAL') or die( 'Restricted access' ); 14 | 15 | /** 16 | * set MAXYEAR to 2036 for 32 bit systems, can be higher for 64 bit systems 17 | * 18 | * @var integer 19 | */ 20 | define('_ZAPCAL_MAXYEAR', 2036); 21 | 22 | /** 23 | * set MAXREVENTS to maximum # of repeating events 24 | * 25 | * @var integer 26 | */ 27 | define('_ZAPCAL_MAXREVENTS', 5000); 28 | 29 | require_once(_ZAPCAL_BASE . '/includes/date.php'); 30 | require_once(_ZAPCAL_BASE . '/includes/recurringdate.php'); 31 | require_once(_ZAPCAL_BASE . '/includes/ical.php'); 32 | require_once(_ZAPCAL_BASE . '/includes/timezone.php'); 33 | -------------------------------------------------------------------------------- /includes/ical.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | // No direct access 13 | defined('_ZAPCAL') or die( 'Restricted access' ); 14 | 15 | /** 16 | * Object for storing an unfolded iCalendar line 17 | * 18 | * The ZCiCalDataNode class contains data from an unfolded iCalendar line 19 | * 20 | */ 21 | class ZCiCalDataNode { 22 | /** 23 | * The name of the node 24 | * 25 | * @var string 26 | */ 27 | var $name = ""; 28 | 29 | /** 30 | * Node parameters (before the colon ":") 31 | * 32 | * @var array 33 | */ 34 | var $parameter=array(); 35 | 36 | /** 37 | * Node values (after the colon ":") 38 | * 39 | * @var array 40 | */ 41 | var $value=array(); 42 | 43 | /** 44 | * Create an object from an unfolded iCalendar line 45 | * 46 | * @param string $line An unfolded iCalendar line 47 | * 48 | * @return void 49 | * 50 | */ 51 | function __construct( $line ) { 52 | //echo "ZCiCalDataNode($line)
\n"; 53 | //separate line into parameters and value 54 | // look for colon separating name or parameter and value 55 | // first change any escaped colons temporarily to make it easier 56 | $tline = str_replace("\\:", "`~", $line); 57 | // see if first colon is inside a quoted string 58 | $i = 0; 59 | $datafind = false; 60 | $inquotes = false; 61 | while(!$datafind && ($i < strlen($tline))) { 62 | //echo "$i: " . $tline[$i] . ", ord() = " . ord($tline[$i]) . "
\n"; 63 | if(!$inquotes && $tline[$i] == ':') 64 | $datafind=true; 65 | else{ 66 | $i += 1; 67 | if(substr($tline,$i,1) == '"') 68 | $inquotes = !$inquotes; 69 | } 70 | } 71 | if($datafind){ 72 | $value = str_replace("`~","\\:",substr($line,$i+1)); 73 | // fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal) 74 | $value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value); 75 | $tvalue = str_replace("\\,", "`~", $value); 76 | //echo "value: " . $tvalue . "
\n"; 77 | $tvalue = explode(",",$tvalue); 78 | $value = str_replace("`~","\\,",$tvalue); 79 | $this->value = $value; 80 | } 81 | 82 | $parameter = trim(substr($line,0,$i)); 83 | 84 | $parameter = str_replace("\\;", "`~", $parameter); 85 | $parameters = explode(";", $parameter); 86 | $parameters = str_replace("`~", "\\;", $parameters); 87 | $this->name = array_shift($parameters); 88 | foreach($parameters as $parameter){ 89 | $pos = strpos($parameter,"="); 90 | if($pos > 0){ 91 | $param = substr($parameter,0,$pos); 92 | $paramvalue = substr($parameter,$pos+1); 93 | $tvalue = str_replace("\\,", "`~", $paramvalue); 94 | //$tvalue = explode(",",$tvalue); 95 | $paramvalue = str_replace("`~","\\,",$tvalue); 96 | $this->parameter[strtolower($param)] = $paramvalue; 97 | //$this->paramvalue[] = $paramvalue; 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * getName() 104 | * 105 | * Return the name of the object 106 | * 107 | * @return string 108 | */ 109 | function getName(){ 110 | return $this->name; 111 | } 112 | 113 | /** 114 | * Get $ith parameter from array 115 | * @param int $i 116 | * 117 | * @return var 118 | */ 119 | function getParameter($i){ 120 | return $this->parameter[$i]; 121 | } 122 | 123 | /** 124 | * Get parameter array 125 | * 126 | * @return array 127 | */ 128 | function getParameters(){ 129 | return $this->parameter; 130 | } 131 | 132 | /** 133 | * Get comma separated values 134 | * 135 | * @return string 136 | */ 137 | function getValues(){ 138 | return implode(",",$this->value); 139 | } 140 | } 141 | 142 | /** 143 | * Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects) 144 | * 145 | * @property object $parentnode Parent of this node 146 | * 147 | * @property array $child Array of children for this node 148 | * 149 | * @property data $data Array of data for this node 150 | * 151 | * @property object $next Next sibling of this node 152 | * 153 | * @property object $prev Previous sibling of this node 154 | */ 155 | 156 | class ZCiCalNode { 157 | /** 158 | * The name of the node 159 | * 160 | * @var string 161 | */ 162 | var $name=""; 163 | 164 | /** 165 | * The parent of this node 166 | * 167 | * @var object 168 | */ 169 | var $parentnode=null; 170 | 171 | /** 172 | * Array of children for this node 173 | * 174 | * @var array 175 | */ 176 | var $child= array(); 177 | 178 | /** 179 | * Array of $data for this node 180 | * 181 | * @var array 182 | */ 183 | var $data= array(); 184 | 185 | 186 | /** 187 | * Next sibling of this node 188 | * 189 | * @var object 190 | */ 191 | var $next=null; 192 | 193 | /** 194 | * Previous sibling of this node 195 | * 196 | * @var object 197 | */ 198 | var $prev=null; 199 | 200 | /** 201 | * Create ZCiCalNode 202 | * 203 | * @param string $_name Name of node 204 | * 205 | * @param object $_parent Parent node for this node 206 | * 207 | * @param bool $first Is this the first child for this parent? 208 | */ 209 | function __construct( $_name, & $_parent, $first = false) { 210 | $this->name = $_name; 211 | $this->parentnode = $_parent; 212 | if($_parent != null){ 213 | if(count($this->parentnode->child) > 0) { 214 | if($first) 215 | { 216 | $first = & $this->parentnode->child[0]; 217 | $first->prev = & $this; 218 | $this->next = & $first; 219 | } 220 | else 221 | { 222 | $prev =& $this->parentnode->child[count($this->parentnode->child)-1]; 223 | $prev->next =& $this; 224 | $this->prev =& $prev; 225 | } 226 | } 227 | if($first) 228 | { 229 | array_unshift($this->parentnode->child, $this); 230 | } 231 | else 232 | { 233 | $this->parentnode->child[] =& $this; 234 | } 235 | } 236 | /* 237 | echo "creating " . $this->getName(); 238 | if($_parent != null) 239 | echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child); 240 | echo "
"; 241 | */ 242 | } 243 | 244 | /** 245 | * Return the name of the object 246 | * 247 | * @return string 248 | */ 249 | function getName() { 250 | return $this->name; 251 | } 252 | 253 | /** 254 | * Add node to list 255 | * 256 | * @param object $node 257 | * 258 | */ 259 | function addNode($node) { 260 | if(array_key_exists($node->getName(), $this->data)) 261 | { 262 | if(!is_array($this->data[$node->getName()])) 263 | { 264 | $this->data[$node->getName()] = array($this->data[$node->getName()]); 265 | } 266 | $this->data[$node->getName()][] = $node; 267 | } 268 | else 269 | { 270 | $this->data[$node->getName()] = $node; 271 | } 272 | } 273 | 274 | /** 275 | * Get Attribute 276 | * 277 | * @param int $i array id of attribute to get 278 | * 279 | * @return string 280 | */ 281 | function getAttrib($i) { 282 | return $this->attrib[$i]; 283 | } 284 | 285 | /** 286 | * Set Attribute 287 | * 288 | * @param string $value value of attribute to set 289 | * 290 | */ 291 | function setAttrib($value) { 292 | $this->attrib[] = $value; 293 | } 294 | 295 | /** 296 | * Get the parent object of this object 297 | * 298 | * @return object parent of this object 299 | */ 300 | function &getParent() { 301 | return $this->parentnode; 302 | } 303 | 304 | /** 305 | * Get the first child of this object 306 | * 307 | * @return object The first child 308 | */ 309 | function &getFirstChild(){ 310 | static $nullguard = null; 311 | if(count($this->child) > 0) { 312 | //echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "
"; 313 | return $this->child[0]; 314 | } 315 | else 316 | return $nullguard; 317 | } 318 | 319 | /** 320 | * Print object tree in HTML for debugging purposes 321 | * 322 | * @param object $node select part of tree to print, or leave blank for full tree 323 | * 324 | * @param int $level Level of recursion (usually leave this blank) 325 | * 326 | * @return string - HTML formatted display of object tree 327 | */ 328 | function printTree(& $node=null, $level=1){ 329 | $level += 1; 330 | $html = ""; 331 | if($node == null) 332 | $node = $this->parentnode; 333 | if($level > 5) 334 | { 335 | die("levels nested too deep
\n"); 336 | //return; 337 | } 338 | for($i = 0 ; $i < $level; $i ++) 339 | $html .= "+"; 340 | $html .= $node->getName() . "
\n"; 341 | foreach ($node->child as $c){ 342 | $html .= $node->printTree($c,$level); 343 | } 344 | $level -= 1; 345 | return $html; 346 | } 347 | 348 | /** 349 | * export tree to icalendar format 350 | * 351 | * @param object $node Top level node to export 352 | * 353 | * @param int $level Level of recursion (usually leave this blank) 354 | * 355 | * @return string iCalendar formatted output 356 | */ 357 | function export(& $node=null, $level=0){ 358 | $txtstr = ""; 359 | if($node == null) 360 | $node = $this; 361 | if($level > 5) 362 | { 363 | //die("levels nested too deep
\n"); 364 | throw new Exception("levels nested too deep"); 365 | } 366 | $txtstr .= "BEGIN:" . $node->getName() . "\r\n"; 367 | if(property_exists($node,"data")) 368 | foreach ($node->data as $d){ 369 | if(is_array($d)) 370 | { 371 | foreach ($d as $c) 372 | { 373 | //$txtstr .= $node->export($c,$level + 1); 374 | $p = ""; 375 | $params = @$c->getParameters(); 376 | if(count($params) > 0) 377 | { 378 | foreach($params as $key => $value){ 379 | $p .= ";" . strtoupper($key) . "=" . $value; 380 | } 381 | } 382 | $txtstr .= $this->printDataLine($c, $p); 383 | } 384 | } 385 | else 386 | { 387 | $p = ""; 388 | $params = @$d->getParameters(); 389 | if(count($params) > 0) 390 | { 391 | foreach($params as $key => $value){ 392 | $p .= ";" . strtoupper($key) . "=" . $value; 393 | } 394 | } 395 | $txtstr .= $this->printDataLine($d, $p); 396 | /* 397 | $values = $d->getValues(); 398 | // don't think we need this, Sunbird does not like it in the EXDATE field 399 | //$values = str_replace(",", "\\,", $values); 400 | 401 | $line = $d->getName() . $p . ":" . $values; 402 | $line = str_replace(array("
","
","
","
0) { 408 | $linewidth = ($linecount == 0? 75 : 74); 409 | $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); 410 | if($linecount > 0) 411 | $txtstr .= " "; 412 | $txtstr .= substr($line,0,$linesize) . "\r\n"; 413 | $linecount += 1; 414 | $line = substr($line,$linewidth); 415 | } 416 | */ 417 | } 418 | //echo $line . "\n"; 419 | } 420 | if(property_exists($node,"child")) 421 | foreach ($node->child as $c){ 422 | $txtstr .= $node->export($c,$level + 1); 423 | } 424 | $txtstr .= "END:" . $node->getName() . "\r\n"; 425 | return $txtstr; 426 | } 427 | 428 | /** 429 | * print an attribute line 430 | 431 | * @param object $d attributes 432 | * @param object $p properties 433 | * 434 | */ 435 | function printDataLine($d, $p) 436 | { 437 | $txtstr = ""; 438 | 439 | $values = $d->getValues(); 440 | // don't think we need this, Sunbird does not like it in the EXDATE field 441 | //$values = str_replace(",", "\\,", $values); 442 | 443 | $line = $d->getName() . $p . ":" . $values; 444 | $line = str_replace(array("
","
","
","
0) { 450 | $linewidth = ($linecount == 0? 75 : 74); 451 | $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); 452 | if($linecount > 0) 453 | $txtstr .= " "; 454 | $txtstr .= substr($line,0,$linesize) . "\r\n"; 455 | $linecount += 1; 456 | $line = substr($line,$linewidth); 457 | } 458 | return $txtstr; 459 | } 460 | } 461 | 462 | /** 463 | * 464 | * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. 465 | * 466 | */ 467 | class ZCiCal { 468 | /** 469 | * The root node of the object tree 470 | * 471 | * @var object 472 | */ 473 | var $tree=null; 474 | /** 475 | * The most recently created node in the tree 476 | * 477 | * @var object 478 | */ 479 | var $curnode=null; 480 | 481 | /** 482 | * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. 483 | * 484 | * use maxevents and startevent to read events in multiple passes (to save memory) 485 | * 486 | * @param string $data icalendar feed string (empty if creating new feed) 487 | * 488 | * @param int $maxevents maximum # of events to read 489 | * 490 | * @param int $startevent starting event to read 491 | * 492 | * @return void 493 | * 494 | * 495 | */ 496 | function __construct($data = "", $maxevents = 1000000, $startevent = 0) { 497 | 498 | if($data != ""){ 499 | // unfold lines 500 | // first change all eol chars to "\n" 501 | $data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data); 502 | // now unfold lines 503 | //$data = str_replace(array("\n ", "\n "),"!?", $data); 504 | $data = str_replace(array("\n ", "\n "),"", $data); 505 | // replace special iCal chars 506 | $data = str_replace(array("\\\\","\,"),array("\\",","), $data); 507 | 508 | // parse each line 509 | $lines = explode("\n", $data); 510 | 511 | $linecount = 0; 512 | $eventcount = 0; 513 | $eventpos = 0; 514 | foreach($lines as $line) { 515 | //$line = str_replace("!?", "\n", $line); // add nl back into descriptions 516 | // echo ($linecount + 1) . ": " . $line . "
"; 517 | if(substr($line,0,6) == "BEGIN:") { 518 | // start new object 519 | $name = substr($line,6); 520 | if($name == "VEVENT") 521 | { 522 | if($eventcount < $maxevents && $eventpos >= $startevent) 523 | { 524 | $this->curnode = new ZCiCalNode($name, $this->curnode); 525 | if($this->tree == null) 526 | $this->tree = $this->curnode; 527 | } 528 | } 529 | else 530 | { 531 | $this->curnode = new ZCiCalNode($name, $this->curnode); 532 | if($this->tree == null) 533 | $this->tree = $this->curnode; 534 | } 535 | //echo "new node: " . $this->curnode->name . "
\n"; 536 | /* 537 | if($this->curnode->getParent() != null) 538 | echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "
"; 539 | else 540 | echo "parent of " . $this->curnode->getName() . " is null
"; 541 | */ 542 | } 543 | else if(substr($line,0,4) == "END:") { 544 | $name = substr($line,4); 545 | if($name == "VEVENT") 546 | { 547 | if($eventcount < $maxevents && $eventpos >= $startevent) 548 | { 549 | $eventcount++; 550 | if($this->curnode->getName() != $name) { 551 | //panic, mismatch in iCal structure 552 | //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); 553 | throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); 554 | } 555 | if($this->curnode->getParent() != null) { 556 | //echo "moving up from " . $this->curnode->getName() ; 557 | $this->curnode = & $this->curnode->getParent(); 558 | //echo " to " . $this->curnode->getName() . "
"; 559 | //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; 560 | } 561 | } 562 | $eventpos++; 563 | } 564 | else 565 | { 566 | if($this->curnode->getName() != $name) { 567 | //panic, mismatch in iCal structure 568 | //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); 569 | throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); 570 | } 571 | if($this->curnode->getParent() != null) { 572 | //echo "moving up from " . $this->curnode->getName() ; 573 | $this->curnode = & $this->curnode->getParent(); 574 | //echo " to " . $this->curnode->getName() . "
"; 575 | //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; 576 | } 577 | } 578 | } 579 | else { 580 | $datanode = new ZCiCalDataNode($line); 581 | if($this->curnode->getName() == "VEVENT") 582 | { 583 | if($eventcount < $maxevents && $eventpos >= $startevent) 584 | { 585 | if($datanode->getName() == "EXDATE") 586 | { 587 | if(!array_key_exists($datanode->getName(),$this->curnode->data)) 588 | { 589 | $this->curnode->data[$datanode->getName()] = $datanode; 590 | } 591 | else 592 | { 593 | $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; 594 | } 595 | } 596 | else 597 | { 598 | if(!array_key_exists($datanode->getName(),$this->curnode->data)) 599 | { 600 | $this->curnode->data[$datanode->getName()] = $datanode; 601 | } 602 | else 603 | { 604 | $tnode = $this->curnode->data[$datanode->getName()]; 605 | $this->curnode->data[$datanode->getName()] = array(); 606 | $this->curnode->data[$datanode->getName()][] = $tnode; 607 | $this->curnode->data[$datanode->getName()][] = $datanode; 608 | } 609 | } 610 | } 611 | } 612 | else 613 | { 614 | if($datanode->getName() == "EXDATE") 615 | { 616 | if(!array_key_exists($datanode->getName(),$this->curnode->data)) 617 | { 618 | $this->curnode->data[$datanode->getName()] = $datanode; 619 | } 620 | else 621 | { 622 | $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; 623 | } 624 | } 625 | else 626 | { 627 | if(!array_key_exists($datanode->getName(),$this->curnode->data)) 628 | { 629 | $this->curnode->data[$datanode->getName()] = $datanode; 630 | } 631 | else 632 | { 633 | $tnode = $this->curnode->data[$datanode->getName()]; 634 | $this->curnode->data[$datanode->getName()] = array(); 635 | $this->curnode->data[$datanode->getName()][] = $tnode; 636 | $this->curnode->data[$datanode->getName()][] = $datanode; 637 | } 638 | } 639 | } 640 | } 641 | $linecount++; 642 | } 643 | } 644 | else { 645 | $name = "VCALENDAR"; 646 | $this->curnode = new ZCiCalNode($name, $this->curnode); 647 | $this->tree = $this->curnode; 648 | $datanode = new ZCiCalDataNode("VERSION:2.0"); 649 | $this->curnode->data[$datanode->getName()] = $datanode; 650 | 651 | $datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//ZapCalLib 1.0//EN"); 652 | $this->curnode->data[$datanode->getName()] = $datanode; 653 | $datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN"); 654 | $this->curnode->data[$datanode->getName()] = $datanode; 655 | $datanode = new ZCiCalDataNode("METHOD:PUBLISH"); 656 | $this->curnode->data[$datanode->getName()] = $datanode; 657 | } 658 | } 659 | 660 | /** 661 | * CountEvents() 662 | * 663 | * Return the # of VEVENTs in the object 664 | * 665 | * @return int 666 | */ 667 | 668 | function countEvents() { 669 | $count = 0; 670 | if(isset($this->tree->child)){ 671 | foreach($this->tree->child as $child){ 672 | if($child->getName() == "VEVENT") 673 | $count++; 674 | } 675 | } 676 | return $count; 677 | } 678 | 679 | /** 680 | * CountVenues() 681 | * 682 | * Return the # of VVENUEs in the object 683 | * 684 | * @return int 685 | */ 686 | 687 | function countVenues() { 688 | $count = 0; 689 | if(isset($this->tree->child)){ 690 | foreach($this->tree->child as $child){ 691 | if($child->getName() == "VVENUE") 692 | $count++; 693 | } 694 | } 695 | return $count; 696 | } 697 | 698 | /** 699 | * Export object to string 700 | * 701 | * This function exports all objects to an iCalendar string 702 | * 703 | * @return string an iCalendar formatted string 704 | */ 705 | 706 | function export() { 707 | return $this->tree->export($this->tree); 708 | } 709 | 710 | /** 711 | * Get first event in object list 712 | * Use getNextEvent() to navigate through list 713 | * 714 | * @return object The first event, or null 715 | */ 716 | function &getFirstEvent() { 717 | static $nullguard = null; 718 | if ($this->countEvents() > 0){ 719 | $child = $this->tree->child[0]; 720 | $event=false; 721 | while(!$event && $child != null){ 722 | if($child->getName() == "VEVENT") 723 | $event = true; 724 | else 725 | $child = $child->next; 726 | } 727 | return $child; 728 | } 729 | else 730 | return $nullguard; 731 | } 732 | 733 | /** 734 | * Get next event in object list 735 | * 736 | * @param object $event The current event object 737 | * 738 | * @return object Returns the next event or null if past last event 739 | */ 740 | function &getNextEvent($event){ 741 | do{ 742 | $event = $event->next; 743 | } while($event != null && $event->getName() != "VEVENT"); 744 | return $event; 745 | } 746 | 747 | /** 748 | * Get first venue in object list 749 | * Use getNextVenue() to navigate through list 750 | * 751 | * @return object The first venue, or null 752 | */ 753 | function &getFirstVenue() { 754 | static $nullguard = null; 755 | if ($this->countVenues() > 0){ 756 | $child = $this->tree->child[0]; 757 | $event=false; 758 | while(!$event && $child != null){ 759 | if($child->getName() == "VVENUE") 760 | $event = true; 761 | else 762 | $child = $child->next; 763 | } 764 | return $child; 765 | } 766 | else 767 | return $nullguard; 768 | } 769 | 770 | /** 771 | * Get next venue in object list 772 | * 773 | * @param object $venue The current venue object 774 | * 775 | * @return object Returns the next venue or null if past last venue 776 | */ 777 | function &getNextVenue($venue){ 778 | do{ 779 | $venue = $venue->next; 780 | } while($venue != null && $venue->getName() != "VVENUE"); 781 | return $venue; 782 | } 783 | 784 | /** 785 | * Get first child in object list 786 | * Use getNextSibling() and getPreviousSibling() to navigate through list 787 | * 788 | * @param object $thisnode The parent object 789 | * 790 | * @return object The child object 791 | */ 792 | function &getFirstChild(& $thisnode){ 793 | $nullvalue = null; 794 | if(count($thisnode->child) > 0) { 795 | //echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "
"; 796 | return $thisnode->child[0]; 797 | } 798 | else 799 | return $nullvalue; 800 | } 801 | 802 | /** 803 | * Get next sibling in object list 804 | * 805 | * @param object $thisnode The current object 806 | * 807 | * @return object Returns the next sibling 808 | */ 809 | function &getNextSibling(& $thisnode){ 810 | return $thisnode->next; 811 | } 812 | 813 | /** 814 | * Get previous sibling in object list 815 | * 816 | * @param object $thisnode The current object 817 | * 818 | * @return object Returns the previous sibling 819 | */ 820 | function &getPrevSibling(& $thisnode){ 821 | return $thisnode->prev; 822 | } 823 | 824 | /** 825 | * Read date/time in iCal formatted string 826 | * 827 | * @param string iCal formated date/time string 828 | * 829 | * @return int Unix timestamp 830 | * @deprecated Use ZDateHelper::toUnixDateTime() instead 831 | */ 832 | 833 | function toUnixDateTime($datetime){ 834 | $year = substr($datetime,0,4); 835 | $month = substr($datetime,4,2); 836 | $day = substr($datetime,6,2); 837 | $hour = 0; 838 | $minute = 0; 839 | $second = 0; 840 | if(strlen($datetime) > 8 && $datetime[8] == "T") { 841 | $hour = substr($datetime,9,2); 842 | $minute = substr($datetime,11,2); 843 | $second = substr($datetime,13,2); 844 | } 845 | $d1 = mktime($hour, $minute, $second, $month, $day, $year); 846 | 847 | } 848 | 849 | /** 850 | * fromUnixDateTime() 851 | * 852 | * Take Unix timestamp and format to iCal date/time string 853 | * 854 | * @param int $datetime Unix timestamp, leave blank for current date/time 855 | * 856 | * @return string formatted iCal date/time string 857 | * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead 858 | */ 859 | 860 | static function fromUnixDateTime($datetime = null){ 861 | date_default_timezone_set('UTC'); 862 | if($datetime == null) 863 | $datetime = time(); 864 | return date("Ymd\THis",$datetime); 865 | } 866 | 867 | 868 | /** 869 | * fromUnixDate() 870 | * 871 | * Take Unix timestamp and format to iCal date string 872 | * 873 | * @param int $datetime Unix timestamp, leave blank for current date/time 874 | * 875 | * @return string formatted iCal date string 876 | * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead 877 | */ 878 | 879 | static function fromUnixDate($datetime = null){ 880 | date_default_timezone_set('UTC'); 881 | if($datetime == null) 882 | $datetime = time(); 883 | return date("Ymd",$datetime); 884 | } 885 | 886 | /** 887 | * Format into iCal time format from SQL date or SQL date-time format 888 | * 889 | * @param string $datetime SQL date or SQL date-time string 890 | * 891 | * @return string iCal formatted string 892 | * @deprecated Use ZDateHelper::fromSqlDateTime() instead 893 | */ 894 | static function fromSqlDateTime($datetime = ""){ 895 | if($datetime == "") 896 | $datetime = ZDateHelper::toSqlDateTime(); 897 | if(strlen($datetime) > 10) 898 | return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), 899 | substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); 900 | else 901 | return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); 902 | } 903 | 904 | /** 905 | * Format iCal time format to either SQL date or SQL date-time format 906 | * 907 | * @param string $datetime icalendar formatted date or date-time 908 | * @return string SQL date or SQL date-time string 909 | * @deprecated Use ZDateHelper::toSqlDateTime() instead 910 | */ 911 | static function toSqlDateTime($datetime = ""){ 912 | if($datetime == "") 913 | return ZDateHelper::toSqlDateTime(); 914 | if(strlen($datetime) > 10) 915 | return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), 916 | substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); 917 | else 918 | return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); 919 | } 920 | 921 | /** 922 | * Pull timezone data from node and put in array 923 | * 924 | * Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule 925 | * 926 | * @param array $node timezone object 927 | * 928 | * @return array 929 | */ 930 | static function getTZValues($node){ 931 | $tzvalues = array(); 932 | 933 | $tnode = @$node->data['TZOFFSETFROM']; 934 | if($tnode != null){ 935 | $tzvalues["tzoffsetfrom"] = $tnode->getValues(); 936 | } 937 | 938 | $tnode = @$node->data['TZOFFSETTO']; 939 | if($tnode != null){ 940 | $tzvalues["tzoffsetto"] = $tnode->getValues(); 941 | } 942 | 943 | $tnode = @$node->data['TZNAME']; 944 | if($tnode != null){ 945 | $tzvalues["tzname"] = $tnode->getValues(); 946 | } 947 | else 948 | $tzvalues["tzname"] = ""; 949 | 950 | $tnode = @$node->data['DTSTART']; 951 | if($tnode != null){ 952 | $tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues()); 953 | } 954 | 955 | $tnode = @$node->data['RRULE']; 956 | if($tnode != null){ 957 | $tzvalues["rrule"] = $tnode->getValues(); 958 | //echo "rule: " . $tzvalues["rrule"] . "
\n"; 959 | } 960 | else{ 961 | // no rule specified, let's create one from based on the date 962 | $date = getdate($tzvalues["dtstart"]); 963 | $month = $date["mon"]; 964 | $day = $date["mday"]; 965 | $tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day"; 966 | } 967 | 968 | return $tzvalues; 969 | } 970 | 971 | /** 972 | * Escape slashes, commas and semicolons in strings 973 | * 974 | * @param string $content 975 | * 976 | * @return string 977 | */ 978 | static function formatContent($content) 979 | { 980 | $content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content); 981 | return $content; 982 | } 983 | 984 | } 985 | 986 | ?> 987 | -------------------------------------------------------------------------------- /includes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /includes/recurringdate.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | // No direct access 13 | defined('_ZAPCAL') or die( 'Restricted access' ); 14 | 15 | /** 16 | * Zap Calendar Recurring Date Helper Class 17 | * 18 | * Class to expand recurring rule to a list of dates 19 | */ 20 | class ZCRecurringDate { 21 | /** 22 | * rules string 23 | * 24 | * @var string 25 | */ 26 | var $rules = ""; 27 | 28 | /** 29 | * start date in Unix Timestamp format (local timezone) 30 | * 31 | * @var integer 32 | */ 33 | var $startdate = null; 34 | 35 | /** 36 | * repeating frequency type (i.e. "y" for yearly, "m" for monthly) 37 | * 38 | * @var string 39 | */ 40 | var $freq = null; 41 | 42 | /** 43 | * timezone of event (using PHP timezones) 44 | * 45 | * @var string 46 | */ 47 | var $tzid = null; 48 | 49 | /** 50 | * repeat mode ('c': count, 'u': until) 51 | * 52 | * @var string 53 | */ 54 | var $repeatmode=null; 55 | 56 | /** 57 | * repeat until date (in UTC Unix Timestamp format) 58 | * 59 | * @var integer 60 | */ 61 | var $until=null; 62 | 63 | /** 64 | * repeat count when repeat mode is 'c' 65 | * 66 | * @var integer 67 | */ 68 | var $count=0; 69 | 70 | /** 71 | * array of repeat by seconds values 72 | * 73 | * @var array 74 | */ 75 | var $bysecond=array(); 76 | 77 | /** 78 | * array of repeat by minutes values 79 | * 80 | * @var array 81 | */ 82 | var $byminute=array(); 83 | 84 | /** 85 | * array of repeat by hour values 86 | * 87 | * @var array 88 | */ 89 | var $byhour=array(); 90 | 91 | /** 92 | * array of repeat by day values 93 | * 94 | * @var array 95 | */ 96 | var $byday=array(); 97 | 98 | /** 99 | * array of repeat by month day values 100 | * 101 | * @var array 102 | */ 103 | var $bymonthday=array(); 104 | 105 | /** 106 | * array of repeat by month values 107 | * 108 | * @var array 109 | */ 110 | var $bymonth=array(); 111 | 112 | /** 113 | * array of repeat by year values 114 | * 115 | * @var array 116 | */ 117 | var $byyear=array(); 118 | 119 | /** 120 | * array of repeat by setpos values 121 | * 122 | * @var array 123 | */ 124 | var $bysetpos=array(); 125 | 126 | /** 127 | * inteval of repeating event (i.e. every 2 weeks, every 6 months) 128 | * 129 | * @var integer 130 | */ 131 | var $interval = 1; 132 | 133 | /** 134 | * debug level (for testing only) 135 | * 136 | * @var integer 137 | */ 138 | var $debug = 0; 139 | 140 | /** 141 | * error string (future use) 142 | * 143 | * @var string 144 | */ 145 | var $error; 146 | 147 | /** 148 | * array of exception dates in Unix Timestamp format (UTC dates) 149 | * 150 | * @var array 151 | */ 152 | var $exdates=array(); 153 | 154 | /** 155 | * Expand recurring rule to a list of dates 156 | * 157 | * @param string $rules iCalendar rules string 158 | * @param integer $startdate start date in Unix Timestamp format 159 | * @param array $exdates array of exception dates 160 | * @param string $tzid timezone of event (using PHP timezones) 161 | */ 162 | function __construct($rules, $startdate, $exdates = array(),$tzid = "UTC"){ 163 | if(strlen($rules) > 0){ 164 | //move exdates to event timezone for comparing with event date 165 | for($i = 0; $i < count($exdates); $i++) 166 | { 167 | $exdates[$i] = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($exdates[$i]),$tzid)); 168 | } 169 | 170 | $rules=str_replace("\'","",$rules); 171 | $this->rules = $rules; 172 | if($startdate == null){ 173 | // if not specified, use start date of beginning of last year 174 | $tdate=getdate(); 175 | $startdate=mktime(0,0,0,1,1,$tdate["year"] - 1); 176 | } 177 | $this->startdate = $startdate; 178 | $this->tzid = $tzid; 179 | $this->exdates = $exdates; 180 | 181 | $rules=explode(";", $rules); 182 | $ruletype = ""; 183 | foreach($rules as $rule){ 184 | $item=explode("=",$rule); 185 | //echo $item[0] . "=" . $item[1] . "
\n"; 186 | switch($item[0]){ 187 | case "FREQ": 188 | switch($item[1]){ 189 | case "YEARLY": 190 | $this->freq="y"; 191 | break; 192 | case "MONTHLY": 193 | $this->freq="m"; 194 | break; 195 | case "WEEKLY": 196 | $this->freq="w"; 197 | break; 198 | case "DAILY": 199 | $this->freq="d"; 200 | break; 201 | case "HOURLY": 202 | $this->freq="h"; 203 | break; 204 | case "MINUTELY": 205 | $this->freq="i"; 206 | break; 207 | case "SECONDLY": 208 | $this->freq="s"; 209 | break; 210 | } 211 | break; 212 | case "INTERVAL": 213 | $this->interval = $item[1]; 214 | break; 215 | case "BYSECOND": 216 | $this->bysecond = explode(",",$item[1]); 217 | $ruletype = $item[0]; 218 | break; 219 | case "BYMINUTE": 220 | $this->byminute = explode(",",$item[1]); 221 | $ruletype = $item[0]; 222 | break; 223 | case "BYHOUR": 224 | $this->byhour = explode(",",$item[1]); 225 | $ruletype = $item[0]; 226 | break; 227 | case "BYDAY": 228 | $this->byday = explode(",",$item[1]); 229 | $ruletype = $item[0]; 230 | break; 231 | case "BYMONTHDAY": 232 | $this->bymonthday = explode(",",$item[1]); 233 | $ruletype = $item[0]; 234 | break; 235 | case "BYMONTH": 236 | $this->bymonth = explode(",",$item[1]); 237 | $ruletype = $item[0]; 238 | break; 239 | case "BYYEAR": 240 | $this->byyear = explode(",",$item[1]); 241 | $ruletype = $item[0]; 242 | break; 243 | case "COUNT": 244 | $this->count = intval($item[1]); 245 | $this->repeatmode = "c"; 246 | break; 247 | case "BYSETPOS": 248 | $this->bysetpos = explode(",",$item[1]); 249 | break; 250 | case "UNTIL": 251 | $this->until = ZDateHelper::fromiCaltoUnixDateTime($item[1]); 252 | $this->repeatmode = "u"; 253 | break; 254 | } 255 | } 256 | if(count($this->bysetpos) > 0){ 257 | switch($ruletype){ 258 | case "BYYEAR": 259 | $this->byyear = $this->bySetPos($this->byyear,$this->bysetpos); 260 | break; 261 | case "BYMONTH": 262 | $this->bymonth = $this->bySetPos($this->bymonth,$this->bysetpos); 263 | break; 264 | case "BYMONTHDAY": 265 | $this->bymonthday = $this->bySetPos($this->bymonthday,$this->bysetpos); 266 | break; 267 | case "BYDAY": 268 | $this->byday = $this->bySetPos($this->byday,$this->bysetpos); 269 | break; 270 | case "BYHOUR": 271 | $this->byhour = $this->bySetPos($this->byhour,$this->bysetpos); 272 | break; 273 | case "BYMINUTE": 274 | $this->byminute = $this->bySetPos($this->byminute,$this->bysetpos); 275 | break; 276 | case "BYSECOND": 277 | $this->bysecond = $this->bySetPos($this->bysecond,$this->bysetpos); 278 | break; 279 | } 280 | } 281 | } 282 | } 283 | 284 | /** 285 | * bysetpos rule support 286 | * 287 | * @param array $bytype 288 | * @param array $bysetpos 289 | * 290 | * @return array 291 | */ 292 | function bySetPos($bytype, $bysetpos){ 293 | $result = array(); 294 | for($i=0; $i < count($bysetpos); $i++){ 295 | for($j=0; $j < count($bytype); $j++){ 296 | $result[] = $bysetpos[$i] . $bytype[$j]; 297 | } 298 | } 299 | return $result; 300 | } 301 | 302 | /** 303 | * save error 304 | * 305 | * @param string $msg 306 | */ 307 | function setError($msg){ 308 | $this->error = $msg; 309 | } 310 | 311 | /** 312 | * get error message 313 | * 314 | * @return string error message 315 | */ 316 | function getError(){ 317 | return $this->error; 318 | } 319 | 320 | /** 321 | * set debug level (0: none, 1: minimal, 2: more output) 322 | * 323 | * @param integer $level 324 | * 325 | */ 326 | function setDebug($level) 327 | { 328 | $this->debug = $level; 329 | } 330 | 331 | /** 332 | * display debug message 333 | * 334 | * @param integer $level 335 | * @param string $msg 336 | */ 337 | function debug($level, $msg){ 338 | if($this->debug >= $level) 339 | echo $msg . "
\n"; 340 | } 341 | 342 | /** 343 | * Get repeating dates by year 344 | * 345 | * @param integer $startdate start date of repeating events, in Unix timestamp format 346 | * @param integer $enddate end date of repeating events, in Unix timestamp format 347 | * @param array $rdates array to contain expanded repeating dates 348 | * @param string $tzid timezone of event (using PHP timezones) 349 | * 350 | * @return integer count of dates 351 | */ 352 | private function byYear($startdate, $enddate, &$rdates, $tzid="UTC"){ 353 | self::debug(1,"byYear(" . ZDateHelper::toSqlDateTime($startdate) . "," 354 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 355 | $count = 0; 356 | if(count($this->byyear) > 0){ 357 | foreach($this->byyear as $year){ 358 | $t = getdate($startdate); 359 | $wdate = mktime($t[hours],$t[minutes],$t[seconds],$t[month],$t[mday],$year); 360 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 361 | $count = $this->byMonth($wdate, $enddate, $rdates, $tzid); 362 | if($count == 0) { 363 | $rdates[] = $wdate; 364 | $count++; 365 | } 366 | } 367 | } 368 | } 369 | else if(!$this->maxDates($rdates)) 370 | $count = $this->byMonth($startdate, $enddate, $rdates, $tzid); 371 | self::debug(1,"byYear() returned " . $count ); 372 | return $count; 373 | } 374 | 375 | /** 376 | * Get repeating dates by month 377 | * 378 | * @param integer $startdate start date of repeating events, in Unix timestamp format 379 | * @param integer $enddate end date of repeating events, in Unix timestamp format 380 | * @param array $rdates array to contain expanded repeating dates 381 | * @param string $tzid timezone of event (using PHP timezones) 382 | * 383 | * @return integer count of dates 384 | */ 385 | private function byMonth($startdate, $enddate, &$rdates, $tzid="UTC"){ 386 | self::debug(1,"byMonth(" . ZDateHelper::toSqlDateTime($startdate) . "," 387 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 388 | $count = 0; 389 | if(count($this->bymonth) > 0){ 390 | foreach($this->bymonth as $month){ 391 | $t = getdate($startdate); 392 | $wdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$month,$t["mday"],$t["year"]); 393 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 394 | $count = $this->byMonthDay($wdate, $enddate, $rdates, $tzid); 395 | if($count == 0) { 396 | $rdates[] = $wdate; 397 | $count++; 398 | } 399 | } 400 | } 401 | } 402 | else if(!$this->maxDates($rdates)) 403 | $count = $this->byMonthDay($startdate, $enddate, $rdates, $tzid); 404 | self::debug(1,"byMonth() returned " . $count ); 405 | return $count; 406 | } 407 | 408 | /** 409 | * Get repeating dates by month day 410 | * 411 | * @param integer $startdate start date of repeating events, in Unix timestamp format 412 | * @param integer $enddate end date of repeating events, in Unix timestamp format 413 | * @param array $rdates array to contain expanded repeating dates 414 | * @param string $tzid timezone of event (using PHP timezones) 415 | * 416 | * @return integer count of dates 417 | */ 418 | private function byMonthDay($startdate, $enddate, &$rdates, $tzid="UTC"){ 419 | self::debug(1,"byMonthDay(" . ZDateHelper::toSqlDateTime($startdate) . "," 420 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 421 | $count = 0; 422 | self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); 423 | if(count($this->bymonthday) > 0){ 424 | foreach($this->bymonthday as $day){ 425 | $day = intval($day); 426 | $t = getdate($startdate); 427 | $wdate = mktime($t['hours'],$t['minutes'],$t['seconds'],$t['mon'],$day,$t['year']); 428 | self::debug(2,"mktime(" . $t['hours'] . ", " . $t['minutes'] 429 | . ", " . $t['mon'] . ", " . $day . ", " . $t['year'] . ") returned $wdate"); 430 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 431 | $count = $this->byDay($wdate, $enddate, $rdates, $tzid); 432 | if($count == 0) { 433 | $rdates[] = $wdate; 434 | $count++; 435 | } 436 | } 437 | } 438 | } 439 | else if(!$this->maxDates($rdates)) { 440 | self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); 441 | $count = $this->byDay($startdate, $enddate, $rdates, $tzid); 442 | } 443 | self::debug(1,"byMonthDay() returned " . $count ); 444 | return $count; 445 | } 446 | 447 | /** 448 | * Get repeating dates by day 449 | * 450 | * @param integer $startdate start date of repeating events, in Unix timestamp format 451 | * @param integer $enddate end date of repeating events, in Unix timestamp format 452 | * @param array $rdates array to contain expanded repeating dates 453 | * @param string $tzid timezone of event (using PHP timezones) 454 | * 455 | * @return integer count of dates 456 | */ 457 | private function byDay($startdate, $enddate, &$rdates, $tzid="UTC"){ 458 | self::debug(1,"byDay(" . ZDateHelper::toSqlDateTime($startdate) . "," 459 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 460 | $days = array( 461 | "SU" => 0, 462 | "MO" => 1, 463 | "TU" => 2, 464 | "WE" => 3, 465 | "TH" => 4, 466 | "FR" => 5, 467 | "SA" => 6); 468 | $idays = array( 469 | 0 => "SU", 470 | 1 => "MO", 471 | 2 => "TU", 472 | 3 => "WE", 473 | 4 => "TH", 474 | 5 => "FR", 475 | 6 => "SA"); 476 | 477 | $count = 0; 478 | if(count($this->byday) > 0){ 479 | if(empty($this->byday[0])) 480 | { 481 | $this->byday[0] = $idays[date("w",$startdate)]; 482 | } 483 | foreach($this->byday as $tday){ 484 | $t = getdate($startdate); 485 | $day = substr($tday,strlen($tday) - 2); 486 | if(strlen($day) < 2) 487 | { 488 | // missing start day, use current date for DOW 489 | $day = $idays[date("w",$startdate)]; 490 | } 491 | if(strlen($tday) > 2) { 492 | $imin = 1; 493 | $imax = 5; // max # of occurances in a month 494 | if(strlen($tday) > 2) 495 | $imin = $imax = substr($tday,0,strlen($tday) - 2); 496 | self::debug(2,"imin: $imin, imax: $imax, tday: $tday, day: $day, daynum: {$days[$day]}"); 497 | for($i = $imin; $i <= $imax; $i++){ 498 | $wdate = ZDateHelper::getDateFromDay($startdate,$i-1,$days[$day],$tzid); 499 | self::debug(2,"getDateFromDay(" . ZDateHelper::toSqlDateTime($startdate) 500 | . ",$i,{$days[$day]}) returned " . ZDateHelper::toSqlDateTime($wdate)); 501 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 502 | $count = $this->byHour($wdate, $enddate, $rdates); 503 | if($count == 0){ 504 | $rdates[] = $wdate; 505 | $count++; 506 | //break; 507 | } 508 | } 509 | } 510 | } 511 | else { 512 | // day of week version 513 | $startdate_dow = date("w",$startdate); 514 | $datedelta = $days[$day] - $startdate_dow; 515 | self::debug(2, "start_dow: $startdate_dow, datedelta: $datedelta"); 516 | if($datedelta >= 0) 517 | { 518 | $wdate = ZDateHelper::addDate($startdate,0,0,0,0,$datedelta,0,$this->tzid); 519 | self::debug(2, "wdate: " . ZDateHelper::toSqlDateTime($wdate)); 520 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 521 | $count = $this->byHour($wdate, $enddate, $rdates); 522 | if($count == 0){ 523 | $rdates[] = $wdate; 524 | $count++; 525 | self::debug(2,"adding date " . ZDateHelper::toSqlDateTime($wdate) ); 526 | } 527 | } 528 | } 529 | } 530 | } 531 | } 532 | else if(!$this->maxDates($rdates)) 533 | $count = $this->byHour($startdate, $enddate, $rdates); 534 | self::debug(1,"byDay() returned " . $count ); 535 | return $count; 536 | } 537 | 538 | /** 539 | * Get repeating dates by hour 540 | * 541 | * @param integer $startdate start date of repeating events, in Unix timestamp format 542 | * @param integer $enddate end date of repeating events, in Unix timestamp format 543 | * @param array $rdates array to contain expanded repeating dates 544 | * @param string $tzid timezone of event (using PHP timezones) 545 | * 546 | * @return integer count of dates 547 | */ 548 | private function byHour($startdate, $enddate, &$rdates, $tzid="UTC"){ 549 | self::debug(1,"byHour(" . ZDateHelper::toSqlDateTime($startdate) . "," 550 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 551 | $count = 0; 552 | if(count($this->byhour) > 0){ 553 | foreach($this->byhour as $hour){ 554 | $t = getdate($startdate); 555 | $wdate = mktime($hour,$t["minutes"],$t["seconds"],$t["mon"],$t["mday"],$t["year"]); 556 | self::debug(2,"checking date/time " . ZDateHelper::toSqlDateTime($wdate)); 557 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 558 | $count = $this->byMinute($wdate, $enddate, $rdates); 559 | if($count == 0) { 560 | $rdates[] = $wdate; 561 | $count++; 562 | } 563 | } 564 | } 565 | } 566 | else if(!$this->maxDates($rdates)) 567 | $count = $this->byMinute($startdate, $enddate, $rdates); 568 | self::debug(1,"byHour() returned " . $count ); 569 | return $count; 570 | } 571 | 572 | /** 573 | * Get repeating dates by minute 574 | * 575 | * @param integer $startdate start date of repeating events, in Unix timestamp format 576 | * @param integer $enddate end date of repeating events, in Unix timestamp format 577 | * @param array $rdates array to contain expanded repeating dates 578 | * @param string $tzid timezone of event (using PHP timezones) 579 | * 580 | * @return integer count of dates 581 | */ 582 | private function byMinute($startdate, $enddate, &$rdates, $tzid="UTC"){ 583 | self::debug(1,"byMinute(" . ZDateHelper::toSqlDateTime($startdate) . "," 584 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 585 | $count = 0; 586 | if(count($this->byminute) > 0){ 587 | foreach($this->byminute as $minute){ 588 | $t = getdate($startdate); 589 | $wdate = mktime($t["hours"],$minute,$t["seconds"],$t["mon"],$t["mday"],$t["year"]); 590 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 591 | $count = $this->bySecond($wdate, $enddate, $rdates); 592 | if($count == 0) { 593 | $rdates[] = $wdate; 594 | $count++; 595 | } 596 | } 597 | } 598 | } 599 | else if(!$this->maxDates($rdates)) 600 | $count = $this->bySecond($startdate, $enddate, $rdates); 601 | self::debug(1,"byMinute() returned " . $count ); 602 | return $count; 603 | } 604 | /** 605 | * Get repeating dates by second 606 | * 607 | * @param integer $startdate start date of repeating events, in Unix timestamp format 608 | * @param integer $enddate end date of repeating events, in Unix timestamp format 609 | * @param array $rdates array to contain expanded repeating dates 610 | * @param string $tzid timezone of event (using PHP timezones) 611 | * 612 | * @return integer count of dates 613 | */ 614 | private function bySecond($startdate, $enddate, &$rdates, $tzid="UTC"){ 615 | self::debug(1,"bySecond(" . ZDateHelper::toSqlDateTime($startdate) . "," 616 | . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); 617 | $count = 0; 618 | if(count($this->bysecond) > 0){ 619 | foreach($this->bysecond as $second){ 620 | $t = getdate($startdate); 621 | $wdate = mktime($t["hours"],$t["minutes"],$second,$t["mon"],$t["mday"],$t["year"]); 622 | if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ 623 | $rdates[] = $wdate; 624 | $count++; 625 | } 626 | } 627 | } 628 | self::debug(1,"bySecond() returned " . $count ); 629 | return $count; 630 | } 631 | 632 | /** 633 | * Determine if the loop has reached the end date 634 | * 635 | * @param array $rdates array of repeating dates 636 | * 637 | * @return boolean 638 | */ 639 | private function maxDates($rdates){ 640 | if($this->repeatmode == "c" && count($rdates) >= $this->count) 641 | return true; // exceeded count 642 | else if(count($rdates) > 0 && $this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ 643 | return true; //past date 644 | } 645 | return false; 646 | } 647 | 648 | /** 649 | * Get array of dates from recurring rule 650 | * 651 | * @param $maxdate integer maximum date to appear in repeating dates in Unix timestamp format 652 | * 653 | * @return array 654 | */ 655 | public function getDates($maxdate = null){ 656 | //$this->debug = 2; 657 | self::debug(1,"getDates()"); 658 | $nextdate = $enddate = $this->startdate; 659 | $rdates = array(); 660 | $done = false; 661 | $eventcount = 0; 662 | $loopcount = 0; 663 | self::debug(2,"freq: " . $this->freq . ", interval: " . $this->interval); 664 | while(!$done){ 665 | self::debug(1,"*** Frequency ({$this->freq}) loop pass $loopcount ***"); 666 | switch($this->freq){ 667 | case "y": 668 | if($eventcount > 0) 669 | { 670 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,0,$this->interval,$this->tzid); 671 | self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); 672 | if(!empty($this->byday)){ 673 | $t = getdate($nextdate); 674 | $nextdate = gmmktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); 675 | } 676 | self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); 677 | } 678 | $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,0,1); 679 | break; 680 | case "m": 681 | if($eventcount > 0) 682 | { 683 | 684 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); 685 | self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); 686 | } 687 | if(count($this->byday) > 0) 688 | { 689 | $t = getdate($nextdate); 690 | if($t["mday"] > 28) 691 | { 692 | //check for short months when using month by day, make sure we do not overshoot the counter and skip a month 693 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); 694 | $t2 = getdate($nextdate); 695 | if($t2["mday"] < $t["mday"]) 696 | { 697 | // oops, skipped a month, backup to previous month 698 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$t2["mday"] - $t["mday"],0,$this->tzid); 699 | } 700 | } 701 | $t = getdate($nextdate); 702 | $nextdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); 703 | } 704 | self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); 705 | $enddate=ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0); 706 | break; 707 | case "w": 708 | if($eventcount == 0) 709 | $nextdate=$nextdate; 710 | else { 711 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0,$this->tzid); 712 | if(count($this->byday) > 0){ 713 | $dow = date("w", $nextdate); 714 | // move to beginning of week (Sunday) 715 | $bow = 0; 716 | $diff = $bow - $dow; 717 | if($diff > 0) 718 | $diff = $diff - 7; 719 | $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$diff,0); 720 | } 721 | self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); 722 | } 723 | $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0); 724 | break; 725 | case "d": 726 | $nextdate=($eventcount==0?$nextdate: 727 | ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval,0,$this->tzid)); 728 | $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,1,0); 729 | break; 730 | } 731 | 732 | $count = $this->byYear($nextdate,$enddate,$rdates,$this->tzid); 733 | $eventcount += $count; 734 | if($maxdate > 0 && $maxdate < $nextdate) 735 | { 736 | array_pop($rdates); 737 | $done = true; 738 | } 739 | else if($count == 0 && !$this->maxDates($rdates)){ 740 | $rdates[] = $nextdate; 741 | $eventcount++; 742 | } 743 | if($this->maxDates($rdates)) 744 | $done = true; 745 | 746 | $year = date("Y", $nextdate); 747 | if($year > _ZAPCAL_MAXYEAR) 748 | { 749 | $done = true; 750 | } 751 | $loopcount++; 752 | if($loopcount > _ZAPCAL_MAXYEAR){ 753 | $done = true; 754 | throw new Exception("Infinite loop detected in getDates()"); 755 | } 756 | } 757 | if($this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ 758 | // erase last item 759 | array_pop($rdates); 760 | } 761 | $count1 = count($rdates); 762 | $rdates = array_unique($rdates); 763 | $count2 = count($rdates); 764 | $dups = $count1 - $count2; 765 | $excount = 0; 766 | 767 | foreach($this->exdates as $exdate) 768 | { 769 | if($pos = array_search($exdate,$rdates)) 770 | { 771 | array_splice($rdates,$pos,1); 772 | $excount++; 773 | } 774 | } 775 | self::debug(1,"getDates() returned " . count($rdates) . " dates, removing $dups duplicates, $excount exceptions"); 776 | 777 | 778 | if($this->debug >= 2) 779 | { 780 | self::debug(2,"Recurring Dates:"); 781 | foreach($rdates as $rdate) 782 | { 783 | $d = getdate($rdate); 784 | self::debug(2,ZDateHelper::toSQLDateTime($rdate) . " " . $d["wday"] ); 785 | } 786 | self::debug(2,"Exception Dates:"); 787 | foreach($this->exdates as $exdate) 788 | { 789 | self::debug(2, ZDateHelper::toSQLDateTime($exdate)); 790 | } 791 | //exit; 792 | } 793 | 794 | return $rdates; 795 | } 796 | } 797 | -------------------------------------------------------------------------------- /includes/timezone.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | // No direct access 13 | defined('_ZAPCAL') or die( 'Restricted access' ); 14 | 15 | /** 16 | * Zap Calendar Time Zone Helper Class 17 | * 18 | * Class to help create timezone section of iCalendar file 19 | * 20 | * @copyright Copyright (C) 2006 - 2016 by Dan Cogliano 21 | * @license GNU General Public License version 2 or later; see LICENSE.txt 22 | */ 23 | class ZCTimeZoneHelper { 24 | 25 | /** 26 | * getTZNode creates VTIMEZONE section in an iCalendar file 27 | * 28 | * @param @startyear int start year of date range 29 | * 30 | * @param @endyear int end year of date range 31 | * 32 | * @param $tzid string PHP timezone, use underscore for multiple words (i.e. "New_York" for "New York") 33 | * 34 | * @param $parentnode object iCalendar object where VTIMEZONE will be created 35 | * 36 | * @return object return VTIMEZONE object 37 | */ 38 | static function getTZNode($startyear, $endyear, $tzid, $parentnode) 39 | { 40 | $tzmins = array(); 41 | $tzmaxs = array(); 42 | if(!array_key_exists($tzid,$tzmins) || $tzmins[$tzid] > $startyear) 43 | { 44 | $tzmins[$tzid] = $startyear; 45 | } 46 | if(!array_key_exists($tzid,$tzmaxs) || $tzmaxs[$tzid] < $endyear) 47 | { 48 | $tzmaxs[$tzid] = $endyear; 49 | } 50 | 51 | foreach(array_keys($tzmins) as $tzid) 52 | { 53 | $tmin = $tzmins[$tzid] - 1; 54 | if(array_key_exists($tzid,$tzmaxs)) 55 | { 56 | $tmax = $tzmaxs[$tzid] + 1; 57 | } 58 | else 59 | { 60 | $tmax = $tzmins[$tzid] + 1; 61 | } 62 | $tstart = gmmktime(0,0,0,1,1,$tmin); 63 | $tend = gmmktime(23,59,59,12,31,$tmax); 64 | $tz = new DateTimeZone($tzid); 65 | $transitions = $tz->getTransitions($tstart,$tend); 66 | $tzobj = new ZCiCalNode("VTIMEZONE", $parentnode, true); 67 | $datanode = new ZCiCalDataNode("TZID:" . str_replace("_"," ",$tzid)); 68 | $tzobj->data[$datanode->getName()] = $datanode; 69 | $count = 0; 70 | $lasttransition = null; 71 | if(count($transitions) == 1) 72 | { 73 | // not enough transitions found, probably UTC 74 | // lets add fake transition at end for those systems that need it (i.e. Outlook) 75 | 76 | $t2 = array(); 77 | $t2["isdst"] = $transitions[0]["isdst"]; 78 | $t2["offset"] = $transitions[0]["offset"]; 79 | $t2["ts"] = $tstart; 80 | $t2["abbr"] = $transitions[0]["abbr"]; 81 | $transitions[] = $t2; 82 | } 83 | foreach($transitions as $transition) 84 | { 85 | $count++; 86 | if($count == 1) 87 | { 88 | $lasttransition = $transition; 89 | continue; // skip first item 90 | } 91 | if($transition["isdst"] == 1) 92 | { 93 | $tobj = new ZCiCalNode("DAYLIGHT", $tzobj); 94 | } 95 | else 96 | { 97 | $tobj = new ZCiCalNode("STANDARD", $tzobj); 98 | } 99 | //$tzobj->data[$tobj->getName()] == $tobj; 100 | 101 | // convert timestamp to local time zone 102 | $ts = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($transition["ts"]),$tzid)); 103 | $datanode = new ZCiCalDataNode("DTSTART:".ZDateHelper::toICalDateTime($ts)); 104 | $tobj->data[$datanode->getName()] = $datanode; 105 | //echo $ts . " => " . ZDateHelper::toICalDateTime($ts) . "
\n"; exit; 106 | $toffset = $lasttransition["offset"]; 107 | $thours = intval($toffset/60/60); 108 | $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; 109 | if($thours < 0) 110 | { 111 | $offset = sprintf("%03d%02d",$thours,$tmins); 112 | } 113 | else 114 | { 115 | $offset = sprintf("+%02d%02d",$thours,$tmins); 116 | } 117 | $datanode = new ZCiCalDataNode("TZOFFSETFROM:".$offset); 118 | $tobj->data[$datanode->getName()] = $datanode; 119 | 120 | $toffset = $transition["offset"]; 121 | $thours = intval($toffset/60/60); 122 | $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; 123 | if($thours < 0) 124 | { 125 | $offset = sprintf("%03d%02d",$thours,$tmins); 126 | } 127 | else 128 | { 129 | $offset = sprintf("+%02d%02d",$thours,$tmins); 130 | } 131 | $datanode = new ZCiCalDataNode("TZOFFSETTO:".$offset); 132 | $tobj->data[$datanode->getName()] = $datanode; 133 | 134 | $datanode = new ZCiCalDataNode("TZNAME:".$transition["abbr"]); 135 | $tobj->data[$datanode->getName()] = $datanode; 136 | 137 | $lasttransition = $transition; 138 | } 139 | } 140 | return $tzobj; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /zapcallib.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano 8 | * @license GNU GPLv3 9 | * @link http://icalendar.org/php-library.html 10 | */ 11 | 12 | /** 13 | * used by ZapCalLib 14 | * @var integer 15 | */ 16 | define('_ZAPCAL',1); 17 | 18 | if(!defined('_ZAPCAL_BASE')) 19 | { 20 | /** 21 | * the base folder of the library 22 | * @var string 23 | */ 24 | define('_ZAPCAL_BASE',__DIR__); 25 | } 26 | 27 | require_once(_ZAPCAL_BASE . '/includes/framework.php'); 28 | 29 | --------------------------------------------------------------------------------