Completed
Pull Request — master (#106)
by no
02:19
created

tests/ValueParsers/IsoTimestampParserTest.php (4 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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