PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/tz/expand.php

https://gitlab.com/tiggerben/davical
PHP | 195 lines | 160 code | 15 blank | 20 comment | 23 complexity | f8e139ff58bbeb0352e13a0b0d2871a5 MD5 | raw file
  1. <?php
  2. /**
  3. * DAViCal Timezone Service handler - capabilitis
  4. *
  5. * @package davical
  6. * @subpackage tzservice
  7. * @author Andrew McMillan <andrew@morphoss.com>
  8. * @copyright Morphoss Ltd
  9. * @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
  10. */
  11. require_once('vCalendar.php');
  12. require_once('RRule-v2.php');
  13. if ( empty($format) ) $format = 'text/calendar';
  14. if ( $format != 'text/calendar' ) {
  15. $request->PreconditionFailed(403, 'supported-format', 'This server currently only supports text/calendar format.', 'urn:ietf:params:xml:ns:timezone-service' );
  16. }
  17. if ( empty($start) ) $start = sprintf( '%04d-01-01', date('Y'));
  18. if ( empty($end) ) $end = sprintf( '%04d-12-31', date('Y') + 10);
  19. $sql = 'SELECT our_tzno, tzid, active, olson_name, vtimezone, etag, ';
  20. $sql .= 'to_char(last_modified,\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS last_modified ';
  21. $sql .= 'FROM timezones WHERE tzid=:tzid';
  22. $params = array( ':tzid' => $tzid );
  23. $qry = new AwlQuery($sql,$params);
  24. if ( !$qry->Exec() ) exit(1);
  25. if ( $qry->rows() < 1 ) {
  26. $sql = 'SELECT our_tzno, tzid, active, olson_name, vtimezone, etag, ';
  27. $sql .= 'to_char(last_modified,\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS last_modified ';
  28. $sql .= 'FROM timezones JOIN tz_aliases USING(our_tzno) WHERE tzalias=:tzid';
  29. if ( !$qry->Exec() ) exit(1);
  30. if ( $qry->rows() < 1 ) $request->DoResponse(404);
  31. }
  32. $tz = $qry->Fetch();
  33. // define( 'DEBUG_EXPAND', true);
  34. define( 'DEBUG_EXPAND', false );
  35. /**
  36. * Expand the instances for a STANDARD or DAYLIGHT component of a VTIMEZONE
  37. *
  38. * @param object $vResource is a VCALENDAR with a VTIMEZONE containing components needing expansion
  39. * @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events.
  40. * @param object $range_end A RepeatRuleDateTime which is the end of the range for events.
  41. * @param int $offset_from The offset from UTC in seconds at the onset time.
  42. *
  43. * @return array of onset datetimes with UTC from/to offsets
  44. */
  45. function expand_timezone_onsets( vCalendar $vResource, RepeatRuleDateTime $range_start, RepeatRuleDateTime $range_end ) {
  46. global $c;
  47. $vtimezones = $vResource->GetComponents();
  48. $vtz = $vtimezones[0];
  49. $components = $vtz->GetComponents();
  50. $instances = array();
  51. $dtstart = null;
  52. $is_date = false;
  53. $has_repeats = false;
  54. $zone_tz = $vtz->GetPValue('TZID');
  55. foreach( $components AS $k => $comp ) {
  56. if ( DEBUG_EXPAND ) {
  57. printf( "Starting TZ expansion for component '%s' in timezone '%s'\n", $comp->GetType(), $zone_tz);
  58. foreach( $instances AS $k => $v ) {
  59. print ' : '.$k;
  60. }
  61. print "\n";
  62. }
  63. $dtstart_prop = $comp->GetProperty('DTSTART');
  64. if ( !isset($dtstart_prop) ) continue;
  65. $dtstart = new RepeatRuleDateTime( $dtstart_prop );
  66. $dtstart->setTimeZone('UTC');
  67. $offset_from = $comp->GetPValue('TZOFFSETFROM');
  68. $offset_from = (($offset_from / 100) * 3600) + ((abs($offset_from) % 100) * 60 * ($offset_from < 0 ? -1 : 0));
  69. $offset_from *= -1;
  70. $offset_from = "$offset_from seconds";
  71. dbg_error_log( 'tz/update', "%s of offset\n", $offset_from);
  72. $dtstart->modify($offset_from);
  73. $is_date = $dtstart->isDate();
  74. $instances[$dtstart->UTC('Y-m-d\TH:i:s\Z')] = $comp;
  75. $rrule = $comp->GetProperty('RRULE');
  76. $has_repeats = isset($rrule);
  77. if ( !$has_repeats ) continue;
  78. $recur = $comp->GetProperty('RRULE');
  79. if ( isset($recur) ) {
  80. $recur = $recur->Value();
  81. $this_start = clone($dtstart);
  82. $rule = new RepeatRule( $this_start, $recur, $is_date );
  83. $i = 0;
  84. $result_limit = 1000;
  85. while( $date = $rule->next() ) {
  86. $instances[$date->UTC('Y-m-d\TH:i:s\Z')] = $comp;
  87. if ( $i++ >= $result_limit || $date > $range_end ) break;
  88. }
  89. if ( DEBUG_EXPAND ) {
  90. print( "After rrule_expand");
  91. foreach( $instances AS $k => $v ) {
  92. print ' : '.$k;
  93. }
  94. print "\n";
  95. }
  96. }
  97. $properties = $comp->GetProperties('RDATE');
  98. if ( count($properties) ) {
  99. foreach( $properties AS $p ) {
  100. $timezone = $p->GetParameterValue('TZID');
  101. $rdate = $p->Value();
  102. $rdates = explode( ',', $rdate );
  103. foreach( $rdates AS $k => $v ) {
  104. $rdate = new RepeatRuleDateTime( $v, $timezone, $is_date);
  105. if ( $return_floating_times ) $rdate->setAsFloat();
  106. $instances[$rdate->UTC('Y-m-d\TH:i:s\Z')] = $comp;
  107. if ( $rdate > $range_end ) break;
  108. }
  109. }
  110. if ( DEBUG_EXPAND ) {
  111. print( "After rdate_expand");
  112. foreach( $instances AS $k => $v ) {
  113. print ' : '.$k;
  114. }
  115. print "\n";
  116. }
  117. }
  118. }
  119. ksort($instances);
  120. $onsets = array();
  121. $start_utc = $range_start->UTC('Y-m-d\TH:i:s\Z');
  122. $end_utc = $range_end->UTC('Y-m-d\TH:i:s\Z');
  123. foreach( $instances AS $utc => $comp ) {
  124. if ( $utc > $end_utc ) {
  125. if ( DEBUG_EXPAND ) printf( "We're done: $utc is out of the range.\n");
  126. break;
  127. }
  128. if ( $utc < $start_utc ) {
  129. continue;
  130. }
  131. $onsets[$utc] = array(
  132. 'from' => $comp->GetPValue('TZOFFSETFROM'),
  133. 'to' => $comp->GetPValue('TZOFFSETTO'),
  134. 'name' => $comp->GetPValue('TZNAME'),
  135. 'type' => $comp->GetType()
  136. );
  137. }
  138. return $onsets;
  139. }
  140. header( 'ETag: "'.$tz->etag.'"' );
  141. header( 'Last-Modified', $tz->last_modified );
  142. header('Content-Type: application/xml; charset="utf-8"');
  143. $vtz = new vCalendar($tz->vtimezone);
  144. $response = new XMLDocument(array("urn:ietf:params:xml:ns:timezone-service" => ""));
  145. $timezones = $response->NewXMLElement('urn:ietf:params:xml:ns:timezone-service:timezones');
  146. $qry = new AwlQuery('SELECT to_char(max(last_modified),\'YYYY-MM-DD"T"HH24:MI:SS"Z"\') AS dtstamp FROM timezones');
  147. if ( $qry->Exec('tz/list',__LINE__,__FILE__) && $qry->rows() > 0 ) {
  148. $row = $qry->Fetch();
  149. $timezones->NewElement('dtstamp', $row->dtstamp);
  150. }
  151. else {
  152. $timezones->NewElement('dtstamp', gmdate('Y-m-d\TH:i:s\Z'));
  153. }
  154. $from = new RepeatRuleDateTime($start);
  155. $until = new RepeatRuleDateTime($end);
  156. $observances = expand_timezone_onsets($vtz, $from, $until);
  157. $tzdata = array();
  158. $tzdata[] = new XMLElement( 'tzid', $tzid );
  159. $tzdata[] = new XMLElement( 'calscale', 'Gregorian' );
  160. foreach( $observances AS $onset => $details ) {
  161. $tzdata[] = new XMLElement( 'observance', array(
  162. new XMLElement('name', (empty($details['name']) ? $details['type'] : $details['name'] ) ),
  163. new XMLElement('onset', $onset ),
  164. new XMLElement('utc-offset-from', substr($details['from'],0,-2).':'.substr($details['from'],-2) ),
  165. new XMLElement('utc-offset-to', substr($details['to'],0,-2).':'.substr($details['to'],-2) )
  166. ));
  167. }
  168. $timezones->NewElement('tzdata', $tzdata );
  169. echo $response->Render($timezones);
  170. exit(0);