Index: trunk/phase3/skins/common/prefs.js |
— | — | @@ -95,6 +95,7 @@ |
96 | 96 | if (tzb) { |
97 | 97 | tzb.style.display = 'inline'; |
98 | 98 | } |
| 99 | + updateTimezoneSelection(false); |
99 | 100 | } |
100 | 101 | |
101 | 102 | // in [-]HH:MM format... |
— | — | @@ -113,7 +114,51 @@ |
114 | 115 | |
115 | 116 | function guessTimezone(box) { |
116 | 117 | document.getElementsByName("wpHourDiff")[0].value = fetchTimezone(); |
| 118 | + updateTimezoneSelection(true); |
117 | 119 | } |
118 | 120 | |
| 121 | +function updateTimezoneSelection(force_offset) { |
| 122 | + var wpTimeZone = document.getElementsByName("wpTimeZone")[0]; |
| 123 | + var wpHourDiff = document.getElementsByName("wpHourDiff")[0]; |
| 124 | + var wpLocalTime = document.getElementById("wpLocalTime"); |
| 125 | + var wpServerTime = document.getElementsByName("wpServerTime")[0]; |
| 126 | + var minDiff = 0; |
| 127 | + |
| 128 | + if (force_offset) wpTimeZone.selectedIndex = 1; |
| 129 | + if (wpTimeZone.selectedIndex == 1) { |
| 130 | + wpHourDiff.disabled = false; |
| 131 | + var diffArr = wpHourDiff.value.split(':'); |
| 132 | + if (diffArr.length == 1) { |
| 133 | + minDiff = parseInt(diffArr[0], 10) * 60; |
| 134 | + } else { |
| 135 | + minDiff = Math.abs(parseInt(diffArr[0], 10))*60 + parseInt(diffArr[1], 10); |
| 136 | + if (parseInt(diffArr[0], 10) < 0) minDiff = -minDiff; |
| 137 | + } |
| 138 | + } else { |
| 139 | + wpHourDiff.disabled = true; |
| 140 | + var diffArr = wpTimeZone.options[wpTimeZone.selectedIndex].value.split('|'); |
| 141 | + minDiff = parseInt(diffArr[1], 10); |
| 142 | + } |
| 143 | + if (isNaN(minDiff)) minDiff = 0; |
| 144 | + var localTime = parseInt(wpServerTime.value, 10) + minDiff; |
| 145 | + while (localTime < 0) localTime += 1440; |
| 146 | + while (localTime >= 1440) localTime -= 1440; |
| 147 | + |
| 148 | + var hour = String(Math.floor(localTime/60)); |
| 149 | + if (hour.length<2) hour = '0'+hour; |
| 150 | + var min = String(localTime%60); |
| 151 | + if (min.length<2) min = '0'+min; |
| 152 | + changeText(wpLocalTime, hour+':'+min); |
| 153 | + |
| 154 | + if (wpTimeZone.selectedIndex != 1) { |
| 155 | + hour = String(Math.abs(Math.floor(minDiff/60))); |
| 156 | + if (hour.length<2) hour = '0'+hour; |
| 157 | + if (minDiff < 0) hour = '-'+hour; |
| 158 | + min = String(minDiff%60); |
| 159 | + if (min.length<2) min = '0'+min; |
| 160 | + wpHourDiff.value = hour+':'+min; |
| 161 | + } |
| 162 | +} |
| 163 | + |
119 | 164 | hookEvent("load", unhidetzbutton); |
120 | 165 | hookEvent("load", tabbedprefs); |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -1444,7 +1444,7 @@ |
1445 | 1445 | * to ensure that client-side caches don't keep obsolete copies of global |
1446 | 1446 | * styles. |
1447 | 1447 | */ |
1448 | | -$wgStyleVersion = '191'; |
| 1448 | +$wgStyleVersion = '192'; |
1449 | 1449 | |
1450 | 1450 | |
1451 | 1451 | # Server-side caching: |
Index: trunk/phase3/includes/specials/SpecialPreferences.php |
— | — | @@ -24,7 +24,7 @@ |
25 | 25 | var $mQuickbar, $mStubs; |
26 | 26 | var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick; |
27 | 27 | var $mUserLanguage, $mUserVariant; |
28 | | - var $mSearch, $mRecent, $mRecentDays, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; |
| 28 | + var $mSearch, $mRecent, $mRecentDays, $mTimeZone, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; |
29 | 29 | var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize; |
30 | 30 | var $mUnderline, $mWatchlistEdits; |
31 | 31 | |
— | — | @@ -51,6 +51,7 @@ |
52 | 52 | $this->mSearch = $request->getVal( 'wpSearch' ); |
53 | 53 | $this->mRecent = $request->getVal( 'wpRecent' ); |
54 | 54 | $this->mRecentDays = $request->getVal( 'wpRecentDays' ); |
| 55 | + $this->mTimeZone = $request->getVal( 'wpTimeZone' ); |
55 | 56 | $this->mHourDiff = $request->getVal( 'wpHourDiff' ); |
56 | 57 | $this->mSearchLines = $request->getVal( 'wpSearchLines' ); |
57 | 58 | $this->mSearchChars = $request->getVal( 'wpSearchChars' ); |
— | — | @@ -170,34 +171,37 @@ |
171 | 172 | |
172 | 173 | /** |
173 | 174 | * Used to validate the user inputed timezone before saving it as |
174 | | - * 'timecorrection', will return '00:00' if fed bogus data. |
175 | | - * Note: It's not a 100% correct implementation timezone-wise, it will |
176 | | - * accept stuff like '14:30', |
| 175 | + * 'timecorrection', will return 'System' if fed bogus data. |
177 | 176 | * @access private |
178 | | - * @param string $s the user input |
| 177 | + * @param string $tz the user input Zoneinfo timezone |
| 178 | + * @param string $s the user input offset string |
179 | 179 | * @return string |
180 | 180 | */ |
181 | | - function validateTimeZone( $s ) { |
182 | | - if ( $s !== '' ) { |
183 | | - if ( strpos( $s, ':' ) ) { |
184 | | - # HH:MM |
185 | | - $array = explode( ':' , $s ); |
186 | | - $hour = intval( $array[0] ); |
187 | | - $minute = intval( $array[1] ); |
188 | | - } else { |
189 | | - $minute = intval( $s * 60 ); |
190 | | - $hour = intval( $minute / 60 ); |
191 | | - $minute = abs( $minute ) % 60; |
192 | | - } |
193 | | - # Max is +14:00 and min is -12:00, see: |
194 | | - # http://en.wikipedia.org/wiki/Timezone |
195 | | - $hour = min( $hour, 14 ); |
196 | | - $hour = max( $hour, -12 ); |
197 | | - $minute = min( $minute, 59 ); |
198 | | - $minute = max( $minute, 0 ); |
199 | | - $s = sprintf( "%02d:%02d", $hour, $minute ); |
| 181 | + function validateTimeZone( $tz, $s ) { |
| 182 | + $data = explode( '|', $tz, 3 ); |
| 183 | + switch ( $data[0] ) { |
| 184 | + case 'ZoneInfo': |
| 185 | + case 'System': |
| 186 | + return $tz; |
| 187 | + case 'Offset': |
| 188 | + default: |
| 189 | + $data = explode( ':', $s, 2 ); |
| 190 | + $minDiff = 0; |
| 191 | + if( count( $data ) == 2 ) { |
| 192 | + $data[0] = intval( $data[0] ); |
| 193 | + $data[1] = intval( $data[1] ); |
| 194 | + $minDiff = abs( $data[0] ) * 60 + $data[1]; |
| 195 | + if ( $data[0] < 0 ) $minDiff = -$minDiff; |
| 196 | + } else { |
| 197 | + $minDiff = intval( $data[0] ) * 60; |
| 198 | + } |
| 199 | + |
| 200 | + # Max is +14:00 and min is -12:00, see: |
| 201 | + # http://en.wikipedia.org/wiki/Timezone |
| 202 | + $minDiff = min( $minDiff, 840 ); # 14:00 |
| 203 | + $minDiff = max( $minDiff, -720 ); # -12:00 |
| 204 | + return 'Offset|'.$minDiff; |
200 | 205 | } |
201 | | - return $s; |
202 | 206 | } |
203 | 207 | |
204 | 208 | /** |
— | — | @@ -259,7 +263,7 @@ |
260 | 264 | $wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) ); |
261 | 265 | $wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) ); |
262 | 266 | $wgUser->setOption( 'stubthreshold', $this->validateIntOrNull( $this->mStubs ) ); |
263 | | - $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mHourDiff, -12, 14 ) ); |
| 267 | + $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mTimeZone, $this->mHourDiff ) ); |
264 | 268 | $wgUser->setOption( 'imagesize', $this->mImageSize ); |
265 | 269 | $wgUser->setOption( 'thumbsize', $this->mThumbSize ); |
266 | 270 | $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) ); |
— | — | @@ -344,7 +348,7 @@ |
345 | 349 | * @access private |
346 | 350 | */ |
347 | 351 | function resetPrefs() { |
348 | | - global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName; |
| 352 | + global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName, $wgLocalTZoffset; |
349 | 353 | |
350 | 354 | $this->mUserEmail = $wgUser->getEmail(); |
351 | 355 | $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp(); |
— | — | @@ -364,7 +368,47 @@ |
365 | 369 | $this->mRows = $wgUser->getOption( 'rows' ); |
366 | 370 | $this->mCols = $wgUser->getOption( 'cols' ); |
367 | 371 | $this->mStubs = $wgUser->getOption( 'stubthreshold' ); |
368 | | - $this->mHourDiff = $wgUser->getOption( 'timecorrection' ); |
| 372 | + |
| 373 | + $tz = $wgUser->getOption( 'timecorrection' ); |
| 374 | + $data = explode( '|', $tz, 3 ); |
| 375 | + $minDiff = null; |
| 376 | + switch ( $data[0] ) { |
| 377 | + case 'ZoneInfo': |
| 378 | + $this->mTimeZone = $tz; |
| 379 | + # Check if the specified TZ exists, and change to 'Offset' if |
| 380 | + # not. |
| 381 | + if ( !function_exists('timezone_open') || @timezone_open( $data[2] ) === false ) { |
| 382 | + $this->mTimeZone = 'Offset'; |
| 383 | + $minDiff = intval( $data[1] ); |
| 384 | + } |
| 385 | + break; |
| 386 | + case '': |
| 387 | + case 'System': |
| 388 | + $this->mTimeZone = 'System|'.$wgLocalTZoffset; |
| 389 | + break; |
| 390 | + case 'Offset': |
| 391 | + $this->mTimeZone = 'Offset'; |
| 392 | + $minDiff = intval( $data[1] ); |
| 393 | + break; |
| 394 | + default: |
| 395 | + $this->mTimeZone = 'Offset'; |
| 396 | + $data = explode( ':', $tz, 2 ); |
| 397 | + if( count( $data ) == 2 ) { |
| 398 | + $data[0] = intval( $data[0] ); |
| 399 | + $data[1] = intval( $data[1] ); |
| 400 | + $minDiff = abs( $data[0] ) * 60 + $data[1]; |
| 401 | + if ( $data[0] < 0 ) $minDiff = -$minDiff; |
| 402 | + } else { |
| 403 | + $minDiff = intval( $data[0] ) * 60; |
| 404 | + } |
| 405 | + break; |
| 406 | + } |
| 407 | + if ( is_null( $minDiff ) ) { |
| 408 | + $this->mHourDiff = ''; |
| 409 | + } else { |
| 410 | + $this->mHourDiff = sprintf( '%+03d:%02d', floor($minDiff/60), abs($minDiff)%60 ); |
| 411 | + } |
| 412 | + |
369 | 413 | $this->mSearch = $wgUser->getOption( 'searchlimit' ); |
370 | 414 | $this->mSearchLines = $wgUser->getOption( 'contextlines' ); |
371 | 415 | $this->mSearchChars = $wgUser->getOption( 'contextchars' ); |
— | — | @@ -490,7 +534,7 @@ |
491 | 535 | global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; |
492 | 536 | global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; |
493 | 537 | global $wgContLanguageCode, $wgDefaultSkin, $wgCookieExpiration; |
494 | | - global $wgEmailConfirmToEdit, $wgEnableMWSuggest; |
| 538 | + global $wgEmailConfirmToEdit, $wgEnableMWSuggest, $wgLocalTZoffset; |
495 | 539 | |
496 | 540 | $wgOut->setPageTitle( wfMsg( 'preferences' ) ); |
497 | 541 | $wgOut->setArticleRelated( false ); |
— | — | @@ -908,18 +952,61 @@ |
909 | 953 | $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" ); |
910 | 954 | } |
911 | 955 | |
912 | | - $nowlocal = $wgLang->time( $now = wfTimestampNow(), true ); |
913 | | - $nowserver = $wgLang->time( $now, false ); |
| 956 | + $nowlocal = Xml::openElement( 'span', array( 'id' => 'wpLocalTime' ) ) . |
| 957 | + $wgLang->time( $now = wfTimestampNow(), true ) . |
| 958 | + Xml::closeElement( 'span' ); |
| 959 | + $nowserver = $wgLang->time( $now, false ) . |
| 960 | + Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) ); |
914 | 961 | |
915 | 962 | $wgOut->addHTML( |
916 | 963 | Xml::openElement( 'fieldset' ) . |
917 | 964 | Xml::element( 'legend', null, wfMsg( 'timezonelegend' ) ) . |
918 | 965 | Xml::openElement( 'table' ) . |
919 | 966 | $this->addRow( wfMsg( 'servertime' ), $nowserver ) . |
920 | | - $this->addRow( wfMsg( 'localtime' ), $nowlocal ) . |
| 967 | + $this->addRow( wfMsg( 'localtime' ), $nowlocal ) |
| 968 | + ); |
| 969 | + $opt = Xml::openElement( 'select', array( |
| 970 | + 'name' => 'wpTimeZone', |
| 971 | + 'id' => 'wpTimeZone', |
| 972 | + 'onchange' => 'javascript:updateTimezoneSelection(false)' ) ); |
| 973 | + $opt .= Xml::option( wfMsg( 'timezoneuseserverdefault' ), "System|$wgLocalTZoffset", $this->mTimeZone === "System|$wgLocalTZoffset" ); |
| 974 | + $opt .= Xml::option( wfMsg( 'timezoneuseoffset' ), 'Offset', $this->mTimeZone === 'Offset' ); |
| 975 | + if ( function_exists( 'timezone_identifiers_list' ) ) { |
| 976 | + $optgroup = ''; |
| 977 | + $tzs = timezone_identifiers_list(); |
| 978 | + sort( $tzs ); |
| 979 | + $selZone = explode( '|', $this->mTimeZone, 3); |
| 980 | + $selZone = ( $selZone[0] == 'ZoneInfo' ) ? $selZone[2] : null; |
| 981 | + $now = date_create( 'now' ); |
| 982 | + foreach ( $tzs as $tz ) { |
| 983 | + $z = explode( '/', $tz, 2 ); |
| 984 | + # timezone_identifiers_list() returns a number of |
| 985 | + # backwards-compatibility entries. This filters them out of the |
| 986 | + # list presented to the user. |
| 987 | + if ( count( $z ) != 2 || !in_array( $z[0], array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' ) ) ) continue; |
| 988 | + if ( $optgroup != $z[0] ) { |
| 989 | + if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); |
| 990 | + $optgroup = $z[0]; |
| 991 | + $opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) ); |
| 992 | + } |
| 993 | + $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 ); |
| 994 | + $opt .= Xml::option( str_replace( '_', ' ', $tz ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) ); |
| 995 | + } |
| 996 | + if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); |
| 997 | + } |
| 998 | + $opt .= Xml::closeElement( 'select' ); |
| 999 | + $wgOut->addHTML( |
921 | 1000 | $this->addRow( |
| 1001 | + Xml::label( wfMsg( 'timezoneselect' ), 'wpTimeZone' ), |
| 1002 | + $opt ) |
| 1003 | + ); |
| 1004 | + $wgOut->addHTML( |
| 1005 | + $this->addRow( |
922 | 1006 | Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ), |
923 | | - Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( 'id' => 'wpHourDiff' ) ) ) . |
| 1007 | + Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array( |
| 1008 | + 'id' => 'wpHourDiff', |
| 1009 | + 'onfocus' => 'javascript:updateTimezoneSelection(true)', |
| 1010 | + 'onblur' => 'javascript:updateTimezoneSelection(false)' ) ) ) . |
924 | 1011 | "<tr> |
925 | 1012 | <td></td> |
926 | 1013 | <td class='mw-submit'>" . |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -1566,6 +1566,9 @@ |
1567 | 1567 | 'timezonelegend' => 'Time zone', |
1568 | 1568 | 'timezonetext' => '¹The number of hours your local time differs from server time (UTC).', |
1569 | 1569 | 'localtime' => 'Local time', |
| 1570 | +'timezoneselect' => 'Timezone', |
| 1571 | +'timezoneuseserverdefault' => 'Use server default', |
| 1572 | +'timezoneuseoffset' => 'Other (specify offset)', |
1570 | 1573 | 'timezoneoffset' => 'Offset¹', |
1571 | 1574 | 'servertime' => 'Server time', |
1572 | 1575 | 'guesstimezone' => 'Fill in from browser', |
Index: trunk/phase3/languages/Language.php |
— | — | @@ -479,39 +479,50 @@ |
480 | 480 | function userAdjust( $ts, $tz = false ) { |
481 | 481 | global $wgUser, $wgLocalTZoffset; |
482 | 482 | |
483 | | - if (!$tz) { |
| 483 | + if ( $tz === false ) { |
484 | 484 | $tz = $wgUser->getOption( 'timecorrection' ); |
485 | 485 | } |
486 | 486 | |
487 | | - # minutes and hours differences: |
| 487 | + $data = explode( '|', $tz, 3 ); |
| 488 | + |
| 489 | + if ( $data[0] == 'ZoneInfo' ) { |
| 490 | + if ( function_exists( 'timezone_open' ) && @timezone_open( $data[2] ) !== false ) { |
| 491 | + $date = date_create( $ts, timezone_open( 'UTC' ) ); |
| 492 | + date_timezone_set( $date, timezone_open( $data[2] ) ); |
| 493 | + $date = date_format( $date, 'YmdHis' ); |
| 494 | + return $date; |
| 495 | + } |
| 496 | + # Unrecognized timezone, default to 'Offset' with the stored offset. |
| 497 | + $data[0] = 'Offset'; |
| 498 | + } |
| 499 | + |
488 | 500 | $minDiff = 0; |
489 | | - $hrDiff = 0; |
490 | | - |
491 | | - if ( $tz === '' ) { |
| 501 | + if ( $data[0] == 'System' || $tz == '' ) { |
492 | 502 | # Global offset in minutes. |
493 | | - if( isset($wgLocalTZoffset) ) { |
494 | | - if( $wgLocalTZoffset >= 0 ) { |
495 | | - $hrDiff = floor($wgLocalTZoffset / 60); |
496 | | - } else { |
497 | | - $hrDiff = ceil($wgLocalTZoffset / 60); |
498 | | - } |
499 | | - $minDiff = $wgLocalTZoffset % 60; |
| 503 | + if( isset($wgLocalTZoffset) ) $minDiff = $wgLocalTZoffset; |
| 504 | + } else if ( $data[0] == 'Offset' ) { |
| 505 | + $minDiff = intval( $data[1] ); |
| 506 | + } else { |
| 507 | + $data = explode( ':', $tz ); |
| 508 | + if( count( $data ) == 2 ) { |
| 509 | + $data[0] = intval( $data[0] ); |
| 510 | + $data[1] = intval( $data[1] ); |
| 511 | + $minDiff = abs( $data[0] ) * 60 + $data[1]; |
| 512 | + if ( $data[0] < 0 ) $minDiff = -$minDiff; |
| 513 | + } else { |
| 514 | + $minDiff = intval( $data[0] ) * 60; |
500 | 515 | } |
501 | | - } elseif ( strpos( $tz, ':' ) !== false ) { |
502 | | - $tzArray = explode( ':', $tz ); |
503 | | - $hrDiff = intval($tzArray[0]); |
504 | | - $minDiff = intval($hrDiff < 0 ? -$tzArray[1] : $tzArray[1]); |
505 | | - } else { |
506 | | - $hrDiff = intval( $tz ); |
507 | 516 | } |
508 | 517 | |
509 | 518 | # No difference ? Return time unchanged |
510 | | - if ( 0 == $hrDiff && 0 == $minDiff ) { return $ts; } |
| 519 | + if ( 0 == $minDiff ) return $ts; |
511 | 520 | |
512 | 521 | wfSuppressWarnings(); // E_STRICT system time bitching |
513 | | - # Generate an adjusted date |
| 522 | + # Generate an adjusted date; take advantage of the fact that mktime |
| 523 | + # will normalize out-of-range values so we don't have to split $minDiff |
| 524 | + # into hours and minutes. |
514 | 525 | $t = mktime( ( |
515 | | - (int)substr( $ts, 8, 2) ) + $hrDiff, # Hours |
| 526 | + (int)substr( $ts, 8, 2) ), # Hours |
516 | 527 | (int)substr( $ts, 10, 2 ) + $minDiff, # Minutes |
517 | 528 | (int)substr( $ts, 12, 2 ), # Seconds |
518 | 529 | (int)substr( $ts, 4, 2 ), # Month |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -239,7 +239,10 @@ |
240 | 240 | * Added 'UserCryptPassword' and 'UserComparePasswords' hooks to allow extensions to implement |
241 | 241 | their own password hashing methods. |
242 | 242 | * (bug 16760) Add CSS-class to action links of Special:Log |
| 243 | +* (bug 505) Time zones can now be specified by location in user preferences, |
| 244 | + avoiding the need to manually update for DST. Patch by Brad Jorsch. |
243 | 245 | |
| 246 | + |
244 | 247 | === Bug fixes in 1.14 === |
245 | 248 | |
246 | 249 | * (bug 14907) DatabasePostgres::fieldType now defined. |