Passed
Push — ymdMonthNames ( 4f0b3e...7ee93b )
by no
08:06
created

IsoTimestampParserTest::getInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
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
	 * @deprecated since DataValues Common 0.3, just use getInstance.
24
	 */
25
	protected function getParserClass() {
26
		throw new \LogicException( 'Should not be called, use getInstance' );
27
	}
28
29
	/**
30
	 * @see ValueParserTestBase::getInstance
31
	 *
32
	 * @return IsoTimestampParser
33
	 */
34
	protected function getInstance() {
35
		return new IsoTimestampParser();
36
	}
37
38
	/**
39
	 * @see ValueParserTestBase::validInputProvider
40
	 */
41
	public function validInputProvider() {
42
		$gregorian = 'http://www.wikidata.org/entity/Q1985727';
43
		$julian = 'http://www.wikidata.org/entity/Q1985786';
44
45
		$julianOpts = new ParserOptions();
46
		$julianOpts->setOption( IsoTimestampParser::OPT_CALENDAR, $julian );
47
48
		$gregorianOpts = new ParserOptions();
49
		$gregorianOpts->setOption( IsoTimestampParser::OPT_CALENDAR, $gregorian );
50
51
		$prec10aOpts = new ParserOptions();
52
		$prec10aOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_YEAR10 );
53
54
		$precDayOpts = new ParserOptions();
55
		$precDayOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_DAY );
56
57
		$precSecondOpts = new ParserOptions();
58
		$precSecondOpts->setOption( IsoTimestampParser::OPT_PRECISION, TimeValue::PRECISION_SECOND );
59
60
		$valid = array(
61
			// Whitespace
62
			"+0000000000002013-07-16T00:00:00Z\n" => array(
63
				'+2013-07-16T00:00:00Z',
64
				TimeValue::PRECISION_DAY,
65
			),
66
			' +0000000000002013-07-00T00:00:00Z ' => array(
67
				'+2013-07-00T00:00:00Z',
68
				TimeValue::PRECISION_MONTH,
69
			),
70
71
			// Empty options tests
72
			'+0000000000002013-00-00T00:00:00Z' => array(
73
				'+2013-00-00T00:00:00Z',
74
				TimeValue::PRECISION_YEAR,
75
			),
76
			'+0000000000000000-00-00T00:00:00Z' => array(
77
				'+0000-00-00T00:00:00Z',
78
				TimeValue::PRECISION_YEAR,
79
				$julian
80
			),
81
			'+0000000000002000-00-00T00:00:00Z' => array(
82
				'+2000-00-00T00:00:00Z',
83
				TimeValue::PRECISION_YEAR,
84
			),
85
			'+0000000000008000-00-00T00:00:00Z' => array(
86
				'+8000-00-00T00:00:00Z',
87
				TimeValue::PRECISION_YEAR1K,
88
			),
89
			'+0000000000020000-00-00T00:00:00Z' => array(
90
				'+20000-00-00T00:00:00Z',
91
				TimeValue::PRECISION_YEAR10K,
92
			),
93
			'+0000000000200000-00-00T00:00:00Z' => array(
94
				'+200000-00-00T00:00:00Z',
95
				TimeValue::PRECISION_YEAR100K,
96
			),
97
			'+0000000002000000-00-00T00:00:00Z' => array(
98
				'+2000000-00-00T00:00:00Z',
99
				TimeValue::PRECISION_YEAR1M,
100
			),
101
			'+0000000020000000-00-00T00:00:00Z' => array(
102
				'+20000000-00-00T00:00:00Z',
103
				TimeValue::PRECISION_YEAR10M,
104
			),
105
			'+0000000200000000-00-00T00:00:00Z' => array(
106
				'+200000000-00-00T00:00:00Z',
107
				TimeValue::PRECISION_YEAR100M,
108
			),
109
			'+0000002000000000-00-00T00:00:00Z' => array(
110
				'+2000000000-00-00T00:00:00Z',
111
				TimeValue::PRECISION_YEAR1G,
112
			),
113
			'+2000000000000000-00-00T00:00:00Z' => array(
114
				'+2000000000000000-00-00T00:00:00Z',
115
				TimeValue::PRECISION_YEAR1G,
116
			),
117
			'-2000000000000000-00-00T00:00:00Z' => array(
118
				'-2000000000000000-00-00T00:00:00Z',
119
				TimeValue::PRECISION_YEAR1G,
120
				$julian
121
			),
122
			'+0000000000002013-07-16T00:00:00Z (Gregorian)' => array(
123
				'+2013-07-16T00:00:00Z',
124
				TimeValue::PRECISION_DAY,
125
			),
126
			'+0000000000000000-01-01T00:00:00Z (Gregorian)' => array(
127
				'+0000-01-01T00:00:00Z',
128
				TimeValue::PRECISION_DAY,
129
130
			),
131
			'+0000000000002001-01-14T00:00:00Z (Julian)' => array(
132
				'+2001-01-14T00:00:00Z',
133
				TimeValue::PRECISION_DAY,
134
				$julian,
135
			),
136
			'+0000000000010000-01-01T00:00:00Z (Gregorian)' => array(
137
				'+10000-01-01T00:00:00Z',
138
				TimeValue::PRECISION_DAY,
139
			),
140
			'-0000000000000001-01-01T00:00:00Z (Gregorian)' => array(
141
				'-0001-01-01T00:00:00Z',
142
				TimeValue::PRECISION_DAY,
143
				$gregorian
144
			),
145
			'-00000000001-01-01T00:00:00Z (Gregorian)' => array(
146
				'-0001-01-01T00:00:00Z',
147
				TimeValue::PRECISION_DAY,
148
				$gregorian,
149
				$julianOpts // overridden by explicit calendar in input string
150
			),
151
			'-00000000001-01-01T00:00:00Z (Julian)' => array(
152
				'-0001-01-01T00:00:00Z',
153
				TimeValue::PRECISION_DAY,
154
				$julian,
155
				$gregorianOpts // overridden by explicit calendar in input string
156
			),
157
			'-000001-01-01T00:00:00Z (Gregorian)' => array(
158
				'-0001-01-01T00:00:00Z',
159
				TimeValue::PRECISION_DAY,
160
				$gregorian
161
			),
162
			'-1-01-01T00:00:00Z (Gregorian)' => array(
163
				'-0001-01-01T00:00:00Z',
164
				TimeValue::PRECISION_DAY,
165
				$gregorian
166
			),
167
168
			// Tests with different options
169
			'-1-01-02T00:00:00Z' => array(
170
				'-0001-01-02T00:00:00Z',
171
				TimeValue::PRECISION_DAY,
172
				$gregorian,
173
				$gregorianOpts,
174
			),
175
			'+2001-01-03T00:00:00Z' => array(
176
				'+2001-01-03T00:00:00Z',
177
				TimeValue::PRECISION_DAY,
178
				$julian,
179
				$julianOpts,
180
			),
181
			'-1-01-04T00:00:00Z' => array(
182
				'-0001-01-04T00:00:00Z',
183
				TimeValue::PRECISION_YEAR10,
184
				$julian,
185
				$prec10aOpts,
186
			),
187
			'-1-01-05T00:00:00Z' => array(
188
				'-0001-01-05T00:00:00Z',
189
				TimeValue::PRECISION_DAY,
190
				$julian,
191
			),
192
193
			'+1999-00-00T00:00:00Z' => array(
194
				'+1999-00-00T00:00:00Z',
195
				TimeValue::PRECISION_YEAR,
196
			),
197
			'+2000-00-00T00:00:00Z' => array(
198
				'+2000-00-00T00:00:00Z',
199
				TimeValue::PRECISION_YEAR,
200
			),
201
			'+2010-00-00T00:00:00Z' => array(
202
				'+2010-00-00T00:00:00Z',
203
				TimeValue::PRECISION_YEAR,
204
			),
205
206
			// Optional sign character
207
			'2015-01-01T00:00:00Z' => array(
208
				'+2015-01-01T00:00:00Z',
209
				TimeValue::PRECISION_DAY,
210
			),
211
212
			// Optional time zone
213
			'2015-01-01T00:00:00' => array(
214
				'+2015-01-01T00:00:00Z',
215
				TimeValue::PRECISION_DAY,
216
			),
217
218
			// Actual minus character from Unicode; roundtrip with TimeDetailsFormatter
219
			"\xE2\x88\x922015-01-01T00:00:00" => array(
220
				'-2015-01-01T00:00:00Z',
221
				TimeValue::PRECISION_DAY,
222
				$julian
223
			),
224
225
			// Optional colons
226
			'2015-01-01T161718' => array(
227
				'+0000000000002015-01-01T16:17:18Z',
228
				TimeValue::PRECISION_SECOND,
229
			),
230
			'2015-01-01T1617' => array(
231
				'+0000000000002015-01-01T16:17:00Z',
232
				TimeValue::PRECISION_MINUTE,
233
			),
234
235
			// Optional second
236
			'2015-01-01T00:00' => array(
237
				'+2015-01-01T00:00:00Z',
238
				TimeValue::PRECISION_DAY,
239
			),
240
241
			// Optional hour and minute
242
			'2015-01-01' => array(
243
				'+2015-01-01T00:00:00Z',
244
				TimeValue::PRECISION_DAY,
245
			),
246
			'60-01-01' => array(
247
				'+0060-01-01T00:00:00Z',
248
				TimeValue::PRECISION_DAY,
249
				$julian
250
			),
251
			// 32 can not be confused with anything. Can't be day or month. Can't be minute or
252
			// second because a time can not start with minute or second.
253
			'32-01-01' => array(
254
				'+0032-01-01T00:00:00Z',
255
				TimeValue::PRECISION_DAY,
256
				$julian
257
			),
258
259
			// Years <= 31 require either the time part or a year with more than 2 digits
260
			'1-01-01T00:00' => array(
261
				'+0001-01-01T00:00:00Z',
262
				TimeValue::PRECISION_DAY,
263
				$julian
264
			),
265
			'001-01-01' => array(
266
				'+0001-01-01T00:00:00Z',
267
				TimeValue::PRECISION_DAY,
268
				$julian
269
			),
270
			// 00-00-00 to 24-00-00 can be confused with a time, but not if it is signed.
271
			'-01-02-03' => array(
272
				'-0001-02-03T00:00:00Z',
273
				TimeValue::PRECISION_DAY,
274
				$julian
275
			),
276
			'+01-02-03' => array(
277
				'+0001-02-03T00:00:00Z',
278
				TimeValue::PRECISION_DAY,
279
				$julian
280
			),
281
282
			// Day zero
283
			'2015-01-00' => array(
284
				'+2015-01-00T00:00:00Z',
285
				TimeValue::PRECISION_MONTH,
286
			),
287
288
			// Month zero
289
			'2015-00-00' => array(
290
				'+2015-00-00T00:00:00Z',
291
				TimeValue::PRECISION_YEAR,
292
			),
293
294
			// Leap seconds are a valid concept
295
			'+2015-01-01T00:00:61Z' => array(
296
				'+2015-01-01T00:00:61Z',
297
				TimeValue::PRECISION_SECOND,
298
			),
299
300
			// Tests for correct precision when a bad precision is passed through the opts
301
			// @see https://bugzilla.wikimedia.org/show_bug.cgi?id=62730
302
			'+0000000000000012-12-00T00:00:00Z' => array(
303
				'+0012-12-00T00:00:00Z',
304
				TimeValue::PRECISION_MONTH,
305
				$julian,
306
				$precDayOpts,
307
			),
308
			'+2015-01-01T00:00:00Z' => array(
309
				'+2015-01-01T00:00:00Z',
310
				TimeValue::PRECISION_SECOND,
311
				$gregorian,
312
				$precSecondOpts,
313
			),
314
315
			// Test Julian/Gregorian switch in October 1582.
316
			'1583-01-01' => array(
317
				'+1583-01-01T00:00:00Z',
318
				TimeValue::PRECISION_DAY,
319
				$gregorian
320
			),
321
322
			// Test Julian/Gregorian switch in October 1582.
323
			'1582-08-01' => array(
324
				'+1582-08-01T00:00:00Z',
325
				TimeValue::PRECISION_DAY,
326
				$julian
327
			),
328
		);
329
330
		$argLists = array();
331
332
		foreach ( $valid as $key => $value ) {
333
			$timestamp = $value[0];
334
			$precision = isset( $value[1] ) ? $value[1] : TimeValue::PRECISION_DAY;
335
			$calendarModel = isset( $value[2] ) ? $value[2] : $gregorian;
336
			$options = isset( $value[3] ) ? $value[3] : null;
337
338
			$argLists[] = array(
339
				// Because PHP magically turns numeric keys into ints/floats
340
				(string)$key,
341
				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 333 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 335 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...
342
				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 336 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...
343
			);
344
		}
345
346
		return $argLists;
347
	}
348
349
	/**
350
	 * @see ValueParserTestBase::invalidInputProvider
351
	 */
352
	public function invalidInputProvider() {
353
		$argLists = array();
354
355
		$invalid = array(
356
			// Stuff that's not even a string
357
			true,
358
			false,
359
			null,
360
			array(),
361
			'foooooooooo',
362
			'1 June 2014',
363
			// Can be confused with a time (HMS), DMY or MDY
364
			'00-00-00',
365
			'12-01-01',
366
			'24-00-00',
367
			'31-01-01',
368
			// Month and day must be two digits
369
			'2015-12-1',
370
			'2015-1-31',
371
			// Time and seconds are optional, but hour without minutes is not allowed
372
			'+2015-12-31T23',
373
			'+2015-12-31T23Z',
374
			// Elements out of allowed bounds
375
			'+2015-00-01T00:00:00Z',
376
			'+2015-01-00T01:00:00Z',
377
			'+2015-01-00T00:01:00Z',
378
			'+2015-01-00T00:00:01Z',
379
			'+2015-13-01T00:00:00Z',
380
			'+2015-01-32T00:00:00Z',
381
			'+2015-01-01T24:00:00Z',
382
			'+2015-01-01T00:60:00Z',
383
			'+2015-01-01T00:00:62Z',
384
			// This parser should not replace the year parser
385
			'1234567890873',
386
			2134567890
387
		);
388
389
		foreach ( $invalid as $value ) {
390
			$argLists[] = array( $value );
391
		}
392
393
		return $argLists;
394
	}
395
396
	/**
397
	 * @dataProvider optionsProvider
398
	 */
399
	public function testOptions(
400
		$value,
401
		array $options,
402
		$timestamp,
403
		$calendarModel,
404
		$precision = TimeValue::PRECISION_DAY
405
	) {
406
		$parser = new IsoTimestampParser( null, new ParserOptions( $options ) );
407
		$this->assertEquals(
408
			new TimeValue( $timestamp, 0, 0, 0, $precision, $calendarModel ),
409
			$parser->parse( $value )
410
		);
411
	}
412
413
	public function optionsProvider() {
414
		$gregorian = 'http://www.wikidata.org/entity/Q1985727';
415
		$julian = 'http://www.wikidata.org/entity/Q1985786';
416
417
		return array(
418
			'Auto-detected Gregorian' => array(
419
				'1583-01-31',
420
				array(),
421
				'+1583-01-31T00:00:00Z',
422
				$gregorian
423
			),
424
			'Option overrides auto-detected Gregorian' => array(
425
				'1583-01-31',
426
				array( IsoTimestampParser::OPT_CALENDAR => $julian ),
427
				'+1583-01-31T00:00:00Z',
428
				$julian
429
			),
430
			'Auto-detected Julian' => array(
431
				'1582-01-31',
432
				array(),
433
				'+1582-01-31T00:00:00Z',
434
				$julian
435
			),
436
			'Option overrides auto-detected Julian' => array(
437
				'1582-01-31',
438
				array( IsoTimestampParser::OPT_CALENDAR => $gregorian ),
439
				'+1582-01-31T00:00:00Z',
440
				$gregorian
441
			),
442
			'Option can decrease precision' => array(
443
				'2016-01-31',
444
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_MONTH ),
445
				'+2016-01-31T00:00:00Z',
446
				$gregorian,
447
				TimeValue::PRECISION_MONTH
448
			),
449
			'Option can set minimal precision' => array(
450
				'2016-01-31',
451
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_YEAR1G ),
452
				'+2016-01-31T00:00:00Z',
453
				$gregorian,
454
				TimeValue::PRECISION_YEAR1G
455
			),
456
			'Option can not increase year precision' => array(
457
				'2016-00-00',
458
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_MONTH ),
459
				'+2016-00-00T00:00:00Z',
460
				$gregorian,
461
				TimeValue::PRECISION_YEAR
462
			),
463
			'Option can not increase month precision' => array(
464
				'2016-01-00',
465
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_DAY ),
466
				'+2016-01-00T00:00:00Z',
467
				$gregorian,
468
				TimeValue::PRECISION_MONTH
469
			),
470
			'Option can increase day precision' => array(
471
				'2016-01-31',
472
				array( IsoTimestampParser::OPT_PRECISION => TimeValue::PRECISION_HOUR ),
473
				'+2016-01-31T00:00:00Z',
474
				$gregorian,
475
				TimeValue::PRECISION_HOUR
476
			),
477
			'Precision option accepts strings' => array(
478
				'2016-01-31',
479
				array( IsoTimestampParser::OPT_PRECISION => '10' ),
480
				'+2016-01-31T00:00:00Z',
481
				$gregorian,
482
				TimeValue::PRECISION_MONTH
483
			),
484
		);
485
	}
486
487
	/**
488
	 * @dataProvider invalidOptionsProvider
489
	 */
490
	public function testInvalidOptions( array $options ) {
491
		$parser = new IsoTimestampParser( null, new ParserOptions( $options ) );
492
		$this->setExpectedException( ParseException::class );
493
		$parser->parse( '2016-01-31' );
494
	}
495
496
	public function invalidOptionsProvider() {
497
		return array(
498
			array( array( IsoTimestampParser::OPT_PRECISION => -1 ) ),
499
			array( array( IsoTimestampParser::OPT_PRECISION => 1.5 ) ),
500
			array( array( IsoTimestampParser::OPT_PRECISION => 1000 ) ),
501
			array( array( IsoTimestampParser::OPT_PRECISION => 'invalid' ) ),
502
		);
503
	}
504
505
}
506