wiki.techinc.nl/includes/htmlform/fields/HTMLTimezoneField.php
Daimona Eaytoy b6431b5caf Fix logic for formatting negative timezone offsets
As described on the task, floor() returns the closest integer to the
left, and so it's not the right choice in this case for negative
offsets.

Put the logic in a static method of the UserTimeCorrection class so that
it can be reused elsewhere without making the previous mistake, and add
tests for it.

Also update a comment in UserTimeCorrection, as a follow-up to
I99a00dff7e3319ce45883191daee16bec1ed68ba.

Bug: T318455
Change-Id: I9acc8fa278d5a58a1d56c28c9e8b3f9093f8add9
2022-09-23 22:35:11 +00:00

148 lines
5.3 KiB
PHP

<?php
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use MediaWiki\User\UserTimeCorrection;
use Wikimedia\Message\ITextFormatter;
use Wikimedia\Message\MessageValue;
/**
* Dropdown widget that allows the user to select a timezone, either by choosing a geographic zone, by using the wiki
* default, or by manually specifying an offset. It also has an option to fill the value from the browser settings.
* The value of this field is in a format accepted by UserTimeCorrection.
*/
class HTMLTimezoneField extends HTMLSelectOrOtherField {
private const FIELD_CLASS = 'mw-htmlform-timezone-field';
/** @var ITextFormatter */
private $msgFormatter;
/**
* @stable to call
* @inheritDoc
* Note that no options should be specified.
*/
public function __construct( $params ) {
if ( isset( $params['options'] ) ) {
throw new InvalidArgumentException( "Options should not be provided to " . __CLASS__ );
}
$params['placeholder-message'] = $params['placeholder-message'] ?? 'timezone-useoffset-placeholder';
$params['options'] = [];
parent::__construct( $params );
$lang = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage();
$langCode = $lang->getCode();
$this->msgFormatter = MediaWikiServices::getInstance()->getMessageFormatterFactory()
->getTextFormatter( $langCode );
$this->mOptions = $this->getTimezoneOptions();
}
/**
* @return array<string|string[]>
*/
private function getTimezoneOptions(): array {
$opt = [];
$localTZoffset = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::LocalTZoffset );
$timeZoneList = $this->getTimeZoneList();
$timestamp = MWTimestamp::getLocalInstance();
// Check that the LocalTZoffset is the same as the local time zone offset
if ( $localTZoffset === (int)$timestamp->format( 'Z' ) / 60 ) {
$timezoneName = $timestamp->getTimezone()->getName();
// Localize timezone
if ( isset( $timeZoneList[$timezoneName] ) ) {
$timezoneName = $timeZoneList[$timezoneName]['name'];
}
$server_tz_msg = $this->msgFormatter->format(
MessageValue::new( 'timezoneuseserverdefault', [ $timezoneName ] )
);
} else {
$tzstring = UserTimeCorrection::formatTimezoneOffset( $localTZoffset );
$server_tz_msg = $this->msgFormatter->format(
MessageValue::new( 'timezoneuseserverdefault', [ $tzstring ] )
);
}
$opt[$server_tz_msg] = "System|$localTZoffset";
$opt[$this->msgFormatter->format( MessageValue::new( 'timezoneuseoffset' ) )] = 'other';
$opt[$this->msgFormatter->format( MessageValue::new( 'guesstimezone' ) )] = 'guess';
foreach ( $timeZoneList as $timeZoneInfo ) {
$region = $timeZoneInfo['region'];
if ( !isset( $opt[$region] ) ) {
$opt[$region] = [];
}
$opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
}
return $opt;
}
/**
* Get a list of all time zones
* @return string[][] A list of all time zones. The system name of the time zone is used as key and
* the value is an array which contains localized name, the timecorrection value used for
* preferences and the region
*/
private function getTimeZoneList(): array {
$identifiers = DateTimeZone::listIdentifiers();
// @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3162
if ( $identifiers === false ) {
return [];
}
sort( $identifiers );
$tzRegions = [
'Africa' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-africa' ) ),
'America' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-america' ) ),
'Antarctica' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-antarctica' ) ),
'Arctic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-arctic' ) ),
'Asia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-asia' ) ),
'Atlantic' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-atlantic' ) ),
'Australia' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-australia' ) ),
'Europe' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-europe' ) ),
'Indian' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-indian' ) ),
'Pacific' => $this->msgFormatter->format( MessageValue::new( 'timezoneregion-pacific' ) ),
];
asort( $tzRegions );
$timeZoneList = [];
$now = new DateTime();
foreach ( $identifiers as $identifier ) {
$parts = explode( '/', $identifier, 2 );
// DateTimeZone::listIdentifiers() returns a number of
// backwards-compatibility entries. This filters them out of the
// list presented to the user.
if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
continue;
}
// Localize region
$parts[0] = $tzRegions[$parts[0]];
$dateTimeZone = new DateTimeZone( $identifier );
$minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
$display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
$value = "ZoneInfo|$minDiff|$identifier";
$timeZoneList[$identifier] = [
'name' => $display,
'timecorrection' => $value,
'region' => $parts[0],
];
}
return $timeZoneList;
}
/**
* @inheritDoc
*/
protected function getFieldClasses(): array {
$classes = parent::getFieldClasses();
$classes[] = self::FIELD_CLASS;
return $classes;
}
}