Skip to content

Commit

Permalink
Major rewrite of TimeFormatter
Browse files Browse the repository at this point in the history
  • Loading branch information
thiemowmde committed Jun 4, 2015
1 parent 65edc81 commit 41762f1
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 38 deletions.
111 changes: 95 additions & 16 deletions src/ValueFormatters/TimeFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
use InvalidArgumentException;

/**
* Time formatter.
*
* Some code in this class has been borrowed from the
* MapsCoordinateParser class of the Maps extension for MediaWiki.
* 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 {

Expand All @@ -28,12 +28,21 @@ class TimeFormatter extends ValueFormatterBase {
*/
const CALENDAR_JULIAN = TimeValue::CALENDAR_JULIAN;

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

/**
* 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';

/**
* @since 0.1
* @see ValueFormatterBase::__construct
*
* @param FormatterOptions|null $options
*/
Expand All @@ -47,27 +56,97 @@ public function __construct( FormatterOptions $options = null ) {
/**
* @see ValueFormatter::format
*
* @since 0.1
*
* @param TimeValue $value The value to format
* @param TimeValue $value
*
* @return string
* @throws InvalidArgumentException
* @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.' );
}

$formatted = $value->getTime();
$formatted = $this->getFormattedTimestamp( $value );
// FIXME: Temporarily disabled.
//$formatted .= ' (' . $this->getFormattedCalendarModel( self::CALENDAR_GREGORIAN ) . ')';
return $formatted;
}

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

$isoFormatter = $this->getOption( self::OPT_TIME_ISO_FORMATTER );
if ( $isoFormatter instanceof ValueFormatter ) {
$formatted = $isoFormatter->format( $value );
if ( $formatter instanceof ValueFormatter ) {
return $formatter->format( $value );
}

return $formatted;
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 $calendarModel;
}

}
124 changes: 102 additions & 22 deletions tests/ValueFormatters/TimeFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use DataValues\TimeValue;
use ValueFormatters\FormatterOptions;
use ValueFormatters\TimeFormatter;
use ValueFormatters\ValueFormatter;

/**
* @covers ValueFormatters\TimeFormatter
Expand Down Expand Up @@ -36,54 +37,133 @@ 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() {
$gregorian = 'http://www.wikidata.org/entity/Q1985727';
$julian = 'http://www.wikidata.org/entity/Q1985786';

$baseOptions = new FormatterOptions();
$baseOptions->setOption( TimeFormatter::OPT_CALENDARNAMES, array(
$gregorian => '<Gregorian>',
$julian => '<Julian>',
) );

$timestampFormatterOptions = new FormatterOptions();
$timestampFormatterOptions->setOption(
TimeFormatter::OPT_TIME_ISO_FORMATTER,
$this->getTimestampFormatter()
);

$tests = array(
'+2013-07-16T00:00:00Z' => array(
'+00000002013-07-16T00:00:00Z',
'2013-07-16' => array(
'+2013-07-16T00:00:00Z',
),
'+0000-01-01T00:00:00Z' => array(
'+00000000000-01-01T00:00:00Z',

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

// Different calendar models
'1701-12-14' => array(
'+1701-12-14T00:00:00Z',
TimeValue::PRECISION_DAY,
$julian,
),
'+0001-01-14T00:00:00Z' => array(
'+00000000001-01-14T00:00:00Z',
'1702-12-14' => array(
'+1702-12-14T00:00:00Z',
TimeValue::PRECISION_DAY,
$julian
'Stardate',
),
'+10000-01-01T00:00:00Z' => array(
'+00000010000-01-01T00:00:00Z',

// Different years
"\xE2\x88\x9210000-01-01" => array(
'-010000-01-01T00:00:00Z',
),
'-0001-01-01T00:00:00Z' => array(
'-00000000001-01-01T00:00:00Z',
"\xE2\x88\x920001-01-01" => array(
'-1-01-01T00:00:00Z',
),
'+2013-07-17T00:00:00Z' => array(
'+00000002013-07-17T00:00:00Z',
TimeValue::PRECISION_MONTH,
"\xE2\x88\x920100-01-01" => array(
'-100-01-01T00:00:00Z',
),
'+2013-07-18T00:00:00Z' => array(
'+00000002013-07-18T00:00:00Z',
TimeValue::PRECISION_YEAR,
"\xE2\x88\x920000-01-01" => array(
'-0-01-01T00:00:00Z',
),
'+2013-07-19T00:00:00Z' => array(
'+00000002013-07-19T00:00:00Z',
'0000-01-01' => array(
'+0-01-01T00:00:00Z',
),
'0001-01-01' => array(
'+1-01-01T00:00:00Z',
),
'0100-01-01' => array(
'+100-01-01T00:00:00Z',
),
'10000-01-01' => array(
'+010000-01-01T00:00:00Z',
),

// Different precisions
'2000' => array(
'+2000-01-01T00:00:00Z',
TimeValue::PRECISION_Ga,
),
'2008' => array(
'+2008-01-08T00:00:00Z',
TimeValue::PRECISION_10a,
),
'2009' => array(
'+2009-01-09T00:00:00Z',
TimeValue::PRECISION_YEAR,
),
'2010-07' => array(
'+2010-07-10T00:00:00Z',
TimeValue::PRECISION_MONTH,
),
'2011-07-11' => array(
'+2011-07-11T00:00:00Z',
TimeValue::PRECISION_DAY,
),
'2012-07-12T00' => array(
'+2012-07-12T00:00:00Z',
TimeValue::PRECISION_HOUR,
),
'2013-07-13T00:00' => array(
'+2013-07-13T00:00:00Z',
TimeValue::PRECISION_MINUTE,
),
'2014-07-14T00:00:00' => 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 ) {
$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 ),
Expand Down

0 comments on commit 41762f1

Please sign in to comment.