[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @package moodlecore 20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 /** 25 * olson_to_timezones ($filename) 26 * 27 * Parses the olson files for Zones and DST rules. 28 * It updates the Moodle database with the Zones/DST rules 29 * 30 * @param string $filename 31 * @return bool true/false 32 * 33 */ 34 function olson_to_timezones ($filename) { 35 36 // Look for zone and rule information up to 10 years in the future. 37 $maxyear = localtime(time(), true); 38 $maxyear = $maxyear['tm_year'] + 1900 + 10; 39 40 $zones = olson_simple_zone_parser($filename, $maxyear); 41 $rules = olson_simple_rule_parser($filename, $maxyear); 42 43 $mdl_zones = array(); 44 45 /** 46 *** To translate the combined Zone & Rule changes 47 *** in the Olson files to the Moodle single ruleset 48 *** format, we need to trasverse every year and see 49 *** if either the Zone or the relevant Rule has a 50 *** change. It's yuck but it yields a rationalized 51 *** set of data, which is arguably simpler. 52 *** 53 *** Also note that I am starting at the epoch (1970) 54 *** because I don't think we'll see many events scheduled 55 *** before that, anyway. 56 *** 57 **/ 58 59 foreach ($zones as $zname => $zbyyear) { // loop over zones 60 /** 61 *** Loop over years, only adding a rule when zone or rule 62 *** have changed. All loops preserver the last seen vars 63 *** until there's an explicit decision to delete them 64 *** 65 **/ 66 67 // clean the slate for a new zone 68 $zone = NULL; 69 $rule = NULL; 70 71 // 72 // Find the pre 1970 zone rule entries 73 // 74 for ($y = 1970 ; $y >= 0 ; $y--) { 75 if (array_key_exists((string)$y, $zbyyear )) { // we have a zone entry for the year 76 $zone = $zbyyear[$y]; 77 //print_object("Zone $zname pre1970 is in $y\n"); 78 break; // Perl's last -- get outta here 79 } 80 } 81 if (!empty($zone['rule']) && array_key_exists($zone['rule'], $rules)) { 82 $rule = NULL; 83 for ($y = 1970 ; $y > 0 ; $y--) { 84 if (array_key_exists((string)$y, $rules[$zone['rule']] )) { // we have a rule entry for the year 85 $rule = $rules[$zone['rule']][$y]; 86 //print_object("Rule $rule[name] pre1970 is $y\n"); 87 break; // Perl's last -- get outta here 88 } 89 90 } 91 if (empty($rule)) { 92 // Colombia and a few others refer to rules before they exist 93 // Perhaps we should comment out this warning... 94 // trigger_error("Cannot find rule in $zone[rule] <= 1970"); 95 $rule = array(); 96 } 97 } else { 98 // no DST this year! 99 $rule = array(); 100 } 101 102 // Prepare to insert the base 1970 zone+rule 103 if (!empty($rule) && array_key_exists($zone['rule'], $rules)) { 104 // merge the two arrays into the moodle rule 105 unset($rule['name']); // warning: $rule must NOT be a reference! 106 unset($rule['year']); 107 $mdl_tz = array_merge($zone, $rule); 108 109 //fix (de)activate_time (AT) field to be GMT 110 $mdl_tz['dst_time'] = olson_parse_at($mdl_tz['dst_time'], 'set', $mdl_tz['gmtoff']); 111 $mdl_tz['std_time'] = olson_parse_at($mdl_tz['std_time'], 'reset', $mdl_tz['gmtoff']); 112 } else { 113 // just a simple zone 114 $mdl_tz = $zone; 115 // TODO: Add other default values here! 116 $mdl_tz['dstoff'] = 0; 117 } 118 119 // Fix the from year to 1970 120 $mdl_tz['year'] = 1970; 121 122 // add to the array 123 $mdl_zones[] = $mdl_tz; 124 //print_object("Zero entry for $zone[name] added"); 125 126 $lasttimezone = $mdl_tz; 127 128 /// 129 /// 1971 onwards 130 /// 131 for ($y = 1971; $y < $maxyear ; $y++) { 132 $changed = false; 133 /// 134 /// We create a "zonerule" entry if either zone or rule change... 135 /// 136 /// force $y to string to avoid PHP 137 /// thinking of a positional array 138 /// 139 if (array_key_exists((string)$y, $zbyyear)) { // we have a zone entry for the year 140 $changed = true; 141 $zone = $zbyyear[(string)$y]; 142 } 143 if (!empty($zone['rule']) && array_key_exists($zone['rule'], $rules)) { 144 if (array_key_exists((string)$y, $rules[$zone['rule']])) { 145 $changed = true; 146 $rule = $rules[$zone['rule']][(string)$y]; 147 } 148 } else { 149 $rule = array(); 150 } 151 152 if ($changed) { 153 //print_object("CHANGE YEAR $y Zone $zone[name] Rule $zone[rule]\n"); 154 if (!empty($rule)) { 155 // merge the two arrays into the moodle rule 156 unset($rule['name']); 157 unset($rule['year']); 158 $mdl_tz = array_merge($zone, $rule); 159 160 // VERY IMPORTANT!! 161 $mdl_tz['year'] = $y; 162 163 //fix (de)activate_time (AT) field to be GMT 164 $mdl_tz['dst_time'] = olson_parse_at($mdl_tz['dst_time'], 'set', $mdl_tz['gmtoff']); 165 $mdl_tz['std_time'] = olson_parse_at($mdl_tz['std_time'], 'reset', $mdl_tz['gmtoff']); 166 } else { 167 // just a simple zone 168 $mdl_tz = $zone; 169 } 170 171 /* 172 if(isset($mdl_tz['dst_time']) && !strpos($mdl_tz['dst_time'], ':') || isset($mdl_tz['std_time']) && !strpos($mdl_tz['std_time'], ':')) { 173 print_object($mdl_tz); 174 print_object('---'); 175 } 176 */ 177 // This is the simplest way to make the != operator just below NOT take the year into account 178 $lasttimezone['year'] = $mdl_tz['year']; 179 180 // If not a duplicate, add and update $lasttimezone 181 if($lasttimezone != $mdl_tz) { 182 $mdl_zones[] = $lasttimezone = $mdl_tz; 183 } 184 } 185 } 186 187 } 188 189 /* 190 if (function_exists('memory_get_usage')) { 191 trigger_error("We are consuming this much memory: " . get_memory_usage()); 192 } 193 */ 194 195 /// Since Moodle 1.7, rule is tzrule in DB (reserved words problem), so change it here 196 /// after everything is calculated to be properly loaded to the timezone table. 197 /// Pre 1.7 users won't have the old rule if updating this from moodle.org but it 198 /// seems that such field isn't used at all by the rest of Moodle (at least I haven't 199 /// found any use when looking for it). 200 201 foreach($mdl_zones as $key=>$mdl_zone) { 202 $mdl_zones[$key]['tzrule'] = $mdl_zones[$key]['rule']; 203 } 204 205 return $mdl_zones; 206 } 207 208 209 /** 210 * olson_simple_rule_parser($filename) 211 * 212 * Parses the olson files for DST rules. 213 * It's a simple implementation that simplifies some fields 214 * 215 * @return array a multidimensional array, or false on error 216 * 217 */ 218 function olson_simple_rule_parser($filename, $maxyear) { 219 220 $file = fopen($filename, 'r', 0); 221 222 if (empty($file)) { 223 return false; 224 } 225 226 while ($line = fgets($file)) { 227 // only pay attention to rules lines 228 if(!preg_match('/^Rule\s/', $line)){ 229 continue; 230 } 231 $line = preg_replace('/\n$/', '',$line); // chomp 232 $rule = preg_split('/\s+/', $line); 233 list($discard, 234 $name, 235 $from, 236 $to, 237 $type, 238 $in, 239 $on, 240 $at, 241 $save, 242 $letter) = $rule; 243 } 244 245 fseek($file, 0); 246 247 $rules = array(); 248 while ($line = fgets($file)) { 249 // only pay attention to rules lines 250 if(!preg_match('/^Rule\s/', $line)){ 251 continue; 252 } 253 $line = preg_replace('/\n$/', '',$line); // chomp 254 $rule = preg_split('/\s+/', $line); 255 list($discard, 256 $name, 257 $from, 258 $to, 259 $type, 260 $in, 261 $on, 262 $at, 263 $save, 264 $letter) = $rule; 265 266 $srs = ($save === '0') ? 'reset' : 'set'; 267 268 if($to == 'only') { 269 $to = $from; 270 } 271 else if($to == 'max') { 272 $to = $maxyear; 273 } 274 275 for($i = $from; $i <= $to; ++$i) { 276 $rules[$name][$i][$srs] = $rule; 277 } 278 279 } 280 281 fclose($file); 282 283 $months = array('jan' => 1, 'feb' => 2, 284 'mar' => 3, 'apr' => 4, 285 'may' => 5, 'jun' => 6, 286 'jul' => 7, 'aug' => 8, 287 'sep' => 9, 'oct' => 10, 288 'nov' => 11, 'dec' => 12); 289 290 291 // now reformat it a bit to match Moodle's DST table 292 $moodle_rules = array(); 293 foreach ($rules as $rule => $rulesbyyear) { 294 foreach ($rulesbyyear as $year => $rulesthisyear) { 295 296 if(!isset($rulesthisyear['reset'])) { 297 // No "reset" rule. We will assume that this is somewhere in the southern hemisphere 298 // after a period of not using DST, otherwise it doesn't make sense at all. 299 // With that assumption, we can put in a fake reset e.g. on Jan 1, 12:00. 300 /* 301 print_object("no reset"); 302 print_object($rules); 303 die(); 304 */ 305 $rulesthisyear['reset'] = array( 306 NULL, NULL, NULL, NULL, NULL, 'jan', 1, '12:00', '00:00', NULL 307 ); 308 } 309 310 if(!isset($rulesthisyear['set'])) { 311 // No "set" rule. We will assume that this is somewhere in the southern hemisphere 312 // and that it begins a period of not using DST, otherwise it doesn't make sense at all. 313 // With that assumption, we can put in a fake set on Dec 31, 12:00, shifting time by 0 minutes. 314 $rulesthisyear['set'] = array( 315 NULL, $rulesthisyear['reset'][1], NULL, NULL, NULL, 'dec', 31, '12:00', '00:00', NULL 316 ); 317 } 318 319 list($discard, 320 $name, 321 $from, 322 $to, 323 $type, 324 $in, 325 $on, 326 $at, 327 $save, 328 $letter) = $rulesthisyear['set']; 329 330 $moodle_rule = array(); 331 332 // $save is sometimes just minutes 333 // and othertimes HH:MM -- only 334 // parse if relevant 335 if (!preg_match('/^\d+$/', $save)) { 336 list($hours, $mins) = explode(':', $save); 337 $save = $hours * 60 + $mins; 338 } 339 340 // we'll parse $at later 341 // $at = olson_parse_at($at); 342 $in = strtolower($in); 343 if(!isset($months[$in])) { 344 trigger_error('Unknown month: '.$in); 345 } 346 347 $moodle_rule['name'] = $name; 348 $moodle_rule['year'] = $year; 349 $moodle_rule['dstoff'] = $save; // time offset 350 351 $moodle_rule['dst_month'] = $months[$in]; // the month 352 $moodle_rule['dst_time'] = $at; // the time 353 354 // Encode index and day as per Moodle's specs 355 $on = olson_parse_on($on); 356 $moodle_rule['dst_startday'] = $on['startday']; 357 $moodle_rule['dst_weekday'] = $on['weekday']; 358 $moodle_rule['dst_skipweeks'] = $on['skipweeks']; 359 360 // and now the "deactivate" data 361 list($discard, 362 $name, 363 $from, 364 $to, 365 $type, 366 $in, 367 $on, 368 $at, 369 $save, 370 $letter) = $rulesthisyear['reset']; 371 372 // we'll parse $at later 373 // $at = olson_parse_at($at); 374 $in = strtolower($in); 375 if(!isset($months[$in])) { 376 trigger_error('Unknown month: '.$in); 377 } 378 379 $moodle_rule['std_month'] = $months[$in]; // the month 380 $moodle_rule['std_time'] = $at; // the time 381 382 // Encode index and day as per Moodle's specs 383 $on = olson_parse_on($on); 384 $moodle_rule['std_startday'] = $on['startday']; 385 $moodle_rule['std_weekday'] = $on['weekday']; 386 $moodle_rule['std_skipweeks'] = $on['skipweeks']; 387 388 $moodle_rules[$moodle_rule['name']][$moodle_rule['year']] = $moodle_rule; 389 //print_object($moodle_rule); 390 391 } // end foreach year within a rule 392 393 // completed with all the entries for this rule 394 // if the last entry has a TO other than 'max' 395 // then we have to deal with closing the last rule 396 //trigger_error("Rule $name ending to $to"); 397 if (!empty($to) && $to !== 'max') { 398 // We can handle two cases for TO: 399 // a year, or "only" 400 $reset_rule = $moodle_rule; 401 $reset_rule['dstoff'] = '00'; 402 if (preg_match('/^\d+$/', $to)){ 403 $reset_rule['year'] = $to; 404 $moodle_rules[$reset_rule['name']][$reset_rule['year']] = $reset_rule; 405 } elseif ($to === 'only') { 406 $reset_rule['year'] = $reset_rule['year'] + 1; 407 $moodle_rules[$reset_rule['name']][$reset_rule['year']] = $reset_rule; 408 } else { 409 trigger_error("Strange value in TO $to rule field for rule $name"); 410 } 411 412 } // end if $to is interesting 413 414 } // end foreach rule 415 416 return $moodle_rules; 417 } 418 419 /** 420 * olson_simple_zone_parser($filename) 421 * 422 * Parses the olson files for zone info 423 * 424 * @return array a multidimensional array, or false on error 425 * 426 */ 427 function olson_simple_zone_parser($filename, $maxyear) { 428 429 $file = fopen($filename, 'r', 0); 430 431 if (empty($file)) { 432 return false; 433 } 434 435 $zones = array(); 436 $lastzone = NULL; 437 438 while ($line = fgets($file)) { 439 // skip obvious non-zone lines 440 if (preg_match('/^#/', $line)) { 441 continue; 442 } 443 if (preg_match('/^(?:Rule|Link|Leap)/',$line)) { 444 $lastzone = NULL; // reset lastzone 445 continue; 446 } 447 448 // If there are blanks in the start of the line but the first non-ws character is a #, 449 // assume it's an "inline comment". The funny thing is that this happens only during 450 // the definition of the Rule for Europe/Athens. 451 if(substr(trim($line), 0, 1) == '#') { 452 continue; 453 } 454 455 /*** Notes 456 *** 457 *** By splitting on space, we are only keeping the 458 *** year of the UNTIL field -- that's on purpose. 459 *** 460 *** The Zone lines are followed by continuation lines 461 *** were we reuse the info from the last one seen. 462 *** 463 *** We are transforming "until" fields into "from" fields 464 *** which make more sense from the Moodle perspective, so 465 *** each initial Zone entry is "from" the year 0, and for the 466 *** continuation lines, we shift the "until" from the previous field 467 *** into this line's "from". 468 *** 469 *** If a RULES field contains a time instead of a rule we discard it 470 *** I have no idea of how to create a DST rule out of that 471 *** (what are the start/end times?) 472 *** 473 *** We remove "until" from the data we keep, but preserve 474 *** it in $lastzone. 475 */ 476 if (preg_match('/^Zone/', $line)) { // a new zone 477 $line = trim($line); 478 $line = preg_split('/\s+/', $line); 479 $zone = array(); 480 list( $discard, // 'Zone' 481 $zone['name'], 482 $zone['gmtoff'], 483 $zone['rule'], 484 $discard // format 485 ) = $line; 486 // the things we do to avoid warnings 487 if (!empty($line[5])) { 488 $zone['until'] = $line[5]; 489 } 490 $zone['year'] = '0'; 491 492 $zones[$zone['name']] = array(); 493 494 } else if (!empty($lastzone) && preg_match('/^\s+/', $line)){ 495 // looks like a credible continuation line 496 $line = trim($line); 497 $line = preg_split('/\s+/', $line); 498 if (count($line) < 3) { 499 $lastzone = NULL; 500 continue; 501 } 502 // retrieve info from the lastzone 503 $zone = $lastzone; 504 $zone['year'] = $zone['until']; 505 // overwrite with current data 506 list( 507 $zone['gmtoff'], 508 $zone['rule'], 509 $discard // format 510 ) = $line; 511 // the things we do to avoid warnings 512 if (!empty($line[3])) { 513 $zone['until'] = $line[3]; 514 } 515 516 } else { 517 $lastzone = NULL; 518 continue; 519 } 520 521 // tidy up, we're done 522 // perhaps we should insert in the DB at this stage? 523 $lastzone = $zone; 524 unset($zone['until']); 525 $zone['gmtoff'] = olson_parse_offset($zone['gmtoff']); 526 if ($zone['rule'] === '-') { // cleanup empty rules 527 $zone['rule'] = ''; 528 } 529 if (preg_match('/:/',$zone['rule'])) { 530 // we are not handling direct SAVE rules here 531 // discard it 532 $zone['rule'] = ''; 533 } 534 535 $zones[$zone['name']][(string)$zone['year']] = $zone; 536 } 537 538 return $zones; 539 } 540 541 /** 542 * olson_parse_offset($offset) 543 * 544 * parses time offsets from the GMTOFF and SAVE 545 * fields into +/-MINUTES 546 * 547 * @return int 548 */ 549 function olson_parse_offset ($offset) { 550 $offset = trim($offset); 551 552 // perhaps it's just minutes 553 if (preg_match('/^(-?)(\d*)$/', $offset)) { 554 return intval($offset); 555 } 556 // (-)hours:minutes(:seconds) 557 if (preg_match('/^(-?)(\d*):(\d+)/', $offset, $matches)) { 558 // we are happy to discard the seconds 559 $sign = $matches[1]; 560 $hours = intval($matches[2]); 561 $seconds = intval($matches[3]); 562 $offset = $sign . ($hours*60 + $seconds); 563 return intval($offset); 564 } 565 566 trigger_error('Strange time format in olson_parse_offset() ' .$offset); 567 return 0; 568 569 } 570 571 572 /** 573 * olson_parse_on_($on) 574 * 575 * see `man zic`. This function translates the following formats 576 * 5 the fifth of the month 577 * lastSun the last Sunday in the month 578 * lastMon the last Monday in the month 579 * Sun>=8 first Sunday on or after the eighth 580 * Sun<=25 last Sunday on or before the 25th 581 * 582 * to a moodle friendly format. Returns an array with: 583 * 584 * startday: the day of the month that we start counting from. 585 * if negative, it means we start from that day and 586 * count backwards. since -1 would be meaningless, 587 * it means "end of month and backwards". 588 * weekday: the day of the week that we must find. we will 589 * scan days from the startday until we find the 590 * first such weekday. 0...6 = Sun...Sat. 591 * -1 means that any day of the week will do, 592 * effectively ending the search on startday. 593 * skipweeks:after finding our end day as outlined above, 594 * skip this many weeks. this enables us to find 595 * "the second sunday >= 10". usually will be 0. 596 */ 597 function olson_parse_on ($on) { 598 599 $rule = array(); 600 $days = array('sun' => 0, 'mon' => 1, 601 'tue' => 2, 'wed' => 3, 602 'thu' => 4, 'fri' => 5, 603 'sat' => 6); 604 605 if(is_numeric($on)) { 606 $rule['startday'] = intval($on); // Start searching from that day 607 $rule['weekday'] = -1; // ...and stop there, no matter what weekday 608 $rule['skipweeks'] = 0; // Don't skip any weeks. 609 } 610 else { 611 $on = strtolower($on); 612 if(substr($on, 0, 4) == 'last') { 613 // e.g. lastSun 614 if(!isset($days[substr($on, 4)])) { 615 trigger_error('Unknown last weekday: '.substr($on, 4)); 616 } 617 else { 618 $rule['startday'] = -1; // Start from end of month 619 $rule['weekday'] = $days[substr($on, 4)]; // Find the first such weekday 620 $rule['skipweeks'] = 0; // Don't skip any weeks. 621 } 622 } 623 else if(substr($on, 3, 2) == '>=') { 624 // e.g. Sun>=8 625 if(!isset($days[substr($on, 0, 3)])) { 626 trigger_error('Unknown >= weekday: '.substr($on, 0, 3)); 627 } 628 else { 629 $rule['startday'] = intval(substr($on, 5)); // Start from that day of the month 630 $rule['weekday'] = $days[substr($on, 0, 3)]; // Find the first such weekday 631 $rule['skipweeks'] = 0; // Don't skip any weeks. 632 } 633 } 634 else if(substr($on, 3, 2) == '<=') { 635 // e.g. Sun<=25 636 if(!isset($days[substr($on, 0, 3)])) { 637 trigger_error('Unknown <= weekday: '.substr($on, 0, 3)); 638 } 639 else { 640 $rule['startday'] = -intval(substr($on, 5)); // Start from that day of the month; COUNT BACKWARDS (minus sign) 641 $rule['weekday'] = $days[substr($on, 0, 3)]; // Find the first such weekday 642 $rule['skipweeks'] = 0; // Don't skip any weeks. 643 } 644 } 645 else { 646 trigger_error('unknown on '.$on); 647 } 648 } 649 return $rule; 650 } 651 652 653 /** 654 * olson_parse_at($at, $set, $gmtoffset) 655 * 656 * see `man zic`. This function translates 657 * 658 * 2 time in hours 659 * 2:00 time in hours and minutes 660 * 15:00 24-hour format time (for times after noon) 661 * 1:28:14 time in hours, minutes, and seconds 662 * 663 * Any of these forms may be followed by the letter w if the given 664 * time is local "wall clock" time, s if the given time is local 665 * "standard" time, or u (or g or z) if the given time is univer- 666 * sal time; in the absence of an indicator, wall clock time is 667 * assumed. 668 * 669 * @return string a moodle friendly $at, in GMT, which is what Moodle wants 670 * 671 * 672 */ 673 function olson_parse_at ($at, $set = 'set', $gmtoffset) { 674 675 // find the time "signature"; 676 $sig = ''; 677 if (preg_match('/[ugzs]$/', $at, $matches)) { 678 $sig = $matches[0]; 679 $at = substr($at, 0, strlen($at)-1); // chop 680 } 681 682 $at = (strpos($at, ':') === false) ? $at . ':0' : $at; 683 list($hours, $mins) = explode(':', $at); 684 685 // GMT -- return as is! 686 if ( !empty($sig) && ( $sig === 'u' 687 || $sig === 'g' 688 || $sig === 'z' )) { 689 return $at; 690 } 691 692 // Wall clock 693 if (empty($sig) || $sig === 'w') { 694 if ($set !== 'set'){ // wall clock is on DST, assume by 1hr 695 $hours = $hours-1; 696 } 697 $sig = 's'; 698 } 699 700 // Standard time 701 if (!empty($sig) && $sig === 's') { 702 $mins = $mins + $hours*60 + $gmtoffset; 703 $hours = $mins / 60; 704 $hours = (int)$hours; 705 $mins = abs($mins % 60); 706 return sprintf('%02d:%02d', $hours, $mins); 707 } 708 709 trigger_error('unhandled case - AT flag is ' . $matches[0]); 710 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |