Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major rewrite of TimeFormatter #49

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 86 additions & 7 deletions src/ValueFormatters/TimeFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

/**
* 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 as it is.
* formatter given via self::OPT_TIME_ISO_FORMATTER or outputs the timestamp in simple YMD-ordered
* fallback formats, resembling ISO 8601.
*
* @since 0.1
*
* @license GPL-2.0+
* @author H. Snater < mediawiki@snater.com >
* @author Thiemo Mättig
*/
class TimeFormatter extends ValueFormatterBase {

Expand Down Expand Up @@ -47,7 +49,11 @@ class TimeFormatter extends ValueFormatterBase {
public function __construct( FormatterOptions $options = null ) {
parent::__construct( $options );

$this->defaultOption( self::OPT_CALENDARNAMES, array() );
// A non-localized default is still better than showing full concept URIs.
$this->defaultOption( self::OPT_CALENDARNAMES, array(
TimeValue::CALENDAR_GREGORIAN => 'Gregorian',
TimeValue::CALENDAR_JULIAN => 'Julian',
) );
$this->defaultOption( self::OPT_TIME_ISO_FORMATTER, null );
}

Expand All @@ -64,14 +70,87 @@ public function format( $value ) {
throw new InvalidArgumentException( 'Data value type mismatch. Expected a TimeValue.' );
}

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

$isoFormatter = $this->getOption( self::OPT_TIME_ISO_FORMATTER );
if ( $isoFormatter instanceof ValueFormatter ) {
$formatted = $isoFormatter->format( $value );
/**
* @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 );
}

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
) ) {
return $value->getTime();
}

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
);
}
}

/**
* @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;
}

}
109 changes: 90 additions & 19 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,6 +37,18 @@ protected function getInstance( FormatterOptions $options = null ) {
return new TimeFormatter( $options );
}

/**
* @return ValueFormatter
*/
private function getTimestampFormatter() {
$mock = $this->getMock( 'ValueFormatters\ValueFormatter' );
$mock->expects( $this->any() )
->method( 'format' )
->will( $this->returnValue( '<timestamp>' ) );

return $mock;
}

/**
* @see ValueFormatterTestBase::validProvider
*/
Expand All @@ -44,42 +57,100 @@ public function validProvider() {
$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(
'2013-07-16' => array(
'+2013-07-16T00:00:00Z',
),
'+0000-01-01T00:00:00Z' => array(
'+0000-01-01T00:00:00Z',

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

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

// Different years
'+10000-01-01T00:00:00Z' => array(
'+10000-01-01T00:00:00Z',
"\xE2\x88\x9210000-01-01" => array(
'-010000-01-01T00:00:00Z',
),
"\xE2\x88\x920001-01-01" => array(
'-1-01-01T00:00:00Z',
),
"\xE2\x88\x920100-01-01" => array(
'-100-01-01T00:00:00Z',
),
"\xE2\x88\x920000-01-01" => array(
'-0-01-01T00:00:00Z',
),
'-0001-01-01T00:00:00Z' => array(
'-0001-01-01T00: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
'+2013-07-17T00:00:00Z' => array(
'+2013-07-17T00:00:00Z',
TimeValue::PRECISION_MONTH,
'2000' => array(
'+2000-01-01T00:00:00Z',
TimeValue::PRECISION_YEAR1G,
),
'+2013-07-18T00:00:00Z' => array(
'+2013-07-18T00:00:00Z',
'2008' => array(
'+2008-01-08T00:00:00Z',
TimeValue::PRECISION_YEAR10,
),
'2009' => array(
'+2009-01-09T00:00:00Z',
TimeValue::PRECISION_YEAR,
),
'+2013-07-19T00:00:00Z' => array(
'+2013-07-19T00:00:00Z',
TimeValue::PRECISION_YEAR10,
'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,
),
);

Expand All @@ -93,7 +164,7 @@ public function validProvider() {

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