Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/olson.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations