diff --git a/inc/tz/expand.php b/inc/tz/expand.php new file mode 100644 index 00000000..c3f2652c --- /dev/null +++ b/inc/tz/expand.php @@ -0,0 +1,157 @@ + +* @copyright Morphoss Ltd +* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later +*/ + +require_once('vCalendar.php'); +require_once('RRule-v2.php'); + +if ( empty($format) ) $format = 'text/calendar'; +if ( $format != 'text/calendar' ) { + $request->PreconditionFailed(403, 'supported-format', 'This server currently only supports text/calendar format.'); +} + +if ( empty($start) ) $start = sprintf( '%04d-01-01', date('Y')); +if ( empty($end) ) $end = sprintf( '%04d-12-31', date('Y') + 10); + +$sql = 'SELECT our_tzno, tzid, active, olson_name, vtimezone, etag, '; +$sql .= 'to_char(last_modified AT TIME ZONE \'UTC\',\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS last_modified '; +$sql .= 'FROM timezones WHERE tzid=:tzid'; +$params = array( ':tzid' => $tzid ); +$qry = new AwlQuery($sql,$params); +if ( !$qry->Exec() ) exit(1); +if ( $qry->rows() < 1 ) { + $sql = 'SELECT our_tzno, tzid, active, olson_name, vtimezone, etag, '; + $sql .= 'to_char(last_modified AT TIME ZONE \'UTC\',\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS last_modified '; + $sql .= 'FROM timezones JOIN tz_aliases USING(our_tzno) WHERE tzalias=:tzid'; + if ( !$qry->Exec() ) exit(1); + if ( $qry->rows() < 1 ) $request->DoResponse(404); +} + +$tz = $qry->Fetch(); + +// define( 'DEBUG_EXPAND', true); +define( 'DEBUG_EXPAND', false ); + + +/** +* Expand the instances for a STANDARD or DAYLIGHT component of a VTIMEZONE +* +* @param object $vResource is a VCALENDAR with a VTIMEZONE containing components needing expansion +* @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events. +* @param object $range_end A RepeatRuleDateTime which is the end of the range for events. +* +* @return array of onset datetimes with UTC from/to offsets +*/ +function expand_timezone_onsets( vCalendar $vResource, RepeatRuleDateTime $range_start, RepeatRuleDateTime $range_end ) { + global $c; + $vtimezones = $vResource->GetComponents(); + $vtz = $vtimezones[0]; + $components = $vtz->GetComponents(); + + $instances = array(); + $dtstart = null; + $is_date = false; + $has_repeats = false; + $zone_tz = $vtz->GetPValue('TZID'); + $range_start->setTimeZone($zone_tz); + $range_end->setTimeZone($zone_tz); + + foreach( $components AS $k => $comp ) { + if ( DEBUG_EXPAND ) { + printf( "Starting TZ expansion for component '%s' in timezone '%s'\n", $comp->GetType(), $zone_tz); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } + $dtstart_prop = $comp->GetProperty('DTSTART'); + if ( !isset($dtstart_prop) ) continue; + $dtstart_prop->SetParameterValue('TZID',$zone_tz); + $dtstart = new RepeatRuleDateTime( $dtstart_prop ); + $is_date = $dtstart->isDate(); + $instances[$dtstart->FloatOrUTC()] = $comp; + $rrule = $comp->GetProperty('RRULE'); + $has_repeats = isset($rrule); + if ( !$has_repeats ) continue; + + $instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end); + if ( DEBUG_EXPAND ) { + print( "After rrule_expand"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } + $instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end); + if ( DEBUG_EXPAND ) { + print( "After rdate_expand"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } + } + + ksort($instances); + + $onsets = array(); + $start_utc = $range_start->FloatOrUTC(); + $end_utc = $range_end->FloatOrUTC(); + foreach( $instances AS $utc => $comp ) { + if ( $utc > $end_utc ) { + if ( DEBUG_EXPAND ) printf( "We're done: $utc is out of the range.\n"); + break; + } + + if ( $utc < $start_utc ) { + continue; + } + $onsets[$utc] = array( + 'from' => $comp->GetPValue('TZOFFSETFROM'), + 'to' => $comp->GetPValue('TZOFFSETTO'), + 'name' => $comp->GetPValue('TZNAME'), + 'type' => $comp->GetType() + ); + } + + return $onsets; +} + +header( 'Etag: "'.$tz->etag.'"' ); +header( 'Last-Modified', $tz->last_modified ); +header('Content-Type: application/xml; charset="utf-8"'); + +$vtz = new vCalendar($tz->vtimezone); + +$response = new XMLDocument(array("urn:ietf:params:xml:ns:timezone-service" => "")); +$timezones = $response->NewXMLElement('urn:ietf:params:xml:ns:timezone-service:timezones'); +$timezones->NewElement('dtstamp', gmdate('Ymd\THis\Z')); + +$from = new RepeatRuleDateTime($start); +$until = new RepeatRuleDateTime($end); + +$observances = expand_timezone_onsets($vtz, $from, $until); +$tzdata = array(); +$tzdata[] = new XMLElement( 'tzid', $tzid ); +$tzdata[] = new XMLElement( 'calscale', 'Gregorian' ); + +foreach( $observances AS $onset => $details ) { + $tzdata[] = new XMLElement( 'observance', array( + new XMLElement('name', (empty($details['name']) ? $details['type'] : $details['name'] ) ), + new XMLElement('onset', $onset ), + new XMLElement('utc-offset-from', substr($details['from'],0,-2).':'.substr($details['from'],-2) ), + new XMLElement('utc-offset-to', substr($details['to'],0,-2).':'.substr($details['to'],-2) ) + )); +} + +$timezones->NewElement('tzdata', $tzdata ); +echo $response->Render($timezones); + +exit(0); \ No newline at end of file diff --git a/testing/tests/timezone/5040-expand.result b/testing/tests/timezone/5040-expand.result new file mode 100644 index 00000000..fe6c167f --- /dev/null +++ b/testing/tests/timezone/5040-expand.result @@ -0,0 +1,146 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +Etag: "e87a687e5f1f3b83389ac9472aea42ed" +Content-Length: 4042 +Content-Type: application/xml; charset="utf-8" + + + + all good + + Pacific/Auckland + Gregorian + + NZST + 20110402T150000Z + +13:00 + +12:00 + + + NZDT + 20110924T140000Z + +12:00 + +13:00 + + + NZST + 20120331T150000Z + +13:00 + +12:00 + + + NZDT + 20120929T140000Z + +12:00 + +13:00 + + + NZST + 20130406T150000Z + +13:00 + +12:00 + + + NZDT + 20130928T140000Z + +12:00 + +13:00 + + + NZST + 20140405T150000Z + +13:00 + +12:00 + + + NZDT + 20140927T140000Z + +12:00 + +13:00 + + + NZST + 20150404T150000Z + +13:00 + +12:00 + + + NZDT + 20150926T140000Z + +12:00 + +13:00 + + + NZST + 20160402T150000Z + +13:00 + +12:00 + + + NZDT + 20160924T140000Z + +12:00 + +13:00 + + + NZST + 20170401T150000Z + +13:00 + +12:00 + + + NZDT + 20170923T140000Z + +12:00 + +13:00 + + + NZST + 20180331T150000Z + +13:00 + +12:00 + + + NZDT + 20180929T140000Z + +12:00 + +13:00 + + + NZST + 20190406T150000Z + +13:00 + +12:00 + + + NZDT + 20190928T140000Z + +12:00 + +13:00 + + + NZST + 20200404T150000Z + +13:00 + +12:00 + + + NZDT + 20200926T140000Z + +12:00 + +13:00 + + + NZST + 20210403T150000Z + +13:00 + +12:00 + + + NZDT + 20210925T140000Z + +12:00 + +13:00 + + + diff --git a/testing/tests/timezone/5040-expand.test b/testing/tests/timezone/5040-expand.test new file mode 100644 index 00000000..13bfa860 --- /dev/null +++ b/testing/tests/timezone/5040-expand.test @@ -0,0 +1,8 @@ +# +# List timezone server timezones +# +TYPE=GET +URL=http://regression.host/tz.php?action=expand&tzid=Pacific/Auckland +HEAD + +REPLACE=/dtstamp>[0-9TZ]{16}/dtstamp>all good/ diff --git a/testing/tests/timezone/5041-expand.result b/testing/tests/timezone/5041-expand.result new file mode 100644 index 00000000..74e64075 --- /dev/null +++ b/testing/tests/timezone/5041-expand.result @@ -0,0 +1,170 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +Etag: "60b52981cbe6fab393b6f40f24aec132" +Content-Length: 4720 +Content-Type: application/xml; charset="utf-8" + + + + all good + + America/Indiana/Indianapolis + Gregorian + + EDT + 20090308T070000Z + -05:00 + -04:00 + + + EST + 20091101T070000Z + -04:00 + -05:00 + + + EDT + 20100314T070000Z + -05:00 + -04:00 + + + EST + 20101107T070000Z + -04:00 + -05:00 + + + EDT + 20110313T070000Z + -05:00 + -04:00 + + + EST + 20111106T070000Z + -04:00 + -05:00 + + + EDT + 20120311T070000Z + -05:00 + -04:00 + + + EST + 20121104T070000Z + -04:00 + -05:00 + + + EDT + 20130310T070000Z + -05:00 + -04:00 + + + EST + 20131103T070000Z + -04:00 + -05:00 + + + EDT + 20140309T070000Z + -05:00 + -04:00 + + + EST + 20141102T070000Z + -04:00 + -05:00 + + + EDT + 20150308T070000Z + -05:00 + -04:00 + + + EST + 20151101T070000Z + -04:00 + -05:00 + + + EDT + 20160313T070000Z + -05:00 + -04:00 + + + EST + 20161106T070000Z + -04:00 + -05:00 + + + EDT + 20170312T070000Z + -05:00 + -04:00 + + + EST + 20171105T070000Z + -04:00 + -05:00 + + + EDT + 20180311T070000Z + -05:00 + -04:00 + + + EST + 20181104T070000Z + -04:00 + -05:00 + + + EDT + 20190310T070000Z + -05:00 + -04:00 + + + EST + 20191103T070000Z + -04:00 + -05:00 + + + EDT + 20200308T070000Z + -05:00 + -04:00 + + + EST + 20201101T070000Z + -04:00 + -05:00 + + + EDT + 20210314T070000Z + -05:00 + -04:00 + + + EST + 20211107T070000Z + -04:00 + -05:00 + + + diff --git a/testing/tests/timezone/5041-expand.test b/testing/tests/timezone/5041-expand.test new file mode 100644 index 00000000..8ab553b4 --- /dev/null +++ b/testing/tests/timezone/5041-expand.test @@ -0,0 +1,8 @@ +# +# Fetch an expanded timezone from the server. +# +TYPE=GET +URL=http://regression.host/tz.php?action=expand&tzid=America/Indiana/Indianapolis&start=2009-01-01 +HEAD + +REPLACE=/dtstamp>[0-9TZ]{16}/dtstamp>all good/ diff --git a/testing/tests/timezone/5042-expand.result b/testing/tests/timezone/5042-expand.result new file mode 100644 index 00000000..ca0f8a8d --- /dev/null +++ b/testing/tests/timezone/5042-expand.result @@ -0,0 +1,44 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +Etag: "60b52981cbe6fab393b6f40f24aec132" +Content-Length: 1108 +Content-Type: application/xml; charset="utf-8" + + + + all good + + America/Indiana/Indianapolis + Gregorian + + EDT + 20110313T070000Z + -05:00 + -04:00 + + + EST + 20111106T070000Z + -04:00 + -05:00 + + + EDT + 20120311T070000Z + -05:00 + -04:00 + + + EST + 20121104T070000Z + -04:00 + -05:00 + + + EDT + 20130310T070000Z + -05:00 + -04:00 + + + diff --git a/testing/tests/timezone/5042-expand.test b/testing/tests/timezone/5042-expand.test new file mode 100644 index 00000000..e576e5b1 --- /dev/null +++ b/testing/tests/timezone/5042-expand.test @@ -0,0 +1,8 @@ +# +# Fetch an expanded timezone from the server. +# +TYPE=GET +URL=http://regression.host/tz.php?action=expand&tzid=America/Indiana/Indianapolis&end=2013-07-01 +HEAD + +REPLACE=/dtstamp>[0-9TZ]{16}/dtstamp>all good/ diff --git a/testing/tests/timezone/5043-expand.result b/testing/tests/timezone/5043-expand.result new file mode 100644 index 00000000..1aeac8a0 --- /dev/null +++ b/testing/tests/timezone/5043-expand.result @@ -0,0 +1,74 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +Etag: "60b52981cbe6fab393b6f40f24aec132" +Content-Length: 1968 +Content-Type: application/xml; charset="utf-8" + + + + all good + + America/Indiana/Indianapolis + Gregorian + + EDT + 20140309T070000Z + -05:00 + -04:00 + + + EST + 20141102T070000Z + -04:00 + -05:00 + + + EDT + 20150308T070000Z + -05:00 + -04:00 + + + EST + 20151101T070000Z + -04:00 + -05:00 + + + EDT + 20160313T070000Z + -05:00 + -04:00 + + + EST + 20161106T070000Z + -04:00 + -05:00 + + + EDT + 20170312T070000Z + -05:00 + -04:00 + + + EST + 20171105T070000Z + -04:00 + -05:00 + + + EDT + 20180311T070000Z + -05:00 + -04:00 + + + EST + 20181104T070000Z + -04:00 + -05:00 + + + diff --git a/testing/tests/timezone/5043-expand.test b/testing/tests/timezone/5043-expand.test new file mode 100644 index 00000000..f45561a6 --- /dev/null +++ b/testing/tests/timezone/5043-expand.test @@ -0,0 +1,8 @@ +# +# Fetch an expanded timezone from the server. +# +TYPE=GET +URL=http://regression.host/tz.php?action=expand&tzid=America/Indiana/Indianapolis&start=2014-01-01&end=2018-12-31 +HEAD + +REPLACE=/dtstamp>[0-9TZ]{16}/dtstamp>all good/