Completed
Push — precisionOption ( 604f81 )
by no
02:21
created

YearMonthDayTimeParser::getCalendarModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
3
namespace ValueParsers;
4
5
use DataValues\TimeValue;
6
7
/**
8
 * A straight time parser with a strict rule set that only accepts YMD, DMY, MDY and YDM formatted
9
 * dates if they can not be confused with an other format.
10
 *
11
 * @since 0.8.1
12
 *
13
 * @licence GNU GPL v2+
14
 * @author Thiemo Mättig
15
 */
16
class YearMonthDayTimeParser extends StringValueParser {
17
18
	const FORMAT_NAME = 'datetime';
19
20
	/**
21
	 * @var ValueParser
22
	 */
23
	private $eraParser;
24
25
	/**
26
	 * @var ValueParser
27
	 */
28
	private $isoTimestampParser;
29
30
	/**
31
	 * @param ValueParser|null $eraParser String parser that detects signs, "BC" suffixes and such and
32
	 * returns an array with the detected sign character and the remaining value.
33
	 * @param ParserOptions|null $options
34
	 */
35
	public function __construct( ValueParser $eraParser = null, ParserOptions $options = null ) {
36
		parent::__construct( $options );
37
38
		$this->defaultOption( IsoTimestampParser::OPT_PRECISION, null );
39
		$this->defaultOption( IsoTimestampParser::OPT_PRECISION, null );
40
41
		$this->eraParser = $eraParser ?: new EraParser();
42
		$this->isoTimestampParser = new IsoTimestampParser( null, $this->options );
43
	}
44
45
	/**
46
	 * @param string $value
47
	 *
48
	 * @throws ParseException
49
	 * @return TimeValue
50
	 */
51
	protected function stringParse( $value ) {
52
		try {
53
			list( $sign, $preparsedValue ) = $this->eraParser->parse( $value );
54
			list( $signedYear, $month, $day ) = $this->parseYearMonthDay( $preparsedValue );
55
56
			if ( substr( $signedYear, 0, 1 ) !== '-' ) {
57
				$signedYear = $sign . $signedYear;
58
			} elseif ( $sign === '-' ) {
59
				throw new ParseException( 'Two eras found' );
60
			}
61
62
			return $this->newTimeValue( $signedYear, $month, $day );
63
		} catch ( ParseException $ex ) {
64
			throw new ParseException( $ex->getMessage(), $value, self::FORMAT_NAME );
65
		}
66
	}
67
68
	/**
69
	 * @param string $value
70
	 *
71
	 * @throws ParseException
72
	 * @return string[]
73
	 */
74
	private function parseYearMonthDay( $value ) {
75
		if ( !preg_match( '/^\D*?(-?\d+)\D+(\d+)\D+?(-?\d+)\D*$/', $value, $matches ) ) {
76
			throw new ParseException( 'Can not find three numbers' );
77
		}
78
79
		// A 32 in the first spot can not be confused with anything.
80
		if ( $matches[1] < 1 || $matches[1] > 31 ) {
81
			if ( $matches[3] > 12 || $matches[2] == $matches[3] ) {
82
				list( , $signedYear, $month, $day ) = $matches;
83
			} elseif ( $matches[2] > 12 ) {
84
				list( , $signedYear, $day, $month ) = $matches;
85
			} else {
86
				throw new ParseException( 'Can not distinguish YDM and YMD' );
87
			}
88
		} elseif ( $matches[3] < 1 || $matches[3] > 59
89
			// A 59 in the third spot may be a second, but can not if the first number is > 24.
90
			// A 31 in the last spot may be the day, but can not if it's negative.
91
			|| ( abs( $matches[1] ) > 24 && $matches[3] > 31 )
92
		) {
93
			if ( $matches[1] > 12 || $matches[1] == $matches[2] ) {
94
				list( , $day, $month, $signedYear ) = $matches;
95
			} elseif ( $matches[2] > 12 ) {
96
				list( , $month, $day, $signedYear ) = $matches;
97
			} else {
98
				throw new ParseException( 'Can not distinguish DMY and MDY' );
99
			}
100
		} else {
101
			// Formats DYM and MYD do not exist.
102
			throw new ParseException( 'Can not identify year' );
103
		}
104
105
		return array( $signedYear, $month, $day );
106
	}
107
108
	/**
109
	 * @param string $signedYear
110
	 * @param string $month
111
	 * @param string $day
112
	 *
113
	 * @throws ParseException
114
	 * @return TimeValue
115
	 */
116
	private function newTimeValue( $signedYear, $month, $day ) {
117
		if ( $month < 1 || $month > 12 ) {
118
			throw new ParseException( 'Month out of range' );
119
		} elseif ( $day < 1 || $day > 31 ) {
120
			throw new ParseException( 'Day out of range' );
121
		}
122
123
		return $this->isoTimestampParser->parse(
124
			sprintf( '%s-%02s-%02sT00:00:00Z', $signedYear, $month, $day )
125
		);
126
	}
127
128
}
129