Skip to content

Commit

Permalink
Re-add removed localization feature
Browse files Browse the repository at this point in the history
  • Loading branch information
thiemowmde committed Mar 24, 2015
1 parent 7f01419 commit edaf292
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 80 deletions.
111 changes: 99 additions & 12 deletions src/ValueFormatters/TimeFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@
use InvalidArgumentException;

/**
* Basic formatter for time values that either delegates formatting to an other formatter given via
* the OPT_TIME_ISO_FORMATTER option or outputs the time value as it is in a simple fallback format.
* Basic plain text formatter for TimeValue objects that either delegates formatting to an other
* formatter given via OPT_TIME_ISO_FORMATTER or outputs the timestamp in simple YMD-ordered
* fallback formats, resembling ISO 8601.
*
* @since 0.1
*
* @licence GNU GPL v2+
* @author H. Snater < mediawiki@snater.com >
* @author Thiemo Mättig
*/
class TimeFormatter extends ValueFormatterBase {

/**
* Option for a custom timestamp formatter. Must contain an instance of a ValueFormatter
* subclass, capable of formatting TimeValue objects. The output of the custom formatter is
* threaded as plain text and passed through.
*/
const OPT_TIME_ISO_FORMATTER = 'time iso formatter';

/**
* Option to localize calendar models. Must contain an array mapping known calendar model URIs
* to localized calendar model names.
*/
const OPT_CALENDARNAMES = 'calendars';

/**
* @deprecated since 0.7, use \ValueParsers\TimeParser::CALENDAR_GREGORIAN instead.
*/
Expand All @@ -26,8 +41,6 @@ class TimeFormatter extends ValueFormatterBase {
*/
const CALENDAR_JULIAN = 'http://www.wikidata.org/entity/Q1985786';

const OPT_TIME_ISO_FORMATTER = 'time iso formatter';

/**
* @see ValueFormatterBase::__construct
*
Expand All @@ -37,28 +50,102 @@ public function __construct( FormatterOptions $options = null ) {
parent::__construct( $options );

$this->defaultOption( self::OPT_TIME_ISO_FORMATTER, null );
$this->defaultOption( self::OPT_CALENDARNAMES, array() );
}

/**
* @see ValueFormatter::format
*
* @param TimeValue $value The value to format
* @param TimeValue $value
*
* @throws InvalidArgumentException
* @return string
* @return string Plain text.
*/
public function format( $value ) {
if ( !( $value instanceof TimeValue ) ) {
throw new InvalidArgumentException( 'ValueFormatters\TimeFormatter can only format '
. 'instances of DataValues\TimeValue' );
throw new InvalidArgumentException( 'Data value type mismatch. Expected a TimeValue.' );
}

$isoFormatter = $this->getOption( self::OPT_TIME_ISO_FORMATTER );
if ( $isoFormatter instanceof ValueFormatter ) {
return $isoFormatter->format( $value );
$formatted = $this->getFormattedTimestamp( $value );
$formatted .= ' (' . $this->getFormattedCalendarModel( $value->getCalendarModel() ) . ')';
return $formatted;
}

/**
* @param TimeValue $value
*
* @return string Plain text.
*/
private function getFormattedTimestamp( TimeValue $value ) {
$formatter = $this->getOption( self::OPT_TIME_ISO_FORMATTER );

if ( $formatter instanceof ValueFormatter ) {
return $formatter->format( $value );
}

if ( preg_match(
// Loose check for ISO-like strings, as used in Gregorian and Julian time values.
'/^([-+]?)(\d+)-(\d+)-(\d+)T(?:(\d+):(\d+)(?::(\d+))?)?Z?$/i',
$value->getTime(),
$matches
) ) {
list( , $sign, $year, $month, $day, $hour, $minute, $second ) = $matches;

// Actual MINUS SIGN (U+2212) instead of HYPHEN-MINUS (U+002D)
$sign = $sign === '-' ? "\xE2\x88\x92" : '';

// Warning, never cast the year to integer to not run into 32-bit integer overflows!
$year = ltrim( $year, '0' );

if ( $value->getPrecision() <= TimeValue::PRECISION_YEAR ) {
return sprintf( '%s%04s', $sign, $year );
}

switch ( $value->getPrecision() ) {
case TimeValue::PRECISION_MONTH:
return sprintf(
'%s%04s-%02s',
$sign, $year, $month
);
case TimeValue::PRECISION_DAY:
return sprintf(
'%s%04s-%02s-%02s',
$sign, $year, $month, $day
);
case TimeValue::PRECISION_HOUR:
return sprintf(
'%s%04s-%02s-%02sT%02s',
$sign, $year, $month, $day, $hour
);
case TimeValue::PRECISION_MINUTE:
return sprintf(
'%s%04s-%02s-%02sT%02s:%02s',
$sign, $year, $month, $day, $hour, $minute
);
default:
return sprintf(
'%s%04s-%02s-%02sT%02s:%02s:%02s',
$sign, $year, $month, $day, $hour, $minute, $second
);
}
}

return $value->getTime();
}

/**
* @param string $calendarModel
*
* @return string Plain text.
*/
private function getFormattedCalendarModel( $calendarModel ) {
$calendarNames = $this->getOption( self::OPT_CALENDARNAMES );

if ( array_key_exists( $calendarModel, $calendarNames ) ) {
return $calendarNames[$calendarModel];
}

return $value->getTime() . ' (' . $value->getCalendarModel() . ')';
return $calendarModel;
}

}
190 changes: 122 additions & 68 deletions tests/ValueFormatters/TimeFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use DataValues\TimeValue;
use ValueFormatters\FormatterOptions;
use ValueFormatters\TimeFormatter;
use ValueFormatters\ValueFormatter;

/**
* @covers ValueFormatters\TimeFormatter
Expand All @@ -13,6 +15,7 @@
*
* @licence GNU GPL v2+
* @author H. Snater < mediawiki@snater.com >
* @author Thiemo Mättig
*/
class TimeFormatterTest extends ValueFormatterTestBase {

Expand All @@ -34,85 +37,136 @@ protected function getInstance( FormatterOptions $options = null ) {
return new TimeFormatter( $options );
}

/**
* @return ValueFormatter
*/
private function getTimestampFormatter() {
$mock = $this->getMockBuilder( 'ValueFormatters\ValueFormatter' )
->disableOriginalConstructor()
->getMock();

$mock->expects( $this->any() )
->method( 'format' )
->will( $this->returnValue( '<timestamp>' ) );

return $mock;
}

/**
* @see ValueFormatterTestBase::validProvider
*/
public function validProvider() {
$baseOptions = new FormatterOptions();
$baseOptions->setOption( TimeFormatter::OPT_CALENDARNAMES, array(
'Gregorian' => '<Gregorian>',
'Julian' => '<Julian>',
) );

$timestampFormatterOptions = clone $baseOptions;
$timestampFormatterOptions->setOption(
TimeFormatter::OPT_TIME_ISO_FORMATTER,
$this->getTimestampFormatter()
);

$tests = array(
'+00000002013-07-16T00:00:00Z (Gregorian)' => array(
'+00000002013-07-16T00:00:00Z',
0,
0,
0,
11,
'Gregorian'
),
'+00000000000-01-01T00:00:00Z (Gregorian)' => array(
'+00000000000-01-01T00:00:00Z',
0,
0,
0,
11,
'Gregorian'
),
'+00000000001-01-14T00:00:00Z (Julian)' => array(
'+00000000001-01-14T00:00:00Z',
0,
0,
0,
11,
'Julian'
),
'+00000010000-01-01T00:00:00Z (Gregorian)' => array(
'+00000010000-01-01T00:00:00Z',
0,
0,
0,
11,
'Gregorian'
),
'-00000000001-01-01T00:00:00Z (Gregorian)' => array(
'-00000000001-01-01T00:00:00Z',
0,
0,
0,
11,
'Gregorian'
),
'+00000002013-07-17T00:00:00Z (Gregorian)' => array(
'+00000002013-07-17T00:00:00Z',
0,
0,
0,
10,
'Gregorian'
),
'+00000002013-07-18T00:00:00Z (Gregorian)' => array(
'+00000002013-07-18T00:00:00Z',
0,
0,
0,
9,
'Gregorian'
),
'+00000002013-07-19T00:00:00Z (Gregorian)' => array(
'+00000002013-07-19T00:00:00Z',
0,
0,
0,
8,
'Gregorian'
'2013-07-16 (<Gregorian>)' => array(
'+2013-07-16T00:00:00Z',
),

// Custom timestamp formatter
'<timestamp> (<Gregorian>)' => array(
'+2013-07-16T00:00:00Z',
TimeValue::PRECISION_DAY,
'Gregorian',
$timestampFormatterOptions,
),

// Different calendar models
'1701-12-14 (<Julian>)' => array(
'+1701-12-14T00:00:00Z',
TimeValue::PRECISION_DAY,
'Julian',
),
'1702-12-14 (Stardate)' => array(
'+1702-12-14T00:00:00Z',
TimeValue::PRECISION_DAY,
'Stardate',
),

// Different years
"\xE2\x88\x9210000-01-01 (<Gregorian>)" => array(
'-010000-01-01T00:00:00Z',
),
"\xE2\x88\x920001-01-01 (<Gregorian>)" => array(
'-1-01-01T00:00:00Z',
),
"\xE2\x88\x920100-01-01 (<Gregorian>)" => array(
'-100-01-01T00:00:00Z',
),
"\xE2\x88\x920000-01-01 (<Gregorian>)" => array(
'-0-01-01T00:00:00Z',
),
'0000-01-01 (<Gregorian>)' => array(
'+0-01-01T00:00:00Z',
),
'0001-01-01 (<Gregorian>)' => array(
'+1-01-01T00:00:00Z',
),
'0100-01-01 (<Gregorian>)' => array(
'+100-01-01T00:00:00Z',
),
'10000-01-01 (<Gregorian>)' => array(
'+010000-01-01T00:00:00Z',
),

// Different precisions
'2000 (<Gregorian>)' => array(
'+2000-01-01T00:00:00Z',
TimeValue::PRECISION_Ga,
),
'2008 (<Gregorian>)' => array(
'+2008-01-08T00:00:00Z',
TimeValue::PRECISION_10a,
),
'2009 (<Gregorian>)' => array(
'+2009-01-09T00:00:00Z',
TimeValue::PRECISION_YEAR,
),
'2010-07 (<Gregorian>)' => array(
'+2010-07-10T00:00:00Z',
TimeValue::PRECISION_MONTH,
),
'2011-07-11 (<Gregorian>)' => array(
'+2011-07-11T00:00:00Z',
TimeValue::PRECISION_DAY,
),
'2012-07-12T00 (<Gregorian>)' => array(
'+2012-07-12T00:00:00Z',
TimeValue::PRECISION_HOUR,
),
'2013-07-13T00:00 (<Gregorian>)' => array(
'+2013-07-13T00:00:00Z',
TimeValue::PRECISION_MINUTE,
),
'2014-07-14T00:00:00 (<Gregorian>)' => array(
'+2014-07-14T00:00:00Z',
TimeValue::PRECISION_SECOND,
),
);

$argLists = array();

// TODO: Test with different parser options.
$options = new FormatterOptions();

foreach ( $tests as $expected => $args ) {
$timeValue = new TimeValue( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
$argLists[] = array( $timeValue, $expected, $options );
$timestamp = $args[0];
$precision = isset( $args[1] ) ? $args[1] : TimeValue::PRECISION_DAY;
$calendarModel = isset( $args[2] ) ? $args[2] : 'Gregorian';
$options = isset( $args[3] ) ? $args[3] : $baseOptions;

$argLists[] = array(
new TimeValue( $timestamp, 0, 0, 0, $precision, $calendarModel ),
$expected,
$options
);
}

return $argLists;
Expand Down

0 comments on commit edaf292

Please sign in to comment.