Completed
Push — precisionOption ( 604f81 )
by no
02:21
created

IsoTimestampParserTest::testOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 13
rs 9.4285
cc 1
eloc 10
nc 1
nop 5
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
Bug introduced by
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...
Bug introduced by
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
Bug introduced by
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...
Bug introduced by
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