Yay! Major DST support progress!
1. calculate_user_dst_table() works correctly and offers many features: Can create or extend on-the-fly a table of pre-computed timestamps on which DST changes happen, keeps track of these changes and the current computed year range in $USER->dstoffsets and $USER->dstrange. Initially the computation is done in a current year +/- 3 years range, to keep the amount of serialized data small and to make traversals of $USER->dstoffsets faster. 2. dst_offset_on() works correctly, and can dynamically instruct the above function to extend the pre-calculated region on demand (i.e., when it's called for a timestamp which falls outside said region). 3. Some other improvements. I have made a few preliminary tests on my dev machine and It Works(tm!) :D
This commit is contained in:
+128
-39
@@ -861,54 +861,118 @@ function get_user_timezone($tz = 99) {
|
||||
return $tz;
|
||||
}
|
||||
|
||||
function calculate_user_dst_table() {
|
||||
function calculate_user_dst_table($from_year = NULL, $to_year = NULL) {
|
||||
global $CFG, $USER;
|
||||
static $presetname = NULL;
|
||||
|
||||
if (!empty($presetname)) {
|
||||
return true;
|
||||
if (empty($USER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($CFG->forcetimezone)) {
|
||||
if (empty($USER->timezonename)) {
|
||||
return false;
|
||||
}
|
||||
$presetname = $USER->timezonename;
|
||||
$timezonename = $USER->timezonename;
|
||||
|
||||
} else {
|
||||
$presetname = $CFG->forcetimezone;
|
||||
$timezonename = $CFG->forcetimezone;
|
||||
}
|
||||
|
||||
$presetrecords = get_records('timezone', 'name', $presetname, 'year ASC');
|
||||
if (!empty($USER->dstoffsets) && empty($from_year) && empty($to_year)) {
|
||||
// Repeat calls which do not request specific year ranges stop here, we have already calculated the table
|
||||
// This will be the return path most of the time, pretty light computationally
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reaching here means we either need to extend our table or create it from scratch
|
||||
if(empty($USER->dstoffsets)) {
|
||||
// If we 're creating from scratch, put the two guard elements in there
|
||||
$USER->dstoffsets = array(1 => NULL, 0 => NULL);
|
||||
}
|
||||
if(empty($USER->dstrange)) {
|
||||
// If creating from scratch
|
||||
$from = max((empty($from_year) ? intval(date('Y')) - 3 : $from_year), 1971);
|
||||
$to = min((empty($to_year) ? intval(date('Y')) + 3 : $to_year), 2035);
|
||||
|
||||
// Fill in the array with the extra years we need to process
|
||||
$yearstoprocess = array();
|
||||
for($i = $from; $i <= $to; ++$i) {
|
||||
$yearstoprocess[] = $i;
|
||||
}
|
||||
|
||||
// Take note of which years we have processed for future calls
|
||||
$USER->dstrange = array($from, $to);
|
||||
}
|
||||
else {
|
||||
// If needing to extend the table, do the same
|
||||
$yearstoprocess = array();
|
||||
|
||||
$from = max((empty($from_year) ? $USER->dstrange[0] : $from_year), 1971);
|
||||
$to = min((empty($to_year) ? $USER->dstrange[1] : $to_year), 2035);
|
||||
|
||||
if($from < $USER->dstrange[0]) {
|
||||
// Take note of which years we need to process and then note that we have processed them for future calls
|
||||
for($i = $from; $i < $USER->dstrange[0]; ++$i) {
|
||||
$yearstoprocess[] = $i;
|
||||
}
|
||||
$USER->dstrange[0] = $from;
|
||||
}
|
||||
if($to > $USER->dstrange[1]) {
|
||||
// Take note of which years we need to process and then note that we have processed them for future calls
|
||||
for($i = $USER->dstrange[1] + 1; $i <= $to; ++$i) {
|
||||
$yearstoprocess[] = $i;
|
||||
}
|
||||
$USER->dstrange[1] = $to;
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($yearstoprocess)) {
|
||||
// This means that there was a call requesting a SMALLER range than we have already calculated
|
||||
return true;
|
||||
}
|
||||
|
||||
// From now on, we know that the array has at least the two guard elements, and $yearstoprocess has the years we need
|
||||
// Also, the array is sorted in descending timestamp order!
|
||||
|
||||
// Get DB data
|
||||
$presetrecords = get_records('timezone', 'name', $timezonename, 'year DESC', 'year, gmtoff, dstoff, dst_month, dst_startday, dst_weekday, dst_skipweeks, dst_time, std_month, std_startday, std_weekday, std_skipweeks, std_time');
|
||||
if(empty($presetrecords)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!empty($USER)) {
|
||||
if(empty($USER->dstoffsets)) {
|
||||
$USER->dstoffsets = array(0 => NULL);
|
||||
foreach($presetrecords as $presetrecord) {
|
||||
$changes = dst_changes_for_year($presetrecord->year, $presetrecord);
|
||||
|
||||
if($changes === NULL) {
|
||||
continue;
|
||||
}
|
||||
if($changes['dst'] != 0) {
|
||||
$USER->dstoffsets[$changes['dst']] = $presetrecord->dstoff * MINSECS;
|
||||
}
|
||||
if($changes['std'] != 0) {
|
||||
$USER->dstoffsets[$changes['std']] = 0;
|
||||
}
|
||||
// Remove ending guard (first element of the array)
|
||||
reset($USER->dstoffsets);
|
||||
unset($USER->dstoffsets[key($USER->dstoffsets)]);
|
||||
|
||||
// Add all required change timestamps
|
||||
foreach($yearstoprocess as $y) {
|
||||
// Find the record which is in effect for the year $y
|
||||
foreach($presetrecords as $year => $preset) {
|
||||
if($year <= $y) {
|
||||
break;
|
||||
}
|
||||
$calcuntil = make_timestamp(2031, 1, 1, 00, 00, 00, get_user_timezone(99), false);
|
||||
$USER->dstoffsets[$calcuntil] = NULL;
|
||||
krsort($USER->dstoffsets);
|
||||
}
|
||||
|
||||
$changes = dst_changes_for_year($y, $preset);
|
||||
|
||||
if($changes === NULL) {
|
||||
continue;
|
||||
}
|
||||
if($changes['dst'] != 0) {
|
||||
$USER->dstoffsets[$changes['dst']] = $preset->dstoff * MINSECS;
|
||||
}
|
||||
if($changes['std'] != 0) {
|
||||
$USER->dstoffsets[$changes['std']] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//print_object($presetrecords);
|
||||
//print_object($USER->dstoffsets);
|
||||
|
||||
// Put in a guard element at the top
|
||||
$maxtimestamp = max(array_keys($USER->dstoffsets));
|
||||
$USER->dstoffsets[($maxtimestamp + DAYSECS)] = NULL; // DAYSECS is arbitrary, any "small" number will do
|
||||
|
||||
// Sort again
|
||||
krsort($USER->dstoffsets);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -925,14 +989,23 @@ function dst_changes_for_year($year, $timezone) {
|
||||
list($std_hour, $std_min) = explode(':', $timezone->std_time);
|
||||
|
||||
$tz = get_user_timezone(99);
|
||||
$timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, $dst_hour, $dst_min, 0, $tz, false);
|
||||
$timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, $std_hour, $std_min, 0, $tz, false);
|
||||
$timedst = make_timestamp($year, $timezone->dst_month, $monthdaydst, 0, 0, 0, $tz, false);
|
||||
$timestd = make_timestamp($year, $timezone->std_month, $monthdaystd, 0, 0, 0, $tz, false);
|
||||
|
||||
// Instead of putting hour and minute in make_timestamp(), we add them afterwards.
|
||||
// This has the advantage of being able to have negative values for hour, i.e. for timezones
|
||||
// where GMT time would be in the PREVIOUS day than the local one on which DST changes.
|
||||
|
||||
$timedst += $dst_hour * HOURSECS + $dst_min * MINSECS;
|
||||
$timestd += $std_hour * HOURSECS + $std_min * MINSECS;
|
||||
|
||||
return array('dst' => $timedst, 0 => $timedst, 'std' => $timestd, 1 => $timestd);
|
||||
}
|
||||
|
||||
// $time must NOT be compensated at all, it has to be a pure timestamp
|
||||
function dst_offset_on($time) {
|
||||
global $USER;
|
||||
|
||||
if(!calculate_user_dst_table()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -941,22 +1014,38 @@ function dst_offset_on($time) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$finaloffset = NULL;
|
||||
|
||||
foreach($USER->dstoffsets as $from => $offset) {
|
||||
reset($USER->dstoffsets);
|
||||
while(list($from, $offset) = each($USER->dstoffsets)) {
|
||||
if($from <= $time) {
|
||||
$finaloffset = $offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($finaloffset === NULL) {
|
||||
// This means we haven't calculated far enough ahead, do it now?
|
||||
print_object($USER->dstoffsets);
|
||||
error('Error in calculating DST offset for timestamp '.$time);
|
||||
// This is the normal return path
|
||||
if($offset !== NULL) {
|
||||
return $offset;
|
||||
}
|
||||
|
||||
return $finaloffset;
|
||||
// Reaching this point means we haven't calculated far enough, do it now:
|
||||
// Calculate extra DST changes if needed and recurse. The recursion always
|
||||
// moves toward the stopping condition, so will always end.
|
||||
|
||||
if($from == 0) {
|
||||
// We need a year smaller than $USER->dstrange[0]
|
||||
if($USER->dstrange[0] == 1971) {
|
||||
return 0;
|
||||
}
|
||||
calculate_user_dst_table($USER->dstrange[0] - 5, NULL);
|
||||
return dst_offset_on($time);
|
||||
}
|
||||
else {
|
||||
// We need a year larger than $USER->dstrange[1]
|
||||
if($USER->dstrange[1] == 2035) {
|
||||
return 0;
|
||||
}
|
||||
calculate_user_dst_table(NULL, $USER->dstrange[1] + 5);
|
||||
return dst_offset_on($time);
|
||||
}
|
||||
}
|
||||
|
||||
function find_day_in_month($startday, $weekday, $month, $year) {
|
||||
|
||||
Reference in New Issue
Block a user