Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thiemowmde committed Jul 22, 2015
1 parent c0d14e9 commit 8deb93e
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 84 deletions.
112 changes: 43 additions & 69 deletions src/ValueParsers/DateFormatParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class DateFormatParser extends StringValueParser {

const OPT_DATE_FORMAT = 'dateFormat';
const OPT_DIGIT_TRANSFORM_TABLE = 'digitTransformTable';

/**
* Must be a two-dimensional array, the first dimension mapping the month's numbers 1 to 12 to
* arrays of month
*/
const OPT_MONTH_NAMES = 'monthNames';

public function __construct( ParserOptions $options = null ) {
Expand All @@ -40,9 +45,9 @@ public function __construct( ParserOptions $options = null ) {
*/
protected function stringParse( $value ) {
$format = $this->getDateFormat();
$numberCharacters = $this->getNumberCharacters();
$pattern = '';
$formatLength = strlen( $format );
$numberPattern = '[' . $this->getNumberCharacters() . ']';
$pattern = '';

for ( $p = 0; $p < $formatLength; $p++ ) {
$code = $format[$p];
Expand All @@ -51,56 +56,48 @@ protected function stringParse( $value ) {
$code .= $format[++$p];
}

if ( preg_match( '/^x[ijkmot]$/', $code ) && $p < $formatLength - 1 ) {
if ( preg_match( '<^x[ijkmot]$>', $code ) && $p < $formatLength - 1 ) {
$code .= $format[++$p];
}

switch ( $code ) {
case 'Y':
$pattern .= '(?P<year>[' . $numberCharacters . ']+)\p{Z}*';
$pattern .= '(?P<year>' . $numberPattern . '+)\p{Z}*';
break;
case 'F':
case 'm':
case 'M':
case 'n':
case 'xg':
$pattern .= '(?P<month>[' . $numberCharacters . ']{1,2}';
foreach ( $this->getMonthNames() as $i => $monthNames ) {
$pattern .= '|(?P<month' . $i . '>'
. implode( '|', array_map(
function( $monthName ) {
return preg_quote( $monthName, '/' );
}, $monthNames
) )
. ')';
}
$pattern .= ')\p{P}*\p{Z}*';
$pattern .= '(?P<month>' . $numberPattern . '{1,2}'
. $this->getMonthNamesPattern()
. ')\p{P}*\p{Z}*';
break;
case 'd':
case 'j':
$pattern .= '(?P<day>[' . $numberCharacters . ']{1,2})\p{P}*\p{Z}*';
$pattern .= '(?P<day>' . $numberPattern . '{1,2})\p{P}*\p{Z}*';
break;
case 'G':
case 'H':
$pattern .= '(?P<hour>[' . $numberCharacters . ']{1,2})\p{Z}*';
$pattern .= '(?P<hour>' . $numberPattern . '{1,2})\p{Z}*';
break;
case 'i':
$pattern .= '(?P<minute>[' . $numberCharacters . ']{1,2})\p{Z}*';
$pattern .= '(?P<minute>' . $numberPattern . '{1,2})\p{Z}*';
break;
case 's':
$pattern .= '(?P<second>[' . $numberCharacters . ']{1,2})\p{Z}*';
$pattern .= '(?P<second>' . $numberPattern . '{1,2})\p{Z}*';
break;
case '\\':
if ( $p < $formatLength - 1 ) {
$pattern .= preg_quote( $format[++$p], '/' );
$pattern .= preg_quote( $format[++$p] );
} else {
$pattern .= '\\';
}
break;
case '"':
$endQuote = strpos( $format, '"', $p + 1 );
if ( $endQuote !== false ) {
$pattern .= preg_quote( substr( $format, $p + 1, $endQuote - $p - 1 ), '/' );
$pattern .= preg_quote( substr( $format, $p + 1, $endQuote - $p - 1 ) );
$p = $endQuote;
} else {
$pattern .= '"';
Expand All @@ -111,104 +108,82 @@ function( $monthName ) {
// We can ignore raw and raw toggle when parsing
break;
default:
if ( preg_match( '/^\p{P}+$/u', $format[$p] ) ) {
if ( preg_match( '<^\p{P}+$>u', $format[$p] ) ) {
$pattern .= '\p{P}*';
} elseif ( preg_match( '/^\p{Z}+$/u', $format[$p] ) ) {
} elseif ( preg_match( '<^\p{Z}+$>u', $format[$p] ) ) {
$pattern .= '\p{Z}*';
} else {
$pattern .= preg_quote( $format[$p], '/' );
$pattern .= preg_quote( $format[$p] );
}
}
}

$isMatch = preg_match( '/^\p{Z}*' . $pattern . '$/iu', $value, $matches );
// if ( $isMatch ) { var_dump( $matches ); die(); }
$isMatch = preg_match( '<^\p{Z}*' . $pattern . '$>iu', $value, $matches );
if ( $isMatch && isset( $matches['year'] ) ) {
$precision = TimeValue::PRECISION_YEAR;
$time = array( $this->normalizeNumber( $matches['year'] ), 0, 0, 0, 0, 0 );
$time = array( $this->parseFormattedNumber( $matches['year'] ), 0, 0, 0, 0, 0 );

// if ( $value === '05:42, 4. Mär. 1201' ) { var_dump( $matches ); die(); }
if ( isset( $matches['month'] ) ) {
$precision = TimeValue::PRECISION_MONTH;
$time[1] = $this->findMonthMatch( $matches );
}

if ( isset( $matches['day'] ) ) {
$precision = TimeValue::PRECISION_DAY;
$time[2] = $this->normalizeNumber( $matches['day'] );
$time[2] = $this->parseFormattedNumber( $matches['day'] );
}

if ( isset( $matches['hour'] ) ) {
$precision = TimeValue::PRECISION_HOUR;
$time[3] = $this->normalizeNumber( $matches['hour'] );
$time[3] = $this->parseFormattedNumber( $matches['hour'] );
}

if ( isset( $matches['minute'] ) ) {
$precision = TimeValue::PRECISION_MINUTE;
$time[4] = $this->normalizeNumber( $matches['minute'] );
$time[4] = $this->parseFormattedNumber( $matches['minute'] );
}

if ( isset( $matches['second'] ) ) {
$precision = TimeValue::PRECISION_SECOND;
$time[5] = $this->normalizeNumber( $matches['second'] );
$time[5] = $this->parseFormattedNumber( $matches['second'] );
}

// TODO: Check for Int32 overflows.
$timestamp = vsprintf( '%+.0f-%02d-%02dT%02d:%02d:%02dZ', $time );
// if ( $month[0] === 'M' && $i === 3 ) { var_dump( $month, $regex, preg_match( $regex, $month ) ); die(); }
return new TimeValue( $timestamp, 0, 0, 0, $precision, TimeValue::CALENDAR_GREGORIAN );
}

throw new ParseException( "Failed to parse $value ("
. $this->parseFormattedNumber( $value ) . ')'.$pattern, $value );
. $this->parseFormattedNumber( $value ) . ')', $value );
}

/**
* @param string[] $matches
*
* @return int
* @return string
*/
private function findMonthMatch( $matches ) {
for ( $i = 1; $i <= 12; $i++ ) {
if ( !empty( $matches['month' . $i] ) ) {
return $i;
}
private function getMonthNamesPattern() {
$pattern = '';

foreach ( $this->getMonthNames() as $i => $monthNames ) {
$pattern .= '|(?P<month' . $i . '>'
. implode( '|', array_map( 'preg_quote', (array)$monthNames ) )
. ')';
}

return $this->normalizeMonth( $matches['month'] );
return $pattern;
}

/**
* @param string $month
* @param string[] $matches
*
* @return int
*/
private function normalizeMonth( $month ) {
foreach ( $this->getMonthNames() as $i => $monthNames ) {
$pattern = '/^\p{Z}*('
. implode( '|', array_map(
function( $monthName ) {
return preg_quote( preg_replace( '/\p{P}+$/u', '', $monthName ), '/' );
}, $monthNames
) )
. ')[\p{P}\p{Z}]*$/iu';

if ( preg_match( $pattern, $month ) ) {
private function findMonthMatch( $matches ) {
for ( $i = 1; $i <= 12; $i++ ) {
if ( !empty( $matches['month' . $i] ) ) {
return $i;
}
}

return $this->normalizeNumber( $month );
}

/**
* @param string $number
*
* @return double
*/
private function normalizeNumber( $number ) {
$number = $this->parseFormattedNumber( $number );
return doubleval( preg_replace( '/\D+/s', '', $number ) );
return $this->parseFormattedNumber( $matches['month'] );
}

/**
Expand All @@ -232,12 +207,11 @@ private function parseFormattedNumber( $number ) {
* @return string
*/
private function getNumberCharacters() {
// TODO: Should there be a relaxed mode with \p{N} instead of \d?
$numberCharacters = '\d';

$transformTable = $this->getDigitTransformTable();
if ( is_array( $transformTable ) ) {
$numberCharacters .= preg_quote( implode( '', $transformTable ), '/' );
$numberCharacters .= preg_quote( implode( '', $transformTable ) );
}

return $numberCharacters;
Expand Down
77 changes: 62 additions & 15 deletions tests/ValueParsers/DateFormatParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,37 +39,84 @@ protected function getInstance() {
* @see ValueParserTestBase::validInputProvider
*/
public function validInputProvider() {
$monthNames = array( 9 => array( 'Sep', 'September' ) );

$valid = array(
array(
'Default options' => array(
'1 9 2014',
'd. M Y', null, null,
'd M Y', null, null,
'+2014-09-01T00:00:00Z'
),
'Transform map' => array(
'Z g Zo15',
'd M Y', array( '0' => 'o', 2 => 'Z', 9 => 'g' ), null,
'+2015-09-02T00:00:00Z'
),
'Default month map' => array(
'1. September 2014',
'd. M Y', null, $monthNames,
'+2014-09-01T00:00:00Z'
),
array(
'Simple month map' => array(
'1 September 2014',
'd. M Y', null, array( 9 => array( 'September' ) ),
'd M Y', null, array( 9 => 'September' ),
'+2014-09-01T00:00:00Z'
),
array(
'1. Sep. 2014',
'd. M Y', null, array( 9 => array( 'Sep' ) ),
'Escapes' => array(
'1s 9s 2014',
'd\s M\s Y', null, null,
'+2014-09-01T00:00:00Z'
),
array(
'1. September 2014',
'd. M Y', null, array( 9 => array( 'September' ) ),
'Quotes' => array(
'1th 9th 2014',
'd"th" M"th" Y', null, null,
'+2014-09-01T00:00:00Z'
),
'Raw modifiers' => array(
'1 9 2014',
'xn xN', null, null,
'+2014-09-01T00:00:00Z'
),
array(
'1.September.2014',
'd. M Y', null, array( 9 => array( 'September' ) ),
'Whitespace is optional' => array(
'1September2014',
'd M Y', null, $monthNames,
'+2014-09-01T00:00:00Z'
),
'Delimiters are optional' => array(
'1 9 2014',
'd. M. Y', null, null,
'+2014-09-01T00:00:00Z'
),
'Delimiters are ignored' => array(
'1. 9. 2014',
'd M Y', null, null,
'+2014-09-01T00:00:00Z'
),
'Year precision' => array(
'2014',
'Y', null, null,
'+2014-00-00T00:00:00Z', TimeValue::PRECISION_YEAR
),
'Month precision' => array(
'9 2014',
'M Y', null, null,
'+2014-09-00T00:00:00Z', TimeValue::PRECISION_MONTH
),
'Minute precision' => array(
'1 9 2014 15:30',
'd M Y H:i', null, null,
'+2014-09-01T15:30:00Z', TimeValue::PRECISION_MINUTE
),
'Second precision' => array(
'1 9 2014 15:30:59',
'd M Y H:i:s', null, null,
'+2014-09-01T15:30:59Z', TimeValue::PRECISION_SECOND
),
);

$cases = array();

foreach ( $valid as $args ) {
foreach ( $valid as $key => $args ) {
$dateString = $args[0];
$dateFormat = $args[1];
$digitTransformTable = $args[2];
Expand All @@ -78,7 +125,7 @@ public function validInputProvider() {
$precision = isset( $args[5] ) ? $args[5] : TimeValue::PRECISION_DAY;
$calendarModel = isset( $args[6] ) ? $args[6] : TimeValue::CALENDAR_GREGORIAN;

$cases[] = array(
$cases[$key] = array(
$dateString,
new TimeValue( $timestamp, 0, 0, 0, $precision, $calendarModel ),
new DateFormatParser( new ParserOptions( array(
Expand Down

0 comments on commit 8deb93e

Please sign in to comment.