? sites/all/modules
? sites/default/files
? sites/default/settings.pgsql.php
? sites/default/settings.php
Index: CHANGELOG.txt
===================================================================
RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v
retrieving revision 1.264
diff -u -p -r1.264 CHANGELOG.txt
--- CHANGELOG.txt	22 Apr 2008 09:18:47 -0000	1.264
+++ CHANGELOG.txt	30 Apr 2008 09:45:35 -0000
@@ -15,6 +15,13 @@ Drupal 7.0, xxxx-xx-xx (development vers
     * Added support for language-aware searches.
 - Testing:
     * Added test framework and tests.
+- Improved time zone support:
+    * Drupal now uses PHP's time zone database when rendering dates in local
+      time. Site-wide and user-configured time zone offsets have been converted
+      to time zone names, e.g. America/Buenos_Aires.
+    * In some cases the upgrade and install scripts do not choose the preferred
+      site or user time zone. The automatically-selected time zone can be
+      corrected at admin/settings/date-time or on the user edit page.
 - Removed ping module:
     * Contributed modules with similar functionality are available.
 - Refactored the "access rules" component of user module:
Index: install.php
===================================================================
RCS file: /cvs/drupal/drupal/install.php,v
retrieving revision 1.117
diff -u -p -r1.117 install.php
--- install.php	14 Apr 2008 17:48:33 -0000	1.117
+++ install.php	30 Apr 2008 09:45:36 -0000
@@ -1047,7 +1047,7 @@ function install_configure_form(&$form_s
   $form['server_settings']['date_default_timezone'] = array(
     '#type' => 'select',
     '#title' => st('Default time zone'),
-    '#default_value' => 0,
+    '#default_value' => date_default_timezone_get(),
     '#options' => _system_zonelist(),
     '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'),
     '#weight' => 5,
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.764
diff -u -p -r1.764 common.inc
--- includes/common.inc	14 Apr 2008 17:48:33 -0000	1.764
+++ includes/common.inc	30 Apr 2008 09:45:39 -0000
@@ -1141,7 +1141,7 @@ function format_interval($timestamp, $gr
  *   before a character to avoid interpreting the character as part of a date
  *   format.
  * @param $timezone
- *   Time zone offset in seconds; if omitted, the user's time zone is used.
+ *   Time zone identifier; if omitted, the user's time zone is used.
  * @param $langcode
  *   Optional language code to translate to a language other than what is used
  *   to display the page.
@@ -1149,17 +1149,21 @@ function format_interval($timestamp, $gr
  *   A translated date string in the requested format.
  */
 function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
-  if (!isset($timezone)) {
+  static $timezones = array();
+  if (!$timezone) {
     global $user;
-    if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
+    if (variable_get('configurable_timezones', 1) && $user->uid && $user->timezone) {
       $timezone = $user->timezone;
     }
     else {
-      $timezone = variable_get('date_default_timezone', 0);
+      $timezone = variable_get('date_default_timezone', 'UTC');
     }
   }
-
-  $timestamp += $timezone;
+  // Store DateTimeZone objects in an array rather than repeatedly 
+  // contructing identical objects over the life of a request.
+  if (!isset($timezones[$timezone])) {
+    $timezones[$timezone] = timezone_open($timezone);
+  }
 
   switch ($type) {
     case 'small':
@@ -1178,28 +1182,27 @@ function format_date($timestamp, $type =
 
   $max = strlen($format);
   $date = '';
+  // Create a DateTime object from the timestamp.
+  $date_time = date_create('@' . $timestamp);
+  // Set the time zone for the DateTime object.
+  date_timezone_set($date_time, $timezones[$timezone]);
+
   for ($i = 0; $i < $max; $i++) {
     $c = $format[$i];
-    if (strpos('AaDlM', $c) !== FALSE) {
-      $date .= t(gmdate($c, $timestamp), array(), $langcode);
+    if (strpos('AaeDlMT', $c) !== FALSE) {
+      $date .= t(date_format($date_time, $c), array(), $langcode);
     }
     else if ($c == 'F') {
       // Special treatment for long month names: May is both an abbreviation
       // and a full month name in English, but other languages have
       // different abbreviations.
-      $date .= trim(t('!long-month-name ' . gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode));
+      $date .= trim(t('!long-month-name ' . date_format($date_time, $c), array('!long-month-name' => ''), $langcode));
     }
-    else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
-      $date .= gmdate($c, $timestamp);
+    else if (strpos('BcdGgHhIijLmNnOoPSstUuWwYyZz', $c) !== FALSE) {
+      $date .= date_format($date_time, $c);
     }
     else if ($c == 'r') {
-      $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode);
-    }
-    else if ($c == 'O') {
-      $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60);
-    }
-    else if ($c == 'Z') {
-      $date .= $timezone;
+      $date .= format_date($timestamp, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode);
     }
     else if ($c == '\\') {
       $date .= $format[++$i];
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.73
diff -u -p -r1.73 system.admin.inc
--- modules/system/system.admin.inc	30 Apr 2008 06:48:27 -0000	1.73
+++ modules/system/system.admin.inc	30 Apr 2008 09:45:41 -0000
@@ -1546,7 +1546,7 @@ function system_date_time_settings() {
   $form['locale']['date_default_timezone'] = array(
     '#type' => 'select',
     '#title' => t('Default time zone'),
-    '#default_value' => variable_get('date_default_timezone', 0),
+    '#default_value' => variable_get('date_default_timezone', date_default_timezone_get()),
     '#options' => $zones,
     '#description' => t('Select the default site time zone.')
   );
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.250
diff -u -p -r1.250 system.install
--- modules/system/system.install	16 Apr 2008 11:35:52 -0000	1.250
+++ modules/system/system.install	30 Apr 2008 09:45:43 -0000
@@ -2861,6 +2861,29 @@ function system_update_7005() {
 }
 
 /**
+ * Convert default time zone offset to default time zone name.
+ */
+function system_update_7006() {
+  $ret = array();
+  // If the contributed date module set a default time zone name, use 
+  // this setting as the default time zone.
+  if ($timezone = variable_get('date_default_timezone_name', NULL)) {
+    variable_set('date_default_timezone', $timezone);
+  }
+  // If the previous default time zone was a non-zero offset, guess the 
+  // site's intended time zone based on that offset and the server's 
+  // daylight saving time status.
+  elseif ($timezone = variable_get('date_default_timezone', 0)) {
+    variable_set('date_default_timezone', timezone_name_from_abbr('', intval($timezone), intval(date('I'))));
+  }
+  // Otherwise, set the default time zone to UTC.
+  else {
+    variable_set('date_default_timezone', 'UTC');    
+  }
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.599
diff -u -p -r1.599 system.module
--- modules/system/system.module	23 Apr 2008 20:01:54 -0000	1.599
+++ modules/system/system.module	30 Apr 2008 09:45:45 -0000
@@ -726,7 +726,7 @@ function system_user($type, $edit, &$use
       $form['timezone']['timezone'] = array(
         '#type' => 'select',
         '#title' => t('Time zone'),
-        '#default_value' => strlen($edit['timezone']) ? $edit['timezone'] : variable_get('date_default_timezone', 0),
+        '#default_value' => $edit['timezone'] ? $edit['timezone'] : variable_get('date_default_timezone', 'UTC'),
         '#options' => $zones,
         '#description' => t('Select your current local time. Dates and times throughout this site will be displayed using this time zone.'),
       );
@@ -2031,11 +2031,15 @@ function system_block_ip_action() {
  */
 function _system_zonelist() {
   $timestamp = time();
-  $zonelist = array(-11, -10, -9.5, -9, -8, -7, -6, -5, -4, -3.5, -3, -2, -1, 0, 1, 2, 3, 3.5, 4, 5, 5.5, 5.75, 6, 6.5, 7, 8, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.75, 13, 14);
+  $zonelist = timezone_identifiers_list();
   $zones = array();
-  foreach ($zonelist as $offset) {
-    $zone = $offset * 3600;
-    $zones[$zone] = format_date($timestamp, 'custom', variable_get('date_format_long', 'l, F j, Y - H:i') . ' O', $zone);
+  foreach ($zonelist as $zone) {
+    // Because many time zones exist in PHP only for backward 
+    // compatibility reasons and should not be used, the list is 
+    // filtered by a regular expression.
+    if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
+      $zones[$zone] = t('@zone: @date', array('@zone' => t($zone), '@date' => format_date($timestamp, 'custom', variable_get('date_format_long', 'l, F j, Y - H:i') . ' O', $zone)));
+    }
   }
   return $zones;
 }
Index: modules/system/system.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.test,v
retrieving revision 1.1
diff -u -p -r1.1 system.test
--- modules/system/system.test	20 Apr 2008 18:23:31 -0000	1.1
+++ modules/system/system.test	30 Apr 2008 09:45:45 -0000
@@ -126,3 +126,44 @@ class EnableDisableCoreTestCase extends 
     }
   }
 }
+
+class DateTimeTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Date and time functionality'),
+      'description' => t('Configure date and time settings. Test date formatting and time zone handling, including daylight saving time.'),
+      'group' => t('System'),
+    );
+  }
+
+  function testDateTimeZone() {
+    // Setup date/time settings for Honolulu time.
+    variable_set('date_default_timezone', 'Pacific/Honolulu');
+    variable_set('configurable_timezones', 0);
+    variable_set('date_format_medium', 'Y-m-d H:i:s O');
+
+    // Create some nodes with different authored-on dates.
+    $date1 = '2007-01-31 21:00:00 -1000';
+    $date2 = '2007-07-31 21:00:00 -1000';
+    $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article'));
+    $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article'));
+
+    // Confirm date format and time zone.
+    $this->drupalGet("node/$node1->nid");
+    $this->assertText('2007-01-31 21:00:00 -1000', 'Date should be identical, with GMT offset of -10 hours.');
+    $this->drupalGet("node/$node2->nid");
+    $this->assertText('2007-07-31 21:00:00 -1000', 'Date should be identical, with GMT offset of -10 hours.');
+
+    // Set time zone to Los Angeles time.
+    variable_set('date_default_timezone', 'America/Los_Angeles');
+
+    // Confirm date format and time zone.
+    $this->drupalGet("node/$node1->nid");
+    $this->assertText('2007-01-31 23:00:00 -0800', 'Date should be two hours ahead, with GMT offset of -8 hours.');
+    $this->drupalGet("node/$node2->nid");
+    $this->assertText('2007-08-01 00:00:00 -0700', 'Date should be three hours ahead, with GMT offset of -7 hours.');
+  }
+}
Index: modules/user/user.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.install,v
retrieving revision 1.11
diff -u -p -r1.11 user.install
--- modules/user/user.install	16 Apr 2008 11:26:29 -0000	1.11
+++ modules/user/user.install	30 Apr 2008 09:45:46 -0000
@@ -170,7 +170,7 @@ function user_schema() {
       ),
       'timezone' => array(
         'type' => 'varchar',
-        'length' => 8,
+        'length' => 32,
         'not null' => FALSE,
         'description' => t("User's timezone."),
       ),
@@ -304,6 +304,33 @@ function user_update_7001() {
 }
 
 /**
+ * Convert user time zones from time zone offsets to time zone names.
+ */
+function user_update_7002() {
+  $ret = array();
+  db_change_field($ret, 'users', 'timezone', 'timezone', array('type' => 'varchar', 'length' => 32, 'not null' => FALSE, 'description' => t("User's timezone.")));
+  // If the contributed date module has created a users.timezone_name 
+  // column, use this data to set each user's time zone.
+  if (db_column_exists('users', 'timezone_name')) {
+    $ret[] = update_sql("UPDATE {users} SET timezone = timezone_name");
+  }
+  else {
+    $result = db_query("SELECT timezone FROM {users} WHERE LENGTH(timezone) > 0 GROUP BY timezone");
+    while ($user_offset = db_fetch_object($result)) {
+      // It is impossible to determine the time zone correctly from the 
+      // offset alone.  We can throw a little extra data at the problem 
+      // by also providing the server's daylight saving time status; 
+      // this, however, may not match the user's daylight saving time 
+      // status.
+      $timezone = timezone_name_from_abbr('', intval($user_offset->timezone), intval(date('I')));
+      db_query("UPDATE {users} SET timezone = '%s' WHERE timezone = '%s'", $timezone, $user_offset->timezone);
+      $ret[] = array('success' => TRUE, 'query' => check_plain("UPDATE {users} SET timezone = '$timezone' WHERE timezone = '$user_offset->timezone'"));
+    }
+  }
+  return $ret;
+}
+
+/**
  * @} End of "defgroup user-updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/user/user.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.js,v
retrieving revision 1.6
diff -u -p -r1.6 user.js
--- modules/user/user.js	12 Sep 2007 18:29:32 -0000	1.6
+++ modules/user/user.js	30 Apr 2008 09:45:46 -0000
@@ -168,11 +168,50 @@ Drupal.evaluatePasswordStrength = functi
 };
 
 /**
- * Set the client's system timezone as default values of form fields.
+ * Set the client's system time zone as default values of form fields.
  */
 Drupal.setDefaultTimezone = function() {
-  var offset = new Date().getTimezoneOffset() * -60;
-  $("#edit-date-default-timezone, #edit-user-register-timezone").val(offset);
+  // In some client environments, date strings include a time zone 
+  // abbreviation which can be interpreted by PHP.
+  var matches = Date().match(/\(([A-Z]{3,5})\)/);
+  var abbreviation = matches ? matches[1] : 0;
+
+  // For all other client environments, the abbreviation is set to "0" 
+  // and the current offset from UTC and daylight saving time status are 
+  // used to guess the time zone.
+  var dateNow = new Date();
+  var offsetNow = dateNow.getTimezoneOffset() * -60;
+
+  // Use January 1 and July 1 as test dates for determining daylight 
+  // saving time status by comparing their offsets.
+  var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);
+  var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);
+  var offsetJan = dateJan.getTimezoneOffset() * -60;
+  var offsetJul = dateJul.getTimezoneOffset() * -60;
+
+  // If the offset from UTC is identical on January 1 and July 1, 
+  // assume daylight saving time is not used in this time zone.
+  if (offsetJan == offsetJul) {
+    var isDaylightSavingTime = '';
+  }
+  // If the maximum annual offset is equivalent to the current offset, 
+  // assume daylight saving time is in effect.
+  else if (Math.max(offsetJan, offsetJul) == offsetNow) {
+    var isDaylightSavingTime = 1;
+  }
+  // Otherwise, assume daylight saving time is not in effect.
+  else {
+    var isDaylightSavingTime = 0;
+  }
+
+  // Submit request to the user/timezone callback and set the form field 
+  // to the response time zone.
+  var path = 'user/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;
+  $.getJSON(Drupal.settings.basePath, { q: path }, function (data) {
+    if (data) {
+      $("#edit-date-default-timezone, #edit-user-register-timezone").val(data);
+    }
+  });
 };
 
 /**
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.904
diff -u -p -r1.904 user.module
--- modules/user/user.module	23 Apr 2008 20:01:56 -0000	1.904
+++ modules/user/user.module	30 Apr 2008 09:45:48 -0000
@@ -855,6 +855,14 @@ function user_menu() {
     'file' => 'user.pages.inc',
   );
 
+  $items['user/timezone'] = array(
+    'title' => 'User timezone',
+    'page callback' => 'user_timezone',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'user.pages.inc',
+  );
+
   // Registration and login pages.
   $items['user'] = array(
     'title' => 'User account',
Index: modules/user/user.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v
retrieving revision 1.13
diff -u -p -r1.13 user.pages.inc
--- modules/user/user.pages.inc	14 Apr 2008 17:48:43 -0000	1.13
+++ modules/user/user.pages.inc	30 Apr 2008 09:45:48 -0000
@@ -22,6 +22,17 @@ function user_autocomplete($string = '')
 }
 
 /**
+ * Menu callback; Retrieve a JSON object containing a suggested time 
+ * zone name.
+ */
+function user_timezone($abbreviation = '', $offset = -1, $is_daylight_saving_time = NULL) {
+  // An abbreviation of "0" passed in the callback arguments should be 
+  // interpreted as the empty string.
+  $abbreviation = $abbreviation ? $abbreviation : '';
+  drupal_json(timezone_name_from_abbr($abbreviation, intval($offset), $is_daylight_saving_time));
+}
+
+/**
  * Form builder; Request a password reset.
  *
  * @ingroup forms
Index: modules/user/user.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.test,v
retrieving revision 1.2
diff -u -p -r1.2 user.test
--- modules/user/user.test	30 Apr 2008 06:43:30 -0000	1.2
+++ modules/user/user.test	30 Apr 2008 09:45:48 -0000
@@ -23,9 +23,9 @@ class UserRegistrationTestCase extends D
     // Set user registration to "Visitors can create accounts and no administrator approval is required."
     variable_set('user_register', 1);
 
-    // Enable user configurable timezone, and set the default timezone to +1 hour (or +3600 seconds).
+    // Enable user-configurable time zones, and set the default time zone to Brussels time.
     variable_set('configurable_timezones', 1);
-    variable_set('date_default_timezone', 3600);
+    variable_set('date_default_timezone', 'Europe/Brussels');
 
     $edit = array();
     $edit['name'] = $name = $this->randomName();
@@ -207,3 +207,59 @@ class UserDeleteTestCase extends DrupalW
     $this->assertFalse(user_load($edit), t('User is not found in the database'));
   }
 }
+
+
+class DateTimeZoneTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('User time zone'),
+      'description' => t('Set a user time zone and verify that dates are displayed in local time.'),
+      'group' => t('User'),
+    );
+  }
+
+  function testDateTimeZone() {
+    // Setup date/time settings for Los Angeles time.
+    variable_set('date_default_timezone', 'America/Los_Angeles');
+    variable_set('configurable_timezones', 1);
+    variable_set('date_format_medium', 'Y-m-d H:i T');
+
+    // Create a user account and login.
+    $web_user = $this->drupalCreateUser();
+    $this->drupalLogin($web_user);
+
+    // Create some nodes with different authored-on dates.
+    $date1 = '2007-03-09 21:00:00 -0800';
+    $date2 = '2007-03-11 01:00:00 -0800';
+    $date3 = '2007-03-20 21:00:00 -0700';
+    $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article'));
+    $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article'));
+    $node3 = $this->drupalCreateNode(array('created' => strtotime($date3), 'type' => 'article'));
+
+    // Confirm date format and time zone.
+    $this->drupalGet("node/$node1->nid");
+    $this->assertText('2007-03-09 21:00 PST', 'Date should be PST.');
+    $this->drupalGet("node/$node2->nid");
+    $this->assertText('2007-03-11 01:00 PST', 'Date should be PST.');
+    $this->drupalGet("node/$node3->nid");
+    $this->assertText('2007-03-20 21:00 PDT', 'Date should be PDT.');
+
+    // Change user time zone to Santiago time.
+    $edit = array();
+    $edit['mail'] = $web_user->mail;
+    $edit['timezone'] = 'America/Santiago';
+    $this->drupalPost("user/$web_user->uid/edit", $edit, t('Save'));
+    $this->assertText(t('The changes have been saved.'), t('Time zone changed to Santiago time.'));
+
+    // Confirm date format and time zone.
+    $this->drupalGet("node/$node1->nid");
+    $this->assertText('2007-03-10 02:00 CLST', 'Date should be Chile summer time; five hours ahead of PST.');
+    $this->drupalGet("node/$node2->nid");
+    $this->assertText('2007-03-11 05:00 CLT', 'Date should be Chile time; four hours ahead of PST');
+    $this->drupalGet("node/$node3->nid");
+    $this->assertText('2007-03-21 00:00 CLT', 'Date should be Chile time; three hours ahead of PDT.');
+  }
+}
