YearMonthTimeParser   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 113
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 18
lcom 1
cbo 5
dl 0
loc 113
ccs 38
cts 38
cp 1
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
B stringParse() 0 34 10
A parseMonth() 0 9 3
A getTimeFromYearMonth() 0 7 2
A canBeMonth() 0 3 2
1
<?php
2
3
namespace ValueParsers;
4
5
use DataValues\TimeValue;
6
7
/**
8
 * A parser that accepts various date formats with month precision. Prefers month/year order when
9
 * both numbers are valid months, e.g. "12/10" is December 2010. Should be called before
10
 * YearTimeParser when you want to accept both formats, because strings like "1 999" may either
11
 * represent a month and a year or a year with digit grouping.
12
 *
13
 * @since 0.8.4
14
 *
15
 * @license GPL-2.0-or-later
16
 * @author Addshore
17
 * @author Thiemo Kreuz
18
 *
19
 * @todo match BCE dates in here
20
 */
21
class YearMonthTimeParser extends StringValueParser {
22
23
	private const FORMAT_NAME = 'year-month';
24
25
	/**
26
	 * @var int[] Array mapping localized month names to month numbers (1 to 12).
27
	 */
28
	private $monthNumbers;
29
30
	/**
31
	 * @var ValueParser
32
	 */
33
	private $isoTimestampParser;
34
35
	/**
36
	 * @see StringValueParser::__construct
37
	 *
38
	 * @param MonthNameProvider $monthNameProvider
39
	 * @param ParserOptions|null $options
40
	 */
41 70
	public function __construct(
42
		MonthNameProvider $monthNameProvider,
43
		ParserOptions $options = null
44
	) {
45 70
		parent::__construct( $options );
46
47 70
		$languageCode = $this->getOption( ValueParser::OPT_LANG );
48 70
		$this->monthNumbers = $monthNameProvider->getMonthNumbers( $languageCode );
49 70
		$this->isoTimestampParser = new IsoTimestampParser( null, $this->options );
50 70
	}
51
52
	/**
53
	 * @see StringValueParser::stringParse
54
	 *
55
	 * @param string $value
56
	 *
57
	 * @throws ParseException
58
	 * @return TimeValue
59
	 */
60 63
	protected function stringParse( $value ) {
61
		// Matches year and month separated by a separator.
62
		// \p{L} matches letters outside the ASCII range.
63 63
		$regex = '/^(-?[\d\p{L}]+)\s*?[\/\-\s.,]\s*(-?[\d\p{L}]+)$/u';
64 63
		if ( !preg_match( $regex, trim( $value ), $matches ) ) {
65 11
			throw new ParseException( 'Failed to parse year and month', $value, self::FORMAT_NAME );
66
		}
67 52
		list( , $a, $b ) = $matches;
68
69 52
		$aIsInt = preg_match( '/^-?\d+$/', $a );
70 52
		$bIsInt = preg_match( '/^-?\d+$/', $b );
71
72 52
		if ( $aIsInt && $bIsInt ) {
73 30
			if ( $this->canBeMonth( $a ) ) {
74 21
				return $this->getTimeFromYearMonth( $b, $a );
75 9
			} elseif ( $this->canBeMonth( $b ) ) {
76 9
				return $this->getTimeFromYearMonth( $a, $b );
77
			}
78 22
		} elseif ( $aIsInt ) {
79 3
			$month = $this->parseMonth( $b );
80
81 3
			if ( $month ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
82 3
				return $this->getTimeFromYearMonth( $a, $month );
83
			}
84 19
		} elseif ( $bIsInt ) {
85 18
			$month = $this->parseMonth( $a );
86
87 18
			if ( $month ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $month of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
88 16
				return $this->getTimeFromYearMonth( $b, $month );
89
			}
90
		}
91
92 8
		throw new ParseException( 'Failed to parse year and month', $value, self::FORMAT_NAME );
93
	}
94
95
	/**
96
	 * @param string $month
97
	 *
98
	 * @return int|null
99
	 */
100 21
	private function parseMonth( $month ) {
101 21
		foreach ( $this->monthNumbers as $monthName => $i ) {
102 21
			if ( strcasecmp( $monthName, $month ) === 0 ) {
103 19
				return $i;
104
			}
105
		}
106
107 2
		return null;
108
	}
109
110
	/**
111
	 * @param string $year
112
	 * @param string $month as a canonical month number
113
	 *
114
	 * @return TimeValue
115
	 */
116 44
	private function getTimeFromYearMonth( $year, $month ) {
117 44
		if ( $year[0] !== '-' ) {
118 40
			$year = '+' . $year;
119
		}
120
121 44
		return $this->isoTimestampParser->parse( sprintf( '%s-%02s-00T00:00:00Z', $year, $month ) );
122
	}
123
124
	/**
125
	 * @param string $value
126
	 *
127
	 * @return bool can the given value be a month?
128
	 */
129 30
	private function canBeMonth( $value ) {
130 30
		return $value >= 0 && $value <= 12;
131
	}
132
133
}
134