Added initial support for DST. Rules are defined in constants.php and are extensible.
authorDan
Sun, 21 Sep 2008 09:01:27 -0400
changeset 711 f70d764aab33
parent 710 1fbce408813c
child 713 6865705ec8a6
Added initial support for DST. Rules are defined in constants.php and are extensible.
includes/common.php
includes/constants.php
includes/functions.php
includes/sessions.php
install/schemas/mysql_stage2.sql
install/schemas/postgresql_stage2.sql
install/schemas/upgrade/1.1.4-1.1.5-mysql.sql
install/schemas/upgrade/1.1.4-1.1.5-postgresql.sql
language/english/core.json
language/english/user.json
plugins/SpecialUserPrefs.php
--- a/includes/common.php	Tue Sep 16 08:22:47 2008 -0400
+++ b/includes/common.php	Sun Sep 21 09:01:27 2008 -0400
@@ -170,6 +170,10 @@
 global $timezone;
 $timezone = 0;
 
+// DST settings
+global $dst_params;
+$dst_params = array(0, 0, 0, 0, 60);
+
 // Divert to CLI loader if running from CLI
 if ( isset($argc) && isset($argv) )
 {
@@ -390,7 +394,7 @@
   // One quick security check...
   if ( !is_valid_ip($_SERVER['REMOTE_ADDR']) )
   {
-    die('SECURITY: spoofed IP address');
+    die('SECURITY: spoofed IP address: ' . htmlspecialchars($_SERVER['REMOTE_ADDR']));
   }
 
   // All checks passed! Start the main components up.  
--- a/includes/constants.php	Tue Sep 16 08:22:47 2008 -0400
+++ b/includes/constants.php	Sun Sep 21 09:01:27 2008 -0400
@@ -92,6 +92,12 @@
 define('TOKEN_PARENTHRIGHT', 4);
 define('TOKEN_NOT', 5);
 
+// DST constants
+define('FIRST_SUNDAY', 1);
+define('SECOND_SUNDAY', 2);
+define('THIRD_SUNDAY', 3);
+define('LAST_SUNDAY', 4);
+
 //
 // User types - don't touch these
 //
@@ -617,3 +623,12 @@
   0xFE =>  "COM",   0x01 =>  "TEM",   0x02 =>  "RES",
 );
 
+// DST profiles
+global $dst_profiles;
+$dst_profiles = array(
+    'off' => '0;0;0;0;60',
+    'usa' => '3;' . SECOND_SUNDAY . ';11;' . FIRST_SUNDAY . ';60',
+    'europe' => '3;' . LAST_SUNDAY . ';10;' . LAST_SUNDAY . ';60',
+    'australia' => '10;' . LAST_SUNDAY . ';3;' . LAST_SUNDAY . ';60',
+    'tasmania' => '10;' . FIRST_SUNDAY . ';3;' . LAST_SUNDAY . ';60'
+  );
--- a/includes/functions.php	Tue Sep 16 08:22:47 2008 -0400
+++ b/includes/functions.php	Sun Sep 21 09:01:27 2008 -0400
@@ -271,31 +271,117 @@
   if ( !is_int($timestamp) && !is_double($timestamp) && strval(intval($timestamp)) !== $timestamp )
     $timestamp = time();
   
-  /*
-  // List of valid characters for date()
-  $date_chars = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZFcrU';
-  // Split them into an array
-  $date_chars = enano_str_split($date_chars);
-  // Emulate date() formatting by replacing date characters with their
-  // percentage-signed counterparts, but not escaped characters which
-  // shouldn't be parsed.
-  foreach ( $date_chars as $char )
-  {
-    $string = str_replace($char, "%$char", $string);
-    $string = str_replace("\\%$char", $char, $string);
-  }
-  */
-  
   // perform timestamp offset
   global $timezone;
   // it's gonna be in minutes, so multiply by 60 to offset the unix timestamp
   $timestamp = $timestamp + ( $timezone * 60 );
   
+  // are we in DST?
+  global $dst_params;
+  if ( check_timestamp_dst($timestamp, $dst_params[0], $dst_params[1], $dst_params[2], $dst_params[3]) )
+  {
+    // offset for DST
+    $timestamp += ( $dst_params[4] * 60 );
+  }
+  
   // Let PHP do the work for us =)
   return gmdate($string, $timestamp);
 }
 
 /**
+ * Determine if a timestamp is within DST.
+ * @param int Timestamp
+ * @param int Start month (1-12) of DST
+ * @param int Which Sunday DST starts on (*_SUNDAY constants)
+ * @param int End month of DST
+ * @param int Which Sunday DST ends on
+ * @return bool
+ */
+
+function check_timestamp_dst($time, $start_month, $start_sunday, $end_month, $end_sunday)
+{
+  static $sundays = array(FIRST_SUNDAY, SECOND_SUNDAY, THIRD_SUNDAY, LAST_SUNDAY);
+  
+  // perform timestamp offset
+  global $timezone;
+  // it's gonna be in minutes, so multiply by 60 to offset the unix timestamp
+  $time = $time + ( $timezone * 60 );
+  $year = intval(gmdate('Y', $time));
+  
+  // one-pass validation
+  if ( !in_array($start_sunday, $sundays) || !in_array($end_sunday, $sundays) ||
+       $start_month < 1 || $start_month > 12 || $end_month < 1 || $end_month > 12 )
+    return false;
+    
+  // get timestamp of the selected sunday (start)
+  $dst_start = get_sunday_timestamp($start_month, $start_sunday, $year);
+  $dst_end   = get_sunday_timestamp($end_month, $end_sunday, $year);
+  
+  if ( $dst_start > $dst_end )
+  {
+    // start time is past the end time, this means we're in the southern hemisphere
+    // as a result, if we're within the range, DST is NOT in progress.
+    return !( $time >= $dst_start && $time <= $dst_end );
+  }
+  
+  return $time >= $dst_start && $time <= $dst_end;
+}
+
+/**
+ * Returns a timestamp for the given *_SUNDAY index.
+ * @param int Month
+ * @param int Which Sunday (FIRST, SECOND, THIRD, or LAST)
+ * @param int Year that we're doing our calculations in
+ * @return int
+ */
+
+function get_sunday_timestamp($month, $sunday, $year)
+{
+  $days_in_month = array(
+    1 => 31,
+    2 => $year % 4 == 0 && ( $year % 100 != 0 || ( $year % 100 == 0 && $year % 400 == 0 ) ) ? 29 : 28,
+    3 => 31,
+    4 => 30,
+    5 => 31,
+    6 => 30,
+    7 => 31,
+    8 => 31,
+    9 => 30,
+    10 => 31,
+    11 => 30,
+    12 => 31
+  );
+  
+  $result = mktime(0, 0, 0, $month, 1, $year);
+  
+  // hack. allows a specific day of the month to be set instead of a sunday. not a good place to do this.
+  if ( is_string($sunday) && substr($sunday, -1) === 'd' )
+  {
+    $result += 86400 * ( intval($sunday) - 1);
+    return $result;
+  }
+  
+  $tick = 0;
+  $days_remaining = $days_in_month[$month];
+  while ( true )
+  {
+    if ( date('D', $result) == 'Sun' )
+    {
+      $tick++;
+      if ( ( $tick == 1 && $sunday == FIRST_SUNDAY ) ||
+           ( $tick == 2 && $sunday == SECOND_SUNDAY ) ||
+           ( $tick == 3 && $sunday == THIRD_SUNDAY ) ||
+           ( $sunday == LAST_SUNDAY && $days_remaining < 7 ) )
+        break;
+    }
+    $days_remaining--;
+    $result += 86400;
+  }
+  
+  return $result;
+}
+
+/**
  * Tells you the title for the given page ID string
  * @param string Page ID string (ex: Special:Administration)
  * @param bool Optional. If true, and if the namespace turns out to be something other than Article, the namespace prefix will be prepended to the return value.
--- a/includes/sessions.php	Tue Sep 16 08:22:47 2008 -0400
+++ b/includes/sessions.php	Sun Sep 21 09:01:27 2008 -0400
@@ -473,7 +473,15 @@
           }
         }
         $user = true;
+        
+        // set timezone params
         $GLOBALS['timezone'] = $userdata['user_timezone'];
+        $GLOBALS['dst_params'] = explode(';', $userdata['user_dst']);
+        foreach ( $GLOBALS['dst_params'] as &$parm )
+        {
+          if ( substr($parm, -1) != 'd' )
+            $parm = intval($parm);
+        }
         
         // Set language
         if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
@@ -1038,7 +1046,7 @@
     // using a normal call to $db->sql_query to avoid failing on errors here
     $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,' . "\n"
                              . '    u.reg_time,u.account_active,u.activation_key,u.user_lang,u.user_title,k.source_ip,k.time,k.auth_level,k.key_type,COUNT(p.message_id) AS num_pms,' . "\n"
-                             . '    u.user_timezone, x.* FROM '.table_prefix.'session_keys AS k' . "\n"
+                             . '    u.user_timezone, u.user_dst, x.* FROM '.table_prefix.'session_keys AS k' . "\n"
                              . '  LEFT JOIN '.table_prefix.'users AS u' . "\n"
                              . '    ON ( u.user_id=k.user_id )' . "\n"
                              . '  LEFT JOIN '.table_prefix.'users_extra AS x' . "\n"
@@ -1051,7 +1059,7 @@
     
     if ( !$query && ( defined('IN_ENANO_INSTALL') or defined('IN_ENANO_UPGRADE') ) )
     {
-      $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms, 1440 AS user_timezone, ' . SK_SHORT . ' AS key_type FROM '.table_prefix.'session_keys AS k
+      $query = $this->sql('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,u.reg_time,u.account_active,u.activation_key,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms, 1440 AS user_timezone, \'0;0;0;0;60\' AS user_dst, ' . SK_SHORT . ' AS key_type FROM '.table_prefix.'session_keys AS k
                              LEFT JOIN '.table_prefix.'users AS u
                                ON ( u.user_id=k.user_id )
                              LEFT JOIN '.table_prefix.'privmsgs AS p
@@ -2707,7 +2715,8 @@
       if ( !$q )
         $db->_die();
       
-      $groups = array();
+      // The l10n engine takes care of this later.
+      $groups = array(1 => 'Everyone');
       
       if ( $row = $db->fetchrow() )
       {
@@ -2983,7 +2992,7 @@
     {
       if ( isset($perm2[$i]) )
       {
-        if ( $is_everyone && !$defaults_used[$i] )
+        if ( $is_everyone && isset($defaults_used[$i]) && $defaults_used[$i] === false )
           continue;
         // Decide precedence
         if ( isset($defaults_used[$i]) )
--- a/install/schemas/mysql_stage2.sql	Tue Sep 16 08:22:47 2008 -0400
+++ b/install/schemas/mysql_stage2.sql	Sun Sep 21 09:01:27 2008 -0400
@@ -115,6 +115,7 @@
   user_timezone int(12) UNSIGNED NOT NULL DEFAULT 0,
   user_title varchar(64) DEFAULT NULL,
   user_group mediumint(5) NOT NULL DEFAULT 1,
+  user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60',
   PRIMARY KEY  (user_id)
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
--- a/install/schemas/postgresql_stage2.sql	Tue Sep 16 08:22:47 2008 -0400
+++ b/install/schemas/postgresql_stage2.sql	Sun Sep 21 09:01:27 2008 -0400
@@ -115,6 +115,7 @@
   user_timezone int NOT NULL DEFAULT 0,
   user_title varchar(64) DEFAULT NULL,
   user_group int NOT NULL DEFAULT 1,
+  user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60',
   CHECK (avatar_type IN ('jpg', 'png', 'gif', 'grv')),
   PRIMARY KEY  (user_id)
 );
--- a/install/schemas/upgrade/1.1.4-1.1.5-mysql.sql	Tue Sep 16 08:22:47 2008 -0400
+++ b/install/schemas/upgrade/1.1.4-1.1.5-mysql.sql	Sun Sep 21 09:01:27 2008 -0400
@@ -1,2 +1,3 @@
 ALTER TABLE {{TABLE_PREFIX}}session_keys ADD COLUMN key_type tinyint(1) NOT NULL DEFAULT 0;
 UPDATE {{TABLE_PREFIX}}session_keys SET key_type = 2 WHERE auth_level > 2;
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60';
--- a/install/schemas/upgrade/1.1.4-1.1.5-postgresql.sql	Tue Sep 16 08:22:47 2008 -0400
+++ b/install/schemas/upgrade/1.1.4-1.1.5-postgresql.sql	Sun Sep 21 09:01:27 2008 -0400
@@ -1,3 +1,4 @@
 ALTER TABLE {{TABLE_PREFIX}}session_keys ADD COLUMN key_type smallint NOT NULL DEFAULT 0;
 UPDATE {{TABLE_PREFIX}}session_keys SET key_type = 2 WHERE auth_level > 2;
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_dst varchar(11) NOT NULL DEFAULT '0;0;0;0;60';
 
--- a/language/english/core.json	Tue Sep 16 08:22:47 2008 -0400
+++ b/language/english/core.json	Sun Sep 21 09:01:27 2008 -0400
@@ -692,7 +692,14 @@
       title_13: '[UTC + 13] Tonga Time, Phoenix Islands Time',
       title_14: '[UTC + 14] Line Island Time',
       // This is a JSON string that lists all the timezones that are defined here.
-      list: '{"n12":-12,"n11":-11,"n10":-10,"n9p5":-9.5,"n9":-9,"n8":-8,"n7":-7,"n6":-6,"n5":-5,"n4":-4,"n3p5":-3.5,"n3":-3,"n2":-2,"n1":-1,"0":0,"1":1,"2":2,"3":3,"3p5":3.5,"4":4,"4p5":4.5,"5":5,"5p5":5.5,"5p75":5.75,"6":6,"6p5":6.5,"7":7,"8":8,"8p75":8.75,"9":9,"9p5":9.5,"10":10,"10p5":10.5,"11":11,"11p5":11.5,"12":12,"12p75":12.75,"13":13,"14":14}'
+      list: '{"n12":-12,"n11":-11,"n10":-10,"n9p5":-9.5,"n9":-9,"n8":-8,"n7":-7,"n6":-6,"n5":-5,"n4":-4,"n3p5":-3.5,"n3":-3,"n2":-2,"n1":-1,"0":0,"1":1,"2":2,"3":3,"3p5":3.5,"4":4,"4p5":4.5,"5":5,"5p5":5.5,"5p75":5.75,"6":6,"6p5":6.5,"7":7,"8":8,"8p75":8.75,"9":9,"9p5":9.5,"10":10,"10p5":10.5,"11":11,"11p5":11.5,"12":12,"12p75":12.75,"13":13,"14":14}',
+      
+      // DST profiles
+      dst_off: 'My region doesn\'t use DST',
+      dst_usa: 'United States: second Sunday of March to first Sunday of November',
+      dst_europe: 'Europe: last Sunday of March to last Sunday of October',
+      dst_australia: 'Australia (except Tasmania): last Sunday of October to last Sunday of March',
+      dst_tasmania: 'Tasmania: first Sunday of October to last Sunday of March'
     },
     etc: {
       redirect_title: 'Redirecting...',
--- a/language/english/user.json	Tue Sep 16 08:22:47 2008 -0400
+++ b/language/english/user.json	Sun Sep 21 09:01:27 2008 -0400
@@ -308,6 +308,7 @@
       publicinfo_field_changetheme: 'Change my theme...',
       publicinfo_field_timezone: 'Time zone:',
       publicinfo_field_timezone_hint: 'Select the time zone you live in and when Daylight Savings Time occurs, if at all.',
+      publicinfo_field_dst: 'Daylight saving time:',
       publicinfo_field_usertitle_title: 'User title:',
       publicinfo_field_usertitle_hint: 'This can be some text that will be displayed underneath your username.',
       publicinfo_th_im: 'Instant messenger contact information',
--- a/plugins/SpecialUserPrefs.php	Tue Sep 16 08:22:47 2008 -0400
+++ b/plugins/SpecialUserPrefs.php	Sun Sep 21 09:01:27 2008 -0400
@@ -652,6 +652,22 @@
             <td class="row2"><?php echo $lang->get('usercp_publicinfo_field_timezone'); ?><br /><small><?php echo $lang->get('usercp_publicinfo_field_timezone_hint'); ?></small></td>
             <td class="row1"><?php echo $tz_select; ?></td>
           </tr>
+          <tr>
+            <td class="row2"><?php echo $lang->get('usercp_publicinfo_field_dst'); ?></td>
+            <td class="row1">
+              <select name="dst">
+                <?php
+                global $dst_profiles, $dst_params;
+                $user_dst = implode(';', $dst_params);
+                foreach ( $dst_profiles as $region => $data )
+                {
+                  $selected = ( $data === $user_dst ) ? ' selected="selected"' : '';
+                  echo '<option value="' . $data . '"' . $selected . '>' . $lang->get("tz_dst_$region") . '</option>';
+                }
+                ?>
+              </select>
+            </td>
+          </tr>
           <?php
           if ( $session->get_permissions('custom_user_title') ):
           ?>