IsoTimestampParserTest::testInvalidOptions()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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