Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v retrieving revision 1.266 diff -u -r1.266 CHANGELOG.txt --- CHANGELOG.txt 7 May 2008 07:05:55 -0000 1.266 +++ CHANGELOG.txt 9 May 2008 02:13:15 -0000 @@ -15,6 +15,13 @@ * 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.118 diff -u -r1.118 install.php --- install.php 6 May 2008 12:18:44 -0000 1.118 +++ install.php 9 May 2008 02:13:15 -0000 @@ -1047,7 +1047,7 @@ $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.765 diff -u -r1.765 common.inc --- includes/common.inc 6 May 2008 12:18:45 -0000 1.765 +++ includes/common.inc 9 May 2008 02:13:15 -0000 @@ -1141,7 +1141,7 @@ * 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 @@ * 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 @@ $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.75 diff -u -r1.75 system.admin.inc --- modules/system/system.admin.inc 7 May 2008 19:17:50 -0000 1.75 +++ modules/system/system.admin.inc 9 May 2008 14:29:40 -0000 @@ -1558,7 +1558,7 @@ $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.') ); @@ -1571,6 +1571,22 @@ '#description' => t('When enabled, users can set their own time zone and dates will be displayed accordingly.') ); + $form['locale']['user_default_timezone'] = array( + '#type' => 'radios', + '#title' => t('User time zone defaults'), + '#default_value' => variable_get('user_default_timezone', 0), + '#options' => array(t('New users will be set to the default time zone at registration.'), t('New users will get an empty time zone at registration.'), t('New users will select their own time zone at registration.')), + '#description' => t('Method for setting user time zones at registration when user-configurable time zones are enabled. This only affects the initial time zone setting for a new registration, users will be able to change their time zone any time they edit their account.') + ); + + $form['locale']['empty_timezone_message'] = array( + '#type' => 'radios', + '#title' => t('Empty user time zones'), + '#default_value' => variable_get('empty_timezone_message', 0), + '#options' => array(t('Ignore empty user time zones.'), t('Remind users at login if their time zone is not set.')), + '#description' => t('Handling for empty user time zones when user-configurable time zones are enabled. Use this option to help ensure that users set the correct time zone.') + ); + $form['locale']['date_first_day'] = array( '#type' => 'select', '#title' => t('First day of week'), Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.253 diff -u -r1.253 system.install --- modules/system/system.install 7 May 2008 19:34:24 -0000 1.253 +++ modules/system/system.install 9 May 2008 14:48:20 -0000 @@ -2995,9 +2995,417 @@ return $ret; } +/** + * Convert default time zone offset to default time zone name. + */ +function system_update_7008() { + $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 contributed Event module has set a default site timezone + // use that information. + elseif ($timezone_id = variable_get('date_default_timezone_id', 0) && !empty($timezone_id)) { + $timezone = db_result(db_query("SELECT name FROM {event_timezone} t WHERE t.timezone=%d", $timezone_id)); + $timezone = str_replace(' ', '_', $timezone); + 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'); + } + drupal_set_message(t('The default time zone has been set to %timezone. Please check !link to configure it correctly.', array('%timezone' => check_plain(variable_get('date_default_timezone', 'UTC')), '!link' => l(t('date and time configuration page'), 'admin/settings/date-time'))), 'warning'); + return $ret; +} /** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ +/** + * These strings exist only for the extractor to pick them up and make them + * available for translation. Putting them here keeps them from being parsed + * on normal pages. + * + * TODO Double-check these just before string freeze to see if there + * have been any changes. + * + t('Africa/Algiers') + t('Africa/Asmera') + t('Africa/Bangui') + t('Africa/Blantyre') + t('Africa/Brazzaville') + t('Africa/Bujumbura') + t('Africa/Cairo') + t('Africa/Ceuta') + t('Africa/Dar_es_Salaam') + t('Africa/Djibouti') + t('Africa/Douala') + t('Africa/Gaborone') + t('Africa/Harare') + t('Africa/Johannesburg') + t('Africa/Kampala') + t('Africa/Khartoum') + t('Africa/Kigali') + t('Africa/Kinshasa') + t('Africa/Lagos') + t('Africa/Libreville') + t('Africa/Luanda') + t('Africa/Lubumbashi') + t('Africa/Lusaka') + t('Africa/Malabo') + t('Africa/Maputo') + t('Africa/Maseru') + t('Africa/Mbabane') + t('Africa/Mogadishu') + t('Africa/Nairobi') + t('Africa/Ndjamena') + t('Africa/Niamey') + t('Africa/Porto-Novo') + t('Africa/Tripoli') + t('Africa/Tunis') + t('Africa/Windhoek') + t('America/Adak') + t('America/Anchorage') + t('America/Anguilla') + t('America/Antigua') + t('America/Araguaina') + t('America/Aruba') + t('America/Asuncion') + t('America/Atka') + t('America/Barbados') + t('America/Belem') + t('America/Belize') + t('America/Boa Vista') + t('America/Bogota') + t('America/Boise') + t('America/Buenos_Aires') + t('America/Cambridge Bay') + t('America/Cancun') + t('America/Caracas') + t('America/Catamarca') + t('America/Cayenne') + t('America/Cayman') + t('America/Chicago') + t('America/Chihuahua') + t('America/Cordoba') + t('America/Costa_Rica') + t('America/Cuiaba') + t('America/Curacao') + t('America/Dawson') + t('America/Dawson_Creek') + t('America/Denver') + t('America/Detroit') + t('America/Dominica') + t('America/Edmonton') + t('America/Eirunepe') + t('America/El_Salvador') + t('America/Ensenada') + t('America/Fort_Wayne') + t('America/Fortaleza') + t('America/Glace_Bay') + t('America/Godthab') + t('America/Goose_Bay') + t('America/Grand_Turk') + t('America/Grenada') + t('America/Guadeloupe') + t('America/Guatemala') + t('America/Guayaquil') + t('America/Guyana') + t('America/Halifax') + t('America/Havana') + t('America/Hermosillo') + t('America/Indiana/Indianapolis') + t('America/Indiana/Knox') + t('America/Indiana/Marengo') + t('America/Indiana/Vevay') + t('America/Indianapolis') + t('America/Inuvik') + t('America/Iqaluit') + t('America/Jamaica') + t('America/Jujuy') + t('America/Juneau') + t('America/Kentucky/Louisville') + t('America/Kentucky/Monticello') + t('America/Knox_IN') + t('America/La_Paz') + t('America/Lima') + t('America/Los_Angeles') + t('America/Louisville') + t('America/Maceio') + t('America/Managua') + t('America/Manaus') + t('America/Martinique') + t('America/Mazatlan') + t('America/Mendoza') + t('America/Menominee') + t('America/Merida') + t('America/Mexico_City') + t('America/Miquelon') + t('America/Monterrey') + t('America/Montevideo') + t('America/Montreal') + t('America/Montserrat') + t('America/Nassau') + t('America/New_York') + t('America/Nipigon') + t('America/Nome') + t('America/Noronha') + t('America/Panama') + t('America/Pangnirtung') + t('America/Paramaribo') + t('America/Phoenix') + t('America/Port-au-Prince') + t('America/Port_of_Spain') + t('America/Porto_Acre') + t('America/Porto_Velho') + t('America/Puerto_Rico') + t('America/Rainy_River') + t('America/Rankin_Inlet') + t('America/Recife') + t('America/Regina') + t('America/Rio_Branco') + t('America/Rosario') + t('America/Santiago') + t('America/Santo_Domingo') + t('America/Sao_Paulo') + t('America/Scoresbysund') + t('America/Shiprock') + t('America/St_Johns') + t('America/St_Kitts') + t('America/St_Lucia') + t('America/St_Thomas') + t('America/St_Vincent') + t('America/Swift_Current') + t('America/Tegucigalpa') + t('America/Thule') + t('America/Thunder_Bay') + t('America/Tijuana') + t('America/Tortola') + t('America/Vancouver') + t('America/Virgin') + t('America/Whitehorse') + t('America/Winnipeg') + t('America/Yakutat') + t('America/Yellowknife') + t('Antarctica/Casey') + t('Antarctica/Davis') + t('Antarctica/DumontDUrville') + t('Antarctica/Mawson') + t('Antarctica/McMurdo') + t('Antarctica/Palmer') + t('Antarctica/South Pole') + t('Antarctica/Syowa') + t('Antarctica/Vostok') + t('Arctic/Longyearbyen') + t('Asia/Aden') + t('Asia/Almaty') + t('Asia/Amman') + t('Asia/Anadyr') + t('Asia/Aqtau') + t('Asia/Aqtobe') + t('Asia/Ashgabat') + t('Asia/Ashkhabad') + t('Asia/Baghdad') + t('Asia/Bahrain') + t('Asia/Baku') + t('Asia/Bangkok') + t('Asia/Beirut') + t('Asia/Bishkek') + t('Asia/Brunei') + t('Asia/Calcutta') + t('Asia/Chungking') + t('Asia/Colombo') + t('Asia/Dacca') + t('Asia/Damascus') + t('Asia/Dhaka') + t('Asia/Dili') + t('Asia/Dubai') + t('Asia/Dushanbe') + t('Asia/Gaza') + t('Asia/Harbin') + t('Asia/Hong Kong') + t('Asia/Hovd') + t('Asia/Irkutsk') + t('Asia/Istanbul') + t('Asia/Jakarta') + t('Asia/Jayapura') + t('Asia/Jerusalem') + t('Asia/Kabul') + t('Asia/Kamchatka') + t('Asia/Karachi') + t('Asia/Kashgar') + t('Asia/Katmandu') + t('Asia/Krasnoyarsk') + t('Asia/Kuala_Lumpur') + t('Asia/Kuching') + t('Asia/Kuwait') + t('Asia/Macao') + t('Asia/Magadan') + t('Asia/Manila') + t('Asia/Muscat') + t('Asia/Nicosia') + t('Asia/Novosibirsk') + t('Asia/Omsk') + t('Asia/Phnom_Penh') + t('Asia/Pyongyang') + t('Asia/Qatar') + t('Asia/Rangoon') + t('Asia/Riyadh') + t('Asia/Riyadh87') + t('Asia/Riyadh88') + t('Asia/Riyadh89') + t('Asia/Saigon') + t('Asia/Samarkand') + t('Asia/Seoul') + t('Asia/Shanghai') + t('Asia/Singapore') + t('Asia/Taipei') + t('Asia/Tashkent') + t('Asia/Tbilisi') + t('Asia/Tehran') + t('Asia/Tel_Aviv') + t('Asia/Thimbu') + t('Asia/Thimphu') + t('Asia/Tokyo') + t('Asia/Ujung_Pandang') + t('Asia/Ulaanbaatar') + t('Asia/Ulan Bator') + t('Asia/Urumqi') + t('Asia/Vientiane') + t('Asia/Vladivostok') + t('Asia/Yakutsk') + t('Asia/Yekaterinburg') + t('Asia/Yerevan') + t('Atlantic/Azores') + t('Atlantic/Bermuda') + t('Atlantic/Canary') + t('Atlantic/Cape Verde') + t('Atlantic/Faeroe') + t('Atlantic/Jan_Mayen') + t('Atlantic/Madeira') + t('Atlantic/South_Georgia') + t('Atlantic/Stanley') + t('Australia/ACT') + t('Australia/Adelaide') + t('Australia/Brisbane') + t('Australia/Broken Hill') + t('Australia/Canberra') + t('Australia/Darwin') + t('Australia/Hobart') + t('Australia/LHI') + t('Australia/Lindeman') + t('Australia/Lord_Howe') + t('Australia/Melbourne') + t('Australia/NSW') + t('Australia/North') + t('Australia/Perth') + t('Australia/Queensland') + t('Australia/South') + t('Australia/Sydney') + t('Australia/Tasmania') + t('Australia/Victoria') + t('Australia/West') + t('Australia/Yancowinna') + t('Europe/Amsterdam') + t('Europe/Andorra') + t('Europe/Athens') + t('Europe/Belfast') + t('Europe/Belgrade') + t('Europe/Berlin') + t('Europe/Bratislava') + t('Europe/Brussels') + t('Europe/Bucharest') + t('Europe/Budapest') + t('Europe/Chisinau') + t('Europe/Copenhagen') + t('Europe/Dublin') + t('Europe/Gibraltar') + t('Europe/Helsinki') + t('Europe/Istanbul') + t('Europe/Kaliningrad') + t('Europe/Kiev') + t('Europe/Lisbon') + t('Europe/Ljubljana') + t('Europe/London') + t('Europe/Luxembourg') + t('Europe/Madrid') + t('Europe/Malta') + t('Europe/Minsk') + t('Europe/Monaco') + t('Europe/Moscow') + t('Europe/Nicosia') + t('Europe/Oslo') + t('Europe/Paris') + t('Europe/Prague') + t('Europe/Riga') + t('Europe/Rome') + t('Europe/Samara') + t('Europe/San_Marino') + t('Europe/Sarajevo') + t('Europe/Simferopol') + t('Europe/Skopje') + t('Europe/Sofia') + t('Europe/Stockholm') + t('Europe/Tallinn') + t('Europe/Tirane') + t('Europe/Tiraspol') + t('Europe/Uzhgorod') + t('Europe/Vaduz') + t('Europe/Vatican') + t('Europe/Vienna') + t('Europe/Vilnius') + t('Europe/Warsaw') + t('Europe/Zagreb') + t('Europe/Zaporozhye') + t('Europe/Zurich') + t('Pacific/Apia') + t('Pacific/Auckland') + t('Pacific/Chatham') + t('Pacific/Easter') + t('Pacific/Efate') + t('Pacific/Enderbury') + t('Pacific/Fakaofo') + t('Pacific/Fiji') + t('Pacific/Funafuti') + t('Pacific/Galapagos') + t('Pacific/Gambier') + t('Pacific/Guadalcanal') + t('Pacific/Guam') + t('Pacific/Honolulu') + t('Pacific/Johnston') + t('Pacific/Kiritimati') + t('Pacific/Kosrae') + t('Pacific/Kwajalein') + t('Pacific/Majuro') + t('Pacific/Marquesas') + t('Pacific/Midway') + t('Pacific/Nauru') + t('Pacific/Niue') + t('Pacific/Norfolk') + t('Pacific/Noumea') + t('Pacific/Pago Pago') + t('Pacific/Palau') + t('Pacific/Pitcairn') + t('Pacific/Ponape') + t('Pacific/Port Moresby') + t('Pacific/Rarotonga') + t('Pacific/Saipan') + t('Pacific/Samoa') + t('Pacific/Tahiti') + t('Pacific/Tarawa') + t('Pacific/Tongatapu') + t('Pacific/Truk') + t('Pacific/Wake') + t('Pacific/Wallis') + t('Pacific/Yap') + */ \ No newline at end of file Index: modules/system/system.js =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.js,v retrieving revision 1.15 diff -u -r1.15 system.js --- modules/system/system.js 8 Feb 2008 02:50:19 -0000 1.15 +++ modules/system/system.js 9 May 2008 12:50:57 -0000 @@ -111,3 +111,53 @@ // Trigger the event handler to show the form input if necessary. $('select.date-format', context).trigger('change'); }; + +/** + * Set the client's system time zone as default values of form fields. + */ +Drupal.setDefaultTimezone = function() { + var dateString = Date(); + // In some client environments, date strings include a time zone + // abbreviation, between 3 and 5 letters enclosed in parentheses, + // which can be interpreted by PHP. + var matches = dateString.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 = 'date/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime; + // The client date is passed to the callback for debugging purposes. + $.getJSON(Drupal.settings.basePath, { q: path, date: dateString }, function (data) { + if (data) { + $("#edit-date-default-timezone, #edit-timezone").val(data); + } + }); +}; \ No newline at end of file Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.602 diff -u -r1.602 system.module --- modules/system/system.module 7 May 2008 19:17:50 -0000 1.602 +++ modules/system/system.module 9 May 2008 14:15:45 -0000 @@ -326,6 +326,12 @@ 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['date/timezone'] = array( + 'title' => 'Timezone', + 'page callback' => 'date_timezone', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['admin'] = array( 'title' => 'Administer', 'access arguments' => array('access administration pages'), @@ -690,10 +696,25 @@ * Allows users to individually set their theme and time zone. */ function system_user($type, $edit, &$user, $category = NULL) { + if ($type == 'form' && $category == 'account') { $form['theme_select'] = system_theme_select_form(t('Selecting a different theme will change the look and feel of the site.'), isset($edit['theme']) ? $edit['theme'] : NULL, 2); - - if (variable_get('configurable_timezones', 1)) { + } + if (variable_get('configurable_timezones', 1) && ($type == 'register' || ($type == 'form' && $category == 'account'))) { + if ($type == 'register' && variable_get('user_default_timezone', 0) < 2) { + $form['timezone'] = array( + '#type' => 'hidden', + '#default_value' => variable_get('user_default_timezone', 0) == 0 ? variable_get('date_default_timezone', NULL) : NULL, + ); + } + else { + if (!$edit['timezone']) { + drupal_add_js('if (Drupal.jsEnabled) { + $(document).ready(function() { + Drupal.setDefaultTimezone(); + }); + }', 'inline'); + } $zones = _system_zonelist(); $form['timezone'] = array( '#type' => 'fieldset', @@ -704,14 +725,19 @@ $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.'), ); } - return $form; } + elseif ($type == 'login') { + // If the user has a NULL time zone, notify them to set a time zone. + if (!$user->timezone && variable_get('configurable_timezones', 1) && variable_get('empty_timezone_message', 1)) { + drupal_set_message(l(t('Please verify your user account time zone.'), 'user/' . $user->uid. '/edit', array('query' => drupal_get_destination(), 'fragment' => 'edit-timezone'))); + } + } } /** @@ -2009,11 +2035,15 @@ */ 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; } @@ -2045,6 +2075,22 @@ } /** + * Menu callback; Retrieve a JSON object containing a suggested time + * zone name. + */ +function date_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 : ''; + $timezone = timezone_name_from_abbr($abbreviation, intval($offset), $is_daylight_saving_time); + // The client date is passed in for debugging purposes. + $date = isset($_GET['date']) ? $_GET['date'] : ''; + // Log a debug message. + watchdog('timezone', 'Detected time zone: %timezone; client date: %date; abbreviation: %abbreviation; offset: %offset; daylight saving time: %is_daylight_saving_time.', array('%timezone' => $timezone, '%date' => $date, '%abbreviation' => $abbreviation, '%offset' => $offset, '%is_daylight_saving_time' => $is_daylight_saving_time)); + drupal_json($timezone); +} + +/** * Format the Powered by Drupal text. * * @ingroup themeable Index: modules/system/system.test =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.test,v retrieving revision 1.1 diff -u -r1.1 system.test --- modules/system/system.test 20 Apr 2008 18:23:31 -0000 1.1 +++ modules/system/system.test 9 May 2008 02:13:15 -0000 @@ -126,3 +126,44 @@ } } } + +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.12 diff -u -r1.12 user.install --- modules/user/user.install 7 May 2008 19:34:24 -0000 1.12 +++ modules/user/user.install 9 May 2008 14:30:53 -0000 @@ -158,7 +158,7 @@ ), 'timezone' => array( 'type' => 'varchar', - 'length' => 8, + 'length' => 32, 'not null' => FALSE, 'description' => t("User's timezone."), ), @@ -292,6 +292,35 @@ } /** + * Convert user time zones from time zone offsets to time zone names. + */ +function user_update_7002() { + $ret = array(); + drupal_set_message(t('User time zones have been changed from offsets to time zone names. You can change the new user time zone options at !link.', array('!link' => l(t('date and time configuration page'), 'admin/settings/date-time'))), 'warning'); + 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"); + } + // If the contributed Event module has stored user timezone information + // use that information to update the user accounts. + elseif (db_column_exists('users', 'timezone_id')) { + $results = db_query("SELECT DISTINCT timezone_id, name FROM {users} u LEFT JOIN {event_timezones} t on u.timezone_id = t.timezone"); + while ($row = db_fetch_object($results)) { + $name = str_replace(' ', '_', $row->name); + $ret[] = update_sql("UPDATE {users} SET timezone = '$name' WHERE timezone_id = ". $row->timezone_id); + } + } + else { + $ret[] = update_sql("UPDATE {users} SET timezone = NULL"); + variable_set('empty_timezone_message', 1); + drupal_set_message(t('User time zones have been emptied and need to be set to the correct values. Use the new time zone options to choose whether to remind users at login to choose the correct time zone name.'), 'warning'); + } + 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 -r1.6 user.js --- modules/user/user.js 12 Sep 2007 18:29:32 -0000 1.6 +++ modules/user/user.js 9 May 2008 12:45:13 -0000 @@ -168,14 +168,6 @@ }; /** - * Set the client's system timezone 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); -}; - -/** * On the admin/user/settings page, conditionally show all of the * picture-related form elements depending on the current value of the * "Picture support" radio buttons. Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.907 diff -u -r1.907 user.module --- modules/user/user.module 7 May 2008 19:34:24 -0000 1.907 +++ modules/user/user.module 9 May 2008 13:00:40 -0000 @@ -2349,25 +2349,6 @@ $form = array_merge($form, $extra); } - if (variable_get('configurable_timezones', 1)) { - // Override field ID, so we only change timezone on user registration, - // and never touch it on user edit pages. - $form['timezone'] = array( - '#type' => 'hidden', - '#default_value' => variable_get('date_default_timezone', NULL), - '#id' => 'edit-user-register-timezone', - ); - - // Add the JavaScript callback to automatically set the timezone. - drupal_add_js(' -// Global Killswitch -if (Drupal.jsEnabled) { - $(document).ready(function() { - Drupal.setDefaultTimezone(); - }); -}', 'inline'); - } - $form['submit'] = array('#type' => 'submit', '#value' => t('Create new account'), '#weight' => 30); $form['#validate'][] = 'user_register_validate'; Index: modules/user/user.test =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.test,v retrieving revision 1.3 diff -u -r1.3 user.test --- modules/user/user.test 7 May 2008 19:34:24 -0000 1.3 +++ modules/user/user.test 9 May 2008 02:13:16 -0000 @@ -23,9 +23,9 @@ // 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(); @@ -261,3 +261,59 @@ } } + + +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.'); + } +}