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

tests/ValueParsers/IsoTimestampParserTest.php (3 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\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
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...
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
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