|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00034 function olson_to_timezones ($filename) { 00035 00036 $zones = olson_simple_zone_parser($filename); 00037 $rules = olson_simple_rule_parser($filename); 00038 00039 $mdl_zones = array(); 00040 00054 $maxyear = localtime(time(), true); 00055 $maxyear = $maxyear['tm_year'] + 1900 + 10; 00056 00057 foreach ($zones as $zname => $zbyyear) { // loop over zones 00065 // clean the slate for a new zone 00066 $zone = NULL; 00067 $rule = NULL; 00068 00069 // 00070 // Find the pre 1970 zone rule entries 00071 // 00072 for ($y = 1970 ; $y >= 0 ; $y--) { 00073 if (array_key_exists((string)$y, $zbyyear )) { // we have a zone entry for the year 00074 $zone = $zbyyear[$y]; 00075 //print_object("Zone $zname pre1970 is in $y\n"); 00076 break; // Perl's last -- get outta here 00077 } 00078 } 00079 if (!empty($zone['rule']) && array_key_exists($zone['rule'], $rules)) { 00080 $rule = NULL; 00081 for ($y = 1970 ; $y > 0 ; $y--) { 00082 if (array_key_exists((string)$y, $rules[$zone['rule']] )) { // we have a rule entry for the year 00083 $rule = $rules[$zone['rule']][$y]; 00084 //print_object("Rule $rule[name] pre1970 is $y\n"); 00085 break; // Perl's last -- get outta here 00086 } 00087 00088 } 00089 if (empty($rule)) { 00090 // Colombia and a few others refer to rules before they exist 00091 // Perhaps we should comment out this warning... 00092 // trigger_error("Cannot find rule in $zone[rule] <= 1970"); 00093 $rule = array(); 00094 } 00095 } else { 00096 // no DST this year! 00097 $rule = array(); 00098 } 00099 00100 // Prepare to insert the base 1970 zone+rule 00101 if (!empty($rule) && array_key_exists($zone['rule'], $rules)) { 00102 // merge the two arrays into the moodle rule 00103 unset($rule['name']); // warning: $rule must NOT be a reference! 00104 unset($rule['year']); 00105 $mdl_tz = array_merge($zone, $rule); 00106 00107 //fix (de)activate_time (AT) field to be GMT 00108 $mdl_tz['dst_time'] = olson_parse_at($mdl_tz['dst_time'], 'set', $mdl_tz['gmtoff']); 00109 $mdl_tz['std_time'] = olson_parse_at($mdl_tz['std_time'], 'reset', $mdl_tz['gmtoff']); 00110 } else { 00111 // just a simple zone 00112 $mdl_tz = $zone; 00113 // TODO: Add other default values here! 00114 $mdl_tz['dstoff'] = 0; 00115 } 00116 00117 // Fix the from year to 1970 00118 $mdl_tz['year'] = 1970; 00119 00120 // add to the array 00121 $mdl_zones[] = $mdl_tz; 00122 //print_object("Zero entry for $zone[name] added"); 00123 00124 $lasttimezone = $mdl_tz; 00125 00129 for ($y = 1971; $y < $maxyear ; $y++) { 00130 $changed = false; 00137 if (array_key_exists((string)$y, $zbyyear)) { // we have a zone entry for the year 00138 $changed = true; 00139 $zone = $zbyyear[(string)$y]; 00140 } 00141 if (!empty($zone['rule']) && array_key_exists($zone['rule'], $rules)) { 00142 if (array_key_exists((string)$y, $rules[$zone['rule']])) { 00143 $changed = true; 00144 $rule = $rules[$zone['rule']][(string)$y]; 00145 } 00146 } else { 00147 $rule = array(); 00148 } 00149 00150 if ($changed) { 00151 //print_object("CHANGE YEAR $y Zone $zone[name] Rule $zone[rule]\n"); 00152 if (!empty($rule)) { 00153 // merge the two arrays into the moodle rule 00154 unset($rule['name']); 00155 unset($rule['year']); 00156 $mdl_tz = array_merge($zone, $rule); 00157 00158 // VERY IMPORTANT!! 00159 $mdl_tz['year'] = $y; 00160 00161 //fix (de)activate_time (AT) field to be GMT 00162 $mdl_tz['dst_time'] = olson_parse_at($mdl_tz['dst_time'], 'set', $mdl_tz['gmtoff']); 00163 $mdl_tz['std_time'] = olson_parse_at($mdl_tz['std_time'], 'reset', $mdl_tz['gmtoff']); 00164 } else { 00165 // just a simple zone 00166 $mdl_tz = $zone; 00167 } 00168 00169 /* 00170 if(isset($mdl_tz['dst_time']) && !strpos($mdl_tz['dst_time'], ':') || isset($mdl_tz['std_time']) && !strpos($mdl_tz['std_time'], ':')) { 00171 print_object($mdl_tz); 00172 print_object('---'); 00173 } 00174 */ 00175 // This is the simplest way to make the != operator just below NOT take the year into account 00176 $lasttimezone['year'] = $mdl_tz['year']; 00177 00178 // If not a duplicate, add and update $lasttimezone 00179 if($lasttimezone != $mdl_tz) { 00180 $mdl_zones[] = $lasttimezone = $mdl_tz; 00181 } 00182 } 00183 } 00184 00185 } 00186 00187 /* 00188 if (function_exists('memory_get_usage')) { 00189 trigger_error("We are consuming this much memory: " . get_memory_usage()); 00190 } 00191 */ 00192 00198 00199 foreach($mdl_zones as $key=>$mdl_zone) { 00200 $mdl_zones[$key]['tzrule'] = $mdl_zones[$key]['rule']; 00201 } 00202 00203 return $mdl_zones; 00204 } 00205 00206 00216 function olson_simple_rule_parser ($filename) { 00217 00218 $file = fopen($filename, 'r', 0); 00219 00220 if (empty($file)) { 00221 return false; 00222 } 00223 00224 // determine the maximum year for this zone 00225 $maxyear = array(); 00226 00227 while ($line = fgets($file)) { 00228 // only pay attention to rules lines 00229 if(!preg_match('/^Rule\s/', $line)){ 00230 continue; 00231 } 00232 $line = preg_replace('/\n$/', '',$line); // chomp 00233 $rule = preg_split('/\s+/', $line); 00234 list($discard, 00235 $name, 00236 $from, 00237 $to, 00238 $type, 00239 $in, 00240 $on, 00241 $at, 00242 $save, 00243 $letter) = $rule; 00244 if (isset($maxyear[$name])) { 00245 if ($maxyear[$name] < $from) { 00246 $maxyear[$name] = $from; 00247 } 00248 } else { 00249 $maxyear[$name] = $from; 00250 } 00251 00252 } 00253 00254 fseek($file, 0); 00255 00256 $rules = array(); 00257 while ($line = fgets($file)) { 00258 // only pay attention to rules lines 00259 if(!preg_match('/^Rule\s/', $line)){ 00260 continue; 00261 } 00262 $line = preg_replace('/\n$/', '',$line); // chomp 00263 $rule = preg_split('/\s+/', $line); 00264 list($discard, 00265 $name, 00266 $from, 00267 $to, 00268 $type, 00269 $in, 00270 $on, 00271 $at, 00272 $save, 00273 $letter) = $rule; 00274 00275 $srs = ($save === '0') ? 'reset' : 'set'; 00276 00277 if($to == 'only') { 00278 $to = $from; 00279 } 00280 else if($to == 'max') { 00281 $to = $maxyear[$name]; 00282 } 00283 00284 for($i = $from; $i <= $to; ++$i) { 00285 $rules[$name][$i][$srs] = $rule; 00286 } 00287 00288 } 00289 00290 fclose($file); 00291 00292 $months = array('jan' => 1, 'feb' => 2, 00293 'mar' => 3, 'apr' => 4, 00294 'may' => 5, 'jun' => 6, 00295 'jul' => 7, 'aug' => 8, 00296 'sep' => 9, 'oct' => 10, 00297 'nov' => 11, 'dec' => 12); 00298 00299 00300 // now reformat it a bit to match Moodle's DST table 00301 $moodle_rules = array(); 00302 foreach ($rules as $rule => $rulesbyyear) { 00303 foreach ($rulesbyyear as $year => $rulesthisyear) { 00304 00305 if(!isset($rulesthisyear['reset'])) { 00306 // No "reset" rule. We will assume that this is somewhere in the southern hemisphere 00307 // after a period of not using DST, otherwise it doesn't make sense at all. 00308 // With that assumption, we can put in a fake reset e.g. on Jan 1, 12:00. 00309 /* 00310 print_object("no reset"); 00311 print_object($rules); 00312 die(); 00313 */ 00314 $rulesthisyear['reset'] = array( 00315 NULL, NULL, NULL, NULL, NULL, 'jan', 1, '12:00', '00:00', NULL 00316 ); 00317 } 00318 00319 if(!isset($rulesthisyear['set'])) { 00320 // No "set" rule. We will assume that this is somewhere in the southern hemisphere 00321 // and that it begins a period of not using DST, otherwise it doesn't make sense at all. 00322 // With that assumption, we can put in a fake set on Dec 31, 12:00, shifting time by 0 minutes. 00323 $rulesthisyear['set'] = array( 00324 NULL, $rulesthisyear['reset'][1], NULL, NULL, NULL, 'dec', 31, '12:00', '00:00', NULL 00325 ); 00326 } 00327 00328 list($discard, 00329 $name, 00330 $from, 00331 $to, 00332 $type, 00333 $in, 00334 $on, 00335 $at, 00336 $save, 00337 $letter) = $rulesthisyear['set']; 00338 00339 $moodle_rule = array(); 00340 00341 // $save is sometimes just minutes 00342 // and othertimes HH:MM -- only 00343 // parse if relevant 00344 if (!preg_match('/^\d+$/', $save)) { 00345 list($hours, $mins) = explode(':', $save); 00346 $save = $hours * 60 + $mins; 00347 } 00348 00349 // we'll parse $at later 00350 // $at = olson_parse_at($at); 00351 $in = strtolower($in); 00352 if(!isset($months[$in])) { 00353 trigger_error('Unknown month: '.$in); 00354 } 00355 00356 $moodle_rule['name'] = $name; 00357 $moodle_rule['year'] = $year; 00358 $moodle_rule['dstoff'] = $save; // time offset 00359 00360 $moodle_rule['dst_month'] = $months[$in]; // the month 00361 $moodle_rule['dst_time'] = $at; // the time 00362 00363 // Encode index and day as per Moodle's specs 00364 $on = olson_parse_on($on); 00365 $moodle_rule['dst_startday'] = $on['startday']; 00366 $moodle_rule['dst_weekday'] = $on['weekday']; 00367 $moodle_rule['dst_skipweeks'] = $on['skipweeks']; 00368 00369 // and now the "deactivate" data 00370 list($discard, 00371 $name, 00372 $from, 00373 $to, 00374 $type, 00375 $in, 00376 $on, 00377 $at, 00378 $save, 00379 $letter) = $rulesthisyear['reset']; 00380 00381 // we'll parse $at later 00382 // $at = olson_parse_at($at); 00383 $in = strtolower($in); 00384 if(!isset($months[$in])) { 00385 trigger_error('Unknown month: '.$in); 00386 } 00387 00388 $moodle_rule['std_month'] = $months[$in]; // the month 00389 $moodle_rule['std_time'] = $at; // the time 00390 00391 // Encode index and day as per Moodle's specs 00392 $on = olson_parse_on($on); 00393 $moodle_rule['std_startday'] = $on['startday']; 00394 $moodle_rule['std_weekday'] = $on['weekday']; 00395 $moodle_rule['std_skipweeks'] = $on['skipweeks']; 00396 00397 $moodle_rules[$moodle_rule['name']][$moodle_rule['year']] = $moodle_rule; 00398 //print_object($moodle_rule); 00399 00400 } // end foreach year within a rule 00401 00402 // completed with all the entries for this rule 00403 // if the last entry has a TO other than 'max' 00404 // then we have to deal with closing the last rule 00405 //trigger_error("Rule $name ending to $to"); 00406 if (!empty($to) && $to !== 'max') { 00407 // We can handle two cases for TO: 00408 // a year, or "only" 00409 $reset_rule = $moodle_rule; 00410 $reset_rule['dstoff'] = '00'; 00411 if (preg_match('/^\d+$/', $to)){ 00412 $reset_rule['year'] = $to; 00413 $moodle_rules[$reset_rule['name']][$reset_rule['year']] = $reset_rule; 00414 } elseif ($to === 'only') { 00415 $reset_rule['year'] = $reset_rule['year'] + 1; 00416 $moodle_rules[$reset_rule['name']][$reset_rule['year']] = $reset_rule; 00417 } else { 00418 trigger_error("Strange value in TO $to rule field for rule $name"); 00419 } 00420 00421 } // end if $to is interesting 00422 00423 } // end foreach rule 00424 00425 return $moodle_rules; 00426 } 00427 00436 function olson_simple_zone_parser ($filename) { 00437 00438 $file = fopen($filename, 'r', 0); 00439 00440 if (empty($file)) { 00441 return false; 00442 } 00443 00444 $zones = array(); 00445 $lastzone = NULL; 00446 00447 while ($line = fgets($file)) { 00448 // skip obvious non-zone lines 00449 if (preg_match('/^#/', $line)) { 00450 continue; 00451 } 00452 if (preg_match('/^(?:Rule|Link|Leap)/',$line)) { 00453 $lastzone = NULL; // reset lastzone 00454 continue; 00455 } 00456 00457 // If there are blanks in the start of the line but the first non-ws character is a #, 00458 // assume it's an "inline comment". The funny thing is that this happens only during 00459 // the definition of the Rule for Europe/Athens. 00460 if(substr(trim($line), 0, 1) == '#') { 00461 continue; 00462 } 00463 00464 /*** Notes 00465 *** 00466 *** By splitting on space, we are only keeping the 00467 *** year of the UNTIL field -- that's on purpose. 00468 *** 00469 *** The Zone lines are followed by continuation lines 00470 *** were we reuse the info from the last one seen. 00471 *** 00472 *** We are transforming "until" fields into "from" fields 00473 *** which make more sense from the Moodle perspective, so 00474 *** each initial Zone entry is "from" the year 0, and for the 00475 *** continuation lines, we shift the "until" from the previous field 00476 *** into this line's "from". 00477 *** 00478 *** If a RULES field contains a time instead of a rule we discard it 00479 *** I have no idea of how to create a DST rule out of that 00480 *** (what are the start/end times?) 00481 *** 00482 *** We remove "until" from the data we keep, but preserve 00483 *** it in $lastzone. 00484 */ 00485 if (preg_match('/^Zone/', $line)) { // a new zone 00486 $line = trim($line); 00487 $line = preg_split('/\s+/', $line); 00488 $zone = array(); 00489 list( $discard, // 'Zone' 00490 $zone['name'], 00491 $zone['gmtoff'], 00492 $zone['rule'], 00493 $discard // format 00494 ) = $line; 00495 // the things we do to avoid warnings 00496 if (!empty($line[5])) { 00497 $zone['until'] = $line[5]; 00498 } 00499 $zone['year'] = '0'; 00500 00501 $zones[$zone['name']] = array(); 00502 00503 } else if (!empty($lastzone) && preg_match('/^\s+/', $line)){ 00504 // looks like a credible continuation line 00505 $line = trim($line); 00506 $line = preg_split('/\s+/', $line); 00507 if (count($line) < 3) { 00508 $lastzone = NULL; 00509 continue; 00510 } 00511 // retrieve info from the lastzone 00512 $zone = $lastzone; 00513 $zone['year'] = $zone['until']; 00514 // overwrite with current data 00515 list( 00516 $zone['gmtoff'], 00517 $zone['rule'], 00518 $discard // format 00519 ) = $line; 00520 // the things we do to avoid warnings 00521 if (!empty($line[3])) { 00522 $zone['until'] = $line[3]; 00523 } 00524 00525 } else { 00526 $lastzone = NULL; 00527 continue; 00528 } 00529 00530 // tidy up, we're done 00531 // perhaps we should insert in the DB at this stage? 00532 $lastzone = $zone; 00533 unset($zone['until']); 00534 $zone['gmtoff'] = olson_parse_offset($zone['gmtoff']); 00535 if ($zone['rule'] === '-') { // cleanup empty rules 00536 $zone['rule'] = ''; 00537 } 00538 if (preg_match('/:/',$zone['rule'])) { 00539 // we are not handling direct SAVE rules here 00540 // discard it 00541 $zone['rule'] = ''; 00542 } 00543 00544 $zones[$zone['name']][(string)$zone['year']] = $zone; 00545 } 00546 00547 return $zones; 00548 } 00549 00558 function olson_parse_offset ($offset) { 00559 $offset = trim($offset); 00560 00561 // perhaps it's just minutes 00562 if (preg_match('/^(-?)(\d*)$/', $offset)) { 00563 return intval($offset); 00564 } 00565 // (-)hours:minutes(:seconds) 00566 if (preg_match('/^(-?)(\d*):(\d+)/', $offset, $matches)) { 00567 // we are happy to discard the seconds 00568 $sign = $matches[1]; 00569 $hours = intval($matches[2]); 00570 $seconds = intval($matches[3]); 00571 $offset = $sign . ($hours*60 + $seconds); 00572 return intval($offset); 00573 } 00574 00575 trigger_error('Strange time format in olson_parse_offset() ' .$offset); 00576 return 0; 00577 00578 } 00579 00580 00606 function olson_parse_on ($on) { 00607 00608 $rule = array(); 00609 $days = array('sun' => 0, 'mon' => 1, 00610 'tue' => 2, 'wed' => 3, 00611 'thu' => 4, 'fri' => 5, 00612 'sat' => 6); 00613 00614 if(is_numeric($on)) { 00615 $rule['startday'] = intval($on); // Start searching from that day 00616 $rule['weekday'] = -1; // ...and stop there, no matter what weekday 00617 $rule['skipweeks'] = 0; // Don't skip any weeks. 00618 } 00619 else { 00620 $on = strtolower($on); 00621 if(substr($on, 0, 4) == 'last') { 00622 // e.g. lastSun 00623 if(!isset($days[substr($on, 4)])) { 00624 trigger_error('Unknown last weekday: '.substr($on, 4)); 00625 } 00626 else { 00627 $rule['startday'] = -1; // Start from end of month 00628 $rule['weekday'] = $days[substr($on, 4)]; // Find the first such weekday 00629 $rule['skipweeks'] = 0; // Don't skip any weeks. 00630 } 00631 } 00632 else if(substr($on, 3, 2) == '>=') { 00633 // e.g. Sun>=8 00634 if(!isset($days[substr($on, 0, 3)])) { 00635 trigger_error('Unknown >= weekday: '.substr($on, 0, 3)); 00636 } 00637 else { 00638 $rule['startday'] = intval(substr($on, 5)); // Start from that day of the month 00639 $rule['weekday'] = $days[substr($on, 0, 3)]; // Find the first such weekday 00640 $rule['skipweeks'] = 0; // Don't skip any weeks. 00641 } 00642 } 00643 else if(substr($on, 3, 2) == '<=') { 00644 // e.g. Sun<=25 00645 if(!isset($days[substr($on, 0, 3)])) { 00646 trigger_error('Unknown <= weekday: '.substr($on, 0, 3)); 00647 } 00648 else { 00649 $rule['startday'] = -intval(substr($on, 5)); // Start from that day of the month; COUNT BACKWARDS (minus sign) 00650 $rule['weekday'] = $days[substr($on, 0, 3)]; // Find the first such weekday 00651 $rule['skipweeks'] = 0; // Don't skip any weeks. 00652 } 00653 } 00654 else { 00655 trigger_error('unknown on '.$on); 00656 } 00657 } 00658 return $rule; 00659 } 00660 00661 00682 function olson_parse_at ($at, $set = 'set', $gmtoffset) { 00683 00684 // find the time "signature"; 00685 $sig = ''; 00686 if (preg_match('/[ugzs]$/', $at, $matches)) { 00687 $sig = $matches[0]; 00688 $at = substr($at, 0, strlen($at)-1); // chop 00689 } 00690 00691 $at = (strpos($at, ':') === false) ? $at . ':0' : $at; 00692 list($hours, $mins) = explode(':', $at); 00693 00694 // GMT -- return as is! 00695 if ( !empty($sig) && ( $sig === 'u' 00696 || $sig === 'g' 00697 || $sig === 'z' )) { 00698 return $at; 00699 } 00700 00701 // Wall clock 00702 if (empty($sig) || $sig === 'w') { 00703 if ($set !== 'set'){ // wall clock is on DST, assume by 1hr 00704 $hours = $hours-1; 00705 } 00706 $sig = 's'; 00707 } 00708 00709 // Standard time 00710 if (!empty($sig) && $sig === 's') { 00711 $mins = $mins + $hours*60 + $gmtoffset; 00712 $hours = $mins / 60; 00713 $hours = (int)$hours; 00714 $mins = abs($mins % 60); 00715 return sprintf('%02d:%02d', $hours, $mins); 00716 } 00717 00718 trigger_error('unhandled case - AT flag is ' . $matches[0]); 00719 }