* (bug 505) Time zones can now be specified by location in user preferences,

avoiding the need to manually update for DST. Patch by Brad Jorsch.
This commit is contained in:
Brion Vibber 2008-12-22 21:55:09 +00:00
parent 79d267d212
commit c672ce6032
6 changed files with 204 additions and 55 deletions

View file

@ -239,6 +239,9 @@ The following extensions are migrated into MediaWiki 1.14:
* Added 'UserCryptPassword' and 'UserComparePasswords' hooks to allow extensions to implement
their own password hashing methods.
* (bug 16760) Add CSS-class to action links of Special:Log
* (bug 505) Time zones can now be specified by location in user preferences,
avoiding the need to manually update for DST. Patch by Brad Jorsch.
=== Bug fixes in 1.14 ===

View file

@ -1444,7 +1444,7 @@ $wgCacheEpoch = '20030516000000';
* to ensure that client-side caches don't keep obsolete copies of global
* styles.
*/
$wgStyleVersion = '191';
$wgStyleVersion = '192';
# Server-side caching:

View file

@ -24,7 +24,7 @@ class PreferencesForm {
var $mQuickbar, $mStubs;
var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick;
var $mUserLanguage, $mUserVariant;
var $mSearch, $mRecent, $mRecentDays, $mHourDiff, $mSearchLines, $mSearchChars, $mAction;
var $mSearch, $mRecent, $mRecentDays, $mTimeZone, $mHourDiff, $mSearchLines, $mSearchChars, $mAction;
var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize;
var $mUnderline, $mWatchlistEdits;
@ -51,6 +51,7 @@ class PreferencesForm {
$this->mSearch = $request->getVal( 'wpSearch' );
$this->mRecent = $request->getVal( 'wpRecent' );
$this->mRecentDays = $request->getVal( 'wpRecentDays' );
$this->mTimeZone = $request->getVal( 'wpTimeZone' );
$this->mHourDiff = $request->getVal( 'wpHourDiff' );
$this->mSearchLines = $request->getVal( 'wpSearchLines' );
$this->mSearchChars = $request->getVal( 'wpSearchChars' );
@ -170,34 +171,37 @@ class PreferencesForm {
/**
* Used to validate the user inputed timezone before saving it as
* 'timecorrection', will return '00:00' if fed bogus data.
* Note: It's not a 100% correct implementation timezone-wise, it will
* accept stuff like '14:30',
* 'timecorrection', will return 'System' if fed bogus data.
* @access private
* @param string $s the user input
* @param string $tz the user input Zoneinfo timezone
* @param string $s the user input offset string
* @return string
*/
function validateTimeZone( $s ) {
if ( $s !== '' ) {
if ( strpos( $s, ':' ) ) {
# HH:MM
$array = explode( ':' , $s );
$hour = intval( $array[0] );
$minute = intval( $array[1] );
} else {
$minute = intval( $s * 60 );
$hour = intval( $minute / 60 );
$minute = abs( $minute ) % 60;
}
# Max is +14:00 and min is -12:00, see:
# http://en.wikipedia.org/wiki/Timezone
$hour = min( $hour, 14 );
$hour = max( $hour, -12 );
$minute = min( $minute, 59 );
$minute = max( $minute, 0 );
$s = sprintf( "%02d:%02d", $hour, $minute );
function validateTimeZone( $tz, $s ) {
$data = explode( '|', $tz, 3 );
switch ( $data[0] ) {
case 'ZoneInfo':
case 'System':
return $tz;
case 'Offset':
default:
$data = explode( ':', $s, 2 );
$minDiff = 0;
if( count( $data ) == 2 ) {
$data[0] = intval( $data[0] );
$data[1] = intval( $data[1] );
$minDiff = abs( $data[0] ) * 60 + $data[1];
if ( $data[0] < 0 ) $minDiff = -$minDiff;
} else {
$minDiff = intval( $data[0] ) * 60;
}
# Max is +14:00 and min is -12:00, see:
# http://en.wikipedia.org/wiki/Timezone
$minDiff = min( $minDiff, 840 ); # 14:00
$minDiff = max( $minDiff, -720 ); # -12:00
return 'Offset|'.$minDiff;
}
return $s;
}
/**
@ -259,7 +263,7 @@ class PreferencesForm {
$wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) );
$wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) );
$wgUser->setOption( 'stubthreshold', $this->validateIntOrNull( $this->mStubs ) );
$wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mHourDiff, -12, 14 ) );
$wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mTimeZone, $this->mHourDiff ) );
$wgUser->setOption( 'imagesize', $this->mImageSize );
$wgUser->setOption( 'thumbsize', $this->mThumbSize );
$wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) );
@ -344,7 +348,7 @@ class PreferencesForm {
* @access private
*/
function resetPrefs() {
global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName;
global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName, $wgLocalTZoffset;
$this->mUserEmail = $wgUser->getEmail();
$this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp();
@ -364,7 +368,47 @@ class PreferencesForm {
$this->mRows = $wgUser->getOption( 'rows' );
$this->mCols = $wgUser->getOption( 'cols' );
$this->mStubs = $wgUser->getOption( 'stubthreshold' );
$this->mHourDiff = $wgUser->getOption( 'timecorrection' );
$tz = $wgUser->getOption( 'timecorrection' );
$data = explode( '|', $tz, 3 );
$minDiff = null;
switch ( $data[0] ) {
case 'ZoneInfo':
$this->mTimeZone = $tz;
# Check if the specified TZ exists, and change to 'Offset' if
# not.
if ( !function_exists('timezone_open') || @timezone_open( $data[2] ) === false ) {
$this->mTimeZone = 'Offset';
$minDiff = intval( $data[1] );
}
break;
case '':
case 'System':
$this->mTimeZone = 'System|'.$wgLocalTZoffset;
break;
case 'Offset':
$this->mTimeZone = 'Offset';
$minDiff = intval( $data[1] );
break;
default:
$this->mTimeZone = 'Offset';
$data = explode( ':', $tz, 2 );
if( count( $data ) == 2 ) {
$data[0] = intval( $data[0] );
$data[1] = intval( $data[1] );
$minDiff = abs( $data[0] ) * 60 + $data[1];
if ( $data[0] < 0 ) $minDiff = -$minDiff;
} else {
$minDiff = intval( $data[0] ) * 60;
}
break;
}
if ( is_null( $minDiff ) ) {
$this->mHourDiff = '';
} else {
$this->mHourDiff = sprintf( '%+03d:%02d', floor($minDiff/60), abs($minDiff)%60 );
}
$this->mSearch = $wgUser->getOption( 'searchlimit' );
$this->mSearchLines = $wgUser->getOption( 'contextlines' );
$this->mSearchChars = $wgUser->getOption( 'contextchars' );
@ -490,7 +534,7 @@ class PreferencesForm {
global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress;
global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication;
global $wgContLanguageCode, $wgDefaultSkin, $wgCookieExpiration;
global $wgEmailConfirmToEdit, $wgEnableMWSuggest;
global $wgEmailConfirmToEdit, $wgEnableMWSuggest, $wgLocalTZoffset;
$wgOut->setPageTitle( wfMsg( 'preferences' ) );
$wgOut->setArticleRelated( false );
@ -908,18 +952,61 @@ class PreferencesForm {
$wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" );
}
$nowlocal = $wgLang->time( $now = wfTimestampNow(), true );
$nowserver = $wgLang->time( $now, false );
$nowlocal = Xml::openElement( 'span', array( 'id' => 'wpLocalTime' ) ) .
$wgLang->time( $now = wfTimestampNow(), true ) .
Xml::closeElement( 'span' );
$nowserver = $wgLang->time( $now, false ) .
Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
$wgOut->addHTML(
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'timezonelegend' ) ) .
Xml::openElement( 'table' ) .
$this->addRow( wfMsg( 'servertime' ), $nowserver ) .
$this->addRow( wfMsg( 'localtime' ), $nowlocal ) .
$this->addRow( wfMsg( 'localtime' ), $nowlocal )
);
$opt = Xml::openElement( 'select', array(
'name' => 'wpTimeZone',
'id' => 'wpTimeZone',
'onchange' => 'javascript:updateTimezoneSelection(false)' ) );
$opt .= Xml::option( wfMsg( 'timezoneuseserverdefault' ), "System|$wgLocalTZoffset", $this->mTimeZone === "System|$wgLocalTZoffset" );
$opt .= Xml::option( wfMsg( 'timezoneuseoffset' ), 'Offset', $this->mTimeZone === 'Offset' );
if ( function_exists( 'timezone_identifiers_list' ) ) {
$optgroup = '';
$tzs = timezone_identifiers_list();
sort( $tzs );
$selZone = explode( '|', $this->mTimeZone, 3);
$selZone = ( $selZone[0] == 'ZoneInfo' ) ? $selZone[2] : null;
$now = date_create( 'now' );
foreach ( $tzs as $tz ) {
$z = explode( '/', $tz, 2 );
# timezone_identifiers_list() returns a number of
# backwards-compatibility entries. This filters them out of the
# list presented to the user.
if ( count( $z ) != 2 || !in_array( $z[0], array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' ) ) ) continue;
if ( $optgroup != $z[0] ) {
if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' );
$optgroup = $z[0];
$opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) );
}
$minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
$opt .= Xml::option( str_replace( '_', ' ', $tz ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) );
}
if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' );
}
$opt .= Xml::closeElement( 'select' );
$wgOut->addHTML(
$this->addRow(
Xml::label( wfMsg( 'timezoneselect' ), 'wpTimeZone' ),
$opt )
);
$wgOut->addHTML(
$this->addRow(
Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ),
Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( 'id' => 'wpHourDiff' ) ) ) .
Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array(
'id' => 'wpHourDiff',
'onfocus' => 'javascript:updateTimezoneSelection(true)',
'onblur' => 'javascript:updateTimezoneSelection(false)' ) ) ) .
"<tr>
<td></td>
<td class='mw-submit'>" .

View file

@ -479,39 +479,50 @@ class Language {
function userAdjust( $ts, $tz = false ) {
global $wgUser, $wgLocalTZoffset;
if (!$tz) {
if ( $tz === false ) {
$tz = $wgUser->getOption( 'timecorrection' );
}
# minutes and hours differences:
$minDiff = 0;
$hrDiff = 0;
$data = explode( '|', $tz, 3 );
if ( $tz === '' ) {
# Global offset in minutes.
if( isset($wgLocalTZoffset) ) {
if( $wgLocalTZoffset >= 0 ) {
$hrDiff = floor($wgLocalTZoffset / 60);
} else {
$hrDiff = ceil($wgLocalTZoffset / 60);
}
$minDiff = $wgLocalTZoffset % 60;
if ( $data[0] == 'ZoneInfo' ) {
if ( function_exists( 'timezone_open' ) && @timezone_open( $data[2] ) !== false ) {
$date = date_create( $ts, timezone_open( 'UTC' ) );
date_timezone_set( $date, timezone_open( $data[2] ) );
$date = date_format( $date, 'YmdHis' );
return $date;
}
} elseif ( strpos( $tz, ':' ) !== false ) {
$tzArray = explode( ':', $tz );
$hrDiff = intval($tzArray[0]);
$minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]);
# Unrecognized timezone, default to 'Offset' with the stored offset.
$data[0] = 'Offset';
}
$minDiff = 0;
if ( $data[0] == 'System' || $tz == '' ) {
# Global offset in minutes.
if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset;
} else if ( $data[0] == 'Offset' ) {
$minDiff = intval( $data[1] );
} else {
$hrDiff = intval( $tz );
$data = explode( ':', $tz );
if( count( $data ) == 2 ) {
$data[0] = intval( $data[0] );
$data[1] = intval( $data[1] );
$minDiff = abs( $data[0] ) * 60 + $data[1];
if ( $data[0] < 0 ) $minDiff = -$minDiff;
} else {
$minDiff = intval( $data[0] ) * 60;
}
}
# No difference ? Return time unchanged
if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; }
if ( 0 == $minDiff ) return $ts;
wfSuppressWarnings(); // E_STRICT system time bitching
# Generate an adjusted date
# Generate an adjusted date; take advantage of the fact that mktime
# will normalize out-of-range values so we don't have to split $minDiff
# into hours and minutes.
$t = mktime( (
(int)substr( $ts, 8, 2) ) + $hrDiff, # Hours
(int)substr( $ts, 8, 2) ), # Hours
(int)substr( $ts, 10, 2 ) + $minDiff, # Minutes
(int)substr( $ts, 12, 2 ), # Seconds
(int)substr( $ts, 4, 2 ), # Month

View file

@ -1566,6 +1566,9 @@ please see math/README to configure.',
'timezonelegend' => 'Time zone',
'timezonetext' => '¹The number of hours your local time differs from server time (UTC).',
'localtime' => 'Local time',
'timezoneselect' => 'Timezone',
'timezoneuseserverdefault' => 'Use server default',
'timezoneuseoffset' => 'Other (specify offset)',
'timezoneoffset' => 'Offset¹',
'servertime' => 'Server time',
'guesstimezone' => 'Fill in from browser',

View file

@ -95,6 +95,7 @@ function unhidetzbutton() {
if (tzb) {
tzb.style.display = 'inline';
}
updateTimezoneSelection(false);
}
// in [-]HH:MM format...
@ -113,6 +114,50 @@ function fetchTimezone() {
function guessTimezone(box) {
document.getElementsByName("wpHourDiff")[0].value = fetchTimezone();
updateTimezoneSelection(true);
}
function updateTimezoneSelection(force_offset) {
var wpTimeZone = document.getElementsByName("wpTimeZone")[0];
var wpHourDiff = document.getElementsByName("wpHourDiff")[0];
var wpLocalTime = document.getElementById("wpLocalTime");
var wpServerTime = document.getElementsByName("wpServerTime")[0];
var minDiff = 0;
if (force_offset) wpTimeZone.selectedIndex = 1;
if (wpTimeZone.selectedIndex == 1) {
wpHourDiff.disabled = false;
var diffArr = wpHourDiff.value.split(':');
if (diffArr.length == 1) {
minDiff = parseInt(diffArr[0], 10) * 60;
} else {
minDiff = Math.abs(parseInt(diffArr[0], 10))*60 + parseInt(diffArr[1], 10);
if (parseInt(diffArr[0], 10) < 0) minDiff = -minDiff;
}
} else {
wpHourDiff.disabled = true;
var diffArr = wpTimeZone.options[wpTimeZone.selectedIndex].value.split('|');
minDiff = parseInt(diffArr[1], 10);
}
if (isNaN(minDiff)) minDiff = 0;
var localTime = parseInt(wpServerTime.value, 10) + minDiff;
while (localTime < 0) localTime += 1440;
while (localTime >= 1440) localTime -= 1440;
var hour = String(Math.floor(localTime/60));
if (hour.length<2) hour = '0'+hour;
var min = String(localTime%60);
if (min.length<2) min = '0'+min;
changeText(wpLocalTime, hour+':'+min);
if (wpTimeZone.selectedIndex != 1) {
hour = String(Math.abs(Math.floor(minDiff/60)));
if (hour.length<2) hour = '0'+hour;
if (minDiff < 0) hour = '-'+hour;
min = String(minDiff%60);
if (min.length<2) min = '0'+min;
wpHourDiff.value = hour+':'+min;
}
}
hookEvent("load", unhidetzbutton);