Completed
Pull Request — master (#113)
by no
05:11 queued 02:43
created

tests/ValueParsers/IsoTimestampParserTest.php (1 issue)

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