Passed
Push — master ( 7af698...31dd3f )
by Jeroen De
01:04 queued 34s
created

IsoTimestampParserTest::getParserClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace ValueParsers\Test;
4
5
use DataValues\TimeValue;
6
use ValueParsers\IsoTimestampParser;
7
use ValueParsers\ParseException;
8
use ValueParsers\ParserOptions;
9
10
/**
11
 * @covers ValueParsers\IsoTimestampParser
12
 *
13
 * @group DataValue
14
 * @group DataValueExtensions
15
 *
16
 * @license GPL-2.0+
17
 * @author Addshore
18
 * @author Thiemo Kreuz
19
 */
20
class IsoTimestampParserTest extends ValueParserTestBase {
21
22
	/**
23
	 * @see ValueParserTestBase::getInstance
24
	 *
25
	 * @return IsoTimestampParser
26
	 */
27
	protected function getInstance() {
28
		return new IsoTimestampParser();
29
	}
30
31
	/**
32
	 * @see ValueParserTestBase::validInputProvider
33
	 */
34
	public function validInputProvider() {
35
		$gregorian = 'http://www.wikidata.org/entity/Q1985727';
36
		$julian = 'http://www.wikidata.org/entity/Q1985786';
37
38
		$julianOpts = new ParserOptions();
39
		$julianOpts->setOption( IsoTimestampParser::OPT_CALENDAR, $julian );
40
41
		$gregorianOpts = new ParserOptions();
42
		$gregorianOpts->setOption( IsoTimestampParser::OPT_CALENDAR, $gregorian );
43
44
		$prec10aOpts = new ParserOptions();
45
		$prec10aOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_YEAR10 );
46
47
		$precDayOpts = new ParserOptions();
48
		$precDayOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_DAY );
49
50
		$precSecondOpts = new ParserOptions();
51
		$precSecondOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_SECOND );
52
53
		$valid = array(
54
			// Whitespace
55
			"+0000000000002013-07-16T00:00:00Z\n" => array(
56
				'+2013-07-16T00:00:00Z',
57
				TimeValue::PRECISION_DAY,
58
			),
59
			' +0000000000002013-07-00T00:00:00Z ' => array(
60
				'+2013-07-00T00:00:00Z',
61
				TimeValue::PRECISION_MONTH,
62
			),
63
64
			// Empty options tests
65
			'+0000000000002013-00-00T00:00:00Z' => array(
66
				'+2013-00-00T00:00:00Z',
67
				TimeValue::PRECISION_YEAR,
68
			),
69
			'+0000000000000000-00-00T00:00:00Z' => array(
70
				'+0000-00-00T00:00:00Z',
71
				TimeValue::PRECISION_YEAR,
72
				$julian
73
			),
74
			'+0000000000002000-00-00T00:00:00Z' => array(
75
				'+2000-00-00T00:00:00Z',
76
				TimeValue::PRECISION_YEAR,
77
			),
78
			'+0000000000008000-00-00T00:00:00Z' => array(
79
				'+8000-00-00T00:00:00Z',
80
				TimeValue::PRECISION_YEAR1K,
81
			),
82
			'+0000000000020000-00-00T00:00:00Z' => array(
83
				'+20000-00-00T00:00:00Z',
84
				TimeValue::PRECISION_YEAR10K,
85
			),
86
			'+0000000000200000-00-00T00:00:00Z' => array(
87
				'+200000-00-00T00:00:00Z',
88
				TimeValue::PRECISION_YEAR100K,
89
			),
90
			'+0000000002000000-00-00T00:00:00Z' => array(
91
				'+2000000-00-00T00:00:00Z',
92
				TimeValue::PRECISION_YEAR1M,
93
			),
94
			'+0000000020000000-00-00T00:00:00Z' => array(
95
				'+20000000-00-00T00:00:00Z',
96
				TimeValue::PRECISION_YEAR10M,
97
			),
98
			'+0000000200000000-00-00T00:00:00Z' => array(
99
				'+200000000-00-00T00:00:00Z',
100
				TimeValue::PRECISION_YEAR100M,
101
			),
102
			'+0000002000000000-00-00T00:00:00Z' => array(
103
				'+2000000000-00-00T00:00:00Z',
104
				TimeValue::PRECISION_YEAR1G,
105
			),
106
			'+2000000000000000-00-00T00:00:00Z' => array(
107
				'+2000000000000000-00-00T00:00:00Z',
108
				TimeValue::PRECISION_YEAR1G,
109
			),
110
			'-2000000000000000-00-00T00:00:00Z' => array(
111
				'-2000000000000000-00-00T00:00:00Z',
112
				TimeValue::PRECISION_YEAR1G,
113
				$julian
114
			),
115
			'+0000000000002013-07-16T00:00:00Z (Gregorian)' => array(
116
				'+2013-07-16T00:00:00Z',
117
				TimeValue::PRECISION_DAY,
118
			),
119
			'+0000000000000000-01-01T00:00:00Z (Gregorian)' => array(
120
				'+0000-01-01T00:00:00Z',
121
				TimeValue::PRECISION_DAY,
122
123
			),
124
			'+0000000000002001-01-14T00:00:00Z (Julian)' => array(
125
				'+2001-01-14T00:00:00Z',
126
				TimeValue::PRECISION_DAY,
127
				$julian,
128
			),
129
			'+0000000000010000-01-01T00:00:00Z (Gregorian)' => array(
130
				'+10000-01-01T00:00:00Z',
131
				TimeValue::PRECISION_DAY,
132
			),
133
			'-0000000000000001-01-01T00:00:00Z (Gregorian)' => array(
134
				'-0001-01-01T00:00:00Z',
135
				TimeValue::PRECISION_DAY,
136
				$gregorian
137
			),
138
			'-00000000001-01-01T00:00:00Z (Gregorian)' => array(
139
				'-0001-01-01T00:00:00Z',
140
				TimeValue::PRECISION_DAY,
141
				$gregorian,
142
				$julianOpts // overridden by explicit calendar in input string
143
			),
144
			'-00000000001-01-01T00:00:00Z (Julian)' => array(
145
				'-0001-01-01T00:00:00Z',
146
				TimeValue::PRECISION_DAY,
147
				$julian,
148
				$gregorianOpts // overridden by explicit calendar in input string
149
			),
150
			'-000001-01-01T00:00:00Z (Gregorian)' => array(
151
				'-0001-01-01T00:00:00Z',
152
				TimeValue::PRECISION_DAY,
153
				$gregorian
154
			),
155
			'-1-01-01T00:00:00Z (Gregorian)' => array(
156
				'-0001-01-01T00:00:00Z',
157
				TimeValue::PRECISION_DAY,
158
				$gregorian
159
			),
160
161
			// Tests with different options
162
			'-1-01-02T00:00:00Z' => array(
163
				'-0001-01-02T00:00:00Z',
164
				TimeValue::PRECISION_DAY,
165
				$gregorian,
166
				$gregorianOpts,
167
			),
168
			'+2001-01-03T00:00:00Z' => array(
169
				'+2001-01-03T00:00:00Z',
170
				TimeValue::PRECISION_DAY,
171
				$julian,
172
				$julianOpts,
173
			),
174
			'-1-01-04T00:00:00Z' => array(
175
				'-0001-01-04T00:00:00Z',
176
				TimeValue::PRECISION_YEAR10,
177
				$julian,
178
				$prec10aOpts,
179
			),
180
			'-1-01-05T00:00:00Z' => array(
181
				'-0001-01-05T00:00:00Z',
182
				TimeValue::PRECISION_DAY,
183
				$julian,
184
			),
185
186
			'+1999-00-00T00:00:00Z' => array(
187
				'+1999-00-00T00:00:00Z',
188
				TimeValue::PRECISION_YEAR,
189
			),
190
			'+2000-00-00T00:00:00Z' => array(
191
				'+2000-00-00T00:00:00Z',
192
				TimeValue::PRECISION_YEAR,
193
			),
194
			'+2010-00-00T00:00:00Z' => array(
195
				'+2010-00-00T00:00:00Z',
196
				TimeValue::PRECISION_YEAR,
197
			),
198
199
			// Optional sign character
200
			'2015-01-01T00:00:00Z' => array(
201
				'+2015-01-01T00:00:00Z',
202
				TimeValue::PRECISION_DAY,
203
			),
204
205
			// Optional time zone
206
			'2015-01-01T00:00:00' => array(
207
				'+2015-01-01T00:00:00Z',
208
				TimeValue::PRECISION_DAY,
209
			),
210
211
			// Actual minus character from Unicode; roundtrip with TimeDetailsFormatter
212
			"\xE2\x88\x922015-01-01T00:00:00" => array(
213
				'-2015-01-01T00:00:00Z',
214
				TimeValue::PRECISION_DAY,
215
				$julian
216
			),
217
218
			// Optional colons
219
			'2015-01-01T161718' => array(
220
				'+0000000000002015-01-01T16:17:18Z',
221
				TimeValue::PRECISION_SECOND,
222
			),
223
			'2015-01-01T1617' => array(
224
				'+0000000000002015-01-01T16:17:00Z',
225
				TimeValue::PRECISION_MINUTE,
226
			),
227
228
			// Optional second
229
			'2015-01-01T00:00' => array(
230
				'+2015-01-01T00:00:00Z',
231
				TimeValue::PRECISION_DAY,
232
			),
233
234
			// Optional hour and minute
235
			'2015-01-01' => array(
236
				'+2015-01-01T00:00:00Z',
237
				TimeValue::PRECISION_DAY,
238
			),
239
			'60-01-01' => array(
240
				'+0060-01-01T00:00:00Z',
241
				TimeValue::PRECISION_DAY,
242
				$julian
243
			),
244
			// 32 can not be confused with anything. Can't be day or month. Can't be minute or
245
			// second because a time can not start with minute or second.
246
			'32-01-01' => array(
247
				'+0032-01-01T00:00:00Z',
248
				TimeValue::PRECISION_DAY,
249
				$julian
250
			),
251
252
			// Years <= 31 require either the time part or a year with more than 2 digits
253
			'1-01-01T00:00' => array(
254
				'+0001-01-01T00:00:00Z',
255
				TimeValue::PRECISION_DAY,
256
				$julian
257
			),
258
			'001-01-01' => array(
259
				'+0001-01-01T00:00:00Z',
260
				TimeValue::PRECISION_DAY,
261
				$julian
262
			),
263
			// 00-00-00 to 24-00-00 can be confused with a time, but not if it is signed.
264
			'-01-02-03' => array(
265
				'-0001-02-03T00:00:00Z',
266
				TimeValue::PRECISION_DAY,
267
				$julian
268
			),
269
			'+01-02-03' => array(
270
				'+0001-02-03T00:00:00Z',
271
				TimeValue::PRECISION_DAY,
272
				$julian
273
			),
274
275
			// Day zero
276
			'2015-01-00' => array(
277
				'+2015-01-00T00:00:00Z',
278
				TimeValue::PRECISION_MONTH,
279
			),
280
281
			// Month zero
282
			'2015-00-00' => array(
283
				'+2015-00-00T00:00:00Z',
284
				TimeValue::PRECISION_YEAR,
285
			),
286
287
			// Leap seconds are a valid concept
288
			'+2015-01-01T00:00:61Z' => array(
289
				'+2015-01-01T00:00:61Z',
290
				TimeValue::PRECISION_SECOND,
291
			),
292
293
			// Tests for correct precision when a bad precision is passed through the opts
294
			// @see https://bugzilla.wikimedia.org/show_bug.cgi?id=62730
295
			'+0000000000000012-12-00T00:00:00Z' => array(
296
				'+0012-12-00T00:00:00Z',
297
				TimeValue::PRECISION_MONTH,
298
				$julian,
299
				$precDayOpts,
300
			),
301
			'+2015-01-01T00:00:00Z' => array(
302
				'+2015-01-01T00:00:00Z',
303
				TimeValue::PRECISION_SECOND,
304
				$gregorian,
305
				$precSecondOpts,
306
			),
307
308
			// Test Julian/Gregorian switch in October 1582.
309
			'1583-01-01' => array(
310
				'+1583-01-01T00:00:00Z',
311
				TimeValue::PRECISION_DAY,
312
				$gregorian
313
			),
314
315
			// Test Julian/Gregorian switch in October 1582.
316
			'1582-08-01' => array(
317
				'+1582-08-01T00:00:00Z',
318
				TimeValue::PRECISION_DAY,
319
				$julian
320
			),
321
		);
322
323
		$argLists = array();
324
325
		foreach ( $valid as $key => $value ) {
326
			$timestamp = $value[0];
327
			$precision = isset( $value[1] ) ? $value[1] : TimeValue::PRECISION_DAY;
328
			$calendarModel = isset( $value[2] ) ? $value[2] : $gregorian;
329
			$options = isset( $value[3] ) ? $value[3] : null;
330
331
			$argLists[] = array(
332
				// Because PHP magically turns numeric keys into ints/floats
333
				(string)$key,
334
				new TimeValue( $timestamp, 0, 0, 0, $precision, $calendarModel ),
0 ignored issues
show
Bug introduced by
It seems like $timestamp defined by $value[0] on line 326 can also be of type object<ValueParsers\ParserOptions>; however, DataValues\TimeValue::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $calendarModel defined by isset($value[2]) ? $value[2] : $gregorian on line 328 can also be of type object<ValueParsers\ParserOptions>; however, DataValues\TimeValue::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
335
				new IsoTimestampParser( null, $options )
0 ignored issues
show
Bug introduced by
It seems like $options defined by isset($value[3]) ? $value[3] : null on line 329 can also be of type string; however, ValueParsers\IsoTimestampParser::__construct() does only seem to accept null|object<ValueParsers\ParserOptions>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
336
			);
337
		}
338
339
		return $argLists;
340
	}
341
342
	/**
343
	 * @see ValueParserTestBase::invalidInputProvider
344
	 */
345
	public function invalidInputProvider() {
346
		$argLists = array();
347
348
		$invalid = array(
349
			// Stuff that's not even a string
350
			true,
351
			false,
352
			null,
353
			array(),
354
			'foooooooooo',
355
			'1 June 2014',
356
			// Can be confused with a time (HMS), DMY or MDY
357
			'00-00-00',
358
			'12-01-01',
359
			'24-00-00',
360
			'31-01-01',
361
			// Month and day must be two digits
362
			'2015-12-1',
363
			'2015-1-31',
364
			// Time and seconds are optional, but hour without minutes is not allowed
365
			'+2015-12-31T23',
366
			'+2015-12-31T23Z',
367
			// Elements out of allowed bounds
368
			'+2015-00-01T00:00:00Z',
369
			'+2015-01-00T01:00:00Z',
370
			'+2015-01-00T00:01:00Z',
371
			'+2015-01-00T00:00:01Z',
372
			'+2015-13-01T00:00:00Z',
373
			'+2015-01-32T00:00:00Z',
374
			'+2015-01-01T24:00:00Z',
375
			'+2015-01-01T00:60:00Z',
376
			'+2015-01-01T00:00:62Z',
377
			// This parser should not replace the year parser
378
			'1234567890873',
379
			2134567890
380
		);
381
382
		foreach ( $invalid as $value ) {
383
			$argLists[] = array( $value );
384
		}
385
386
		return $argLists;
387
	}
388
389
	/**
390
	 * @dataProvider optionsProvider
391
	 */
392
	public function testOptions(
393
		$value,
394
		array $options,
395
		$timestamp,
396
		$calendarModel,
397
		$precision = TimeValue::PRECISION_DAY
398
	) {
399
		$parser = new IsoTimestampParser( null, new ParserOptions( $options ) );
400
		$this->assertEquals(
401
			new TimeValue( $timestamp, 0, 0, 0, $precision, $calendarModel ),
402
			$parser->parse( $value )
403
		);
404
	}
405
406
	public function optionsProvider() {
407
		$gregorian = 'http://www.wikidata.org/entity/Q1985727';
408
		$julian = 'http://www.wikidata.org/entity/Q1985786';
409
410
		return array(
411
			'Auto-detected Gregorian' => array(
412
				'1583-01-31',
413
				array(),
414
				'+1583-01-31T00:00:00Z',
415
				$gregorian
416
			),
417
			'Option overrides auto-detected Gregorian' => array(
418
				'1583-01-31',
419
				array( IsoTimestampParser::OPT_CALENDAR => $julian ),
420
				'+1583-01-31T00:00:00Z',
421
				$julian
422
			),
423
			'Auto-detected Julian' => array(
424
				'1582-01-31',
425
				array(),
426
				'+1582-01-31T00:00:00Z',
427
				$julian
428
			),
429
			'Option overrides auto-detected Julian' => array(
430
				'1582-01-31',
431
				array( IsoTimestampParser::OPT_CALENDAR => $gregorian ),
432
				'+1582-01-31T00:00:00Z',
433
				$gregorian
434
			),
435
			'Option can decrease precision' => array(
436
				'2016-01-31',
437
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_MONTH ),
438
				'+2016-01-31T00:00:00Z',
439
				$gregorian,
440
				TimeValue::PRECISION_MONTH
441
			),
442
			'Option can set minimal precision' => array(
443
				'2016-01-31',
444
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_YEAR1G ),
445
				'+2016-01-31T00:00:00Z',
446
				$gregorian,
447
				TimeValue::PRECISION_YEAR1G
448
			),
449
			'Option can not increase year precision' => array(
450
				'2016-00-00',
451
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_MONTH ),
452
				'+2016-00-00T00:00:00Z',
453
				$gregorian,
454
				TimeValue::PRECISION_YEAR
455
			),
456
			'Option can not increase month precision' => array(
457
				'2016-01-00',
458
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_DAY ),
459
				'+2016-01-00T00:00:00Z',
460
				$gregorian,
461
				TimeValue::PRECISION_MONTH
462
			),
463
			'Option can increase day precision' => array(
464
				'2016-01-31',
465
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_HOUR ),
466
				'+2016-01-31T00:00:00Z',
467
				$gregorian,
468
				TimeValue::PRECISION_HOUR
469
			),
470
			'Precision option accepts strings' => array(
471
				'2016-01-31',
472
				array( IsoTimestampParser::OPT_PRECISION => '10' ),
473
				'+2016-01-31T00:00:00Z',
474
				$gregorian,
475
				TimeValue::PRECISION_MONTH
476
			),
477
		);
478
	}
479
480
	/**
481
	 * @dataProvider invalidOptionsProvider
482
	 */
483
	public function testInvalidOptions( array $options ) {
484
		$parser = new IsoTimestampParser( null, new ParserOptions( $options ) );
485
		$this->setExpectedException( ParseException::class );
486
		$parser->parse( '2016-01-31' );
487
	}
488
489
	public function invalidOptionsProvider() {
490
		return array(
491
			array( array( IsoTimestampParser::OPT_PRECISION => -1 ) ),
492
			array( array( IsoTimestampParser::OPT_PRECISION => 1.5 ) ),
493
			array( array( IsoTimestampParser::OPT_PRECISION => 1000 ) ),
494
			array( array( IsoTimestampParser::OPT_PRECISION => 'invalid' ) ),
495
		);
496
	}
497
498
}
499