Completed
Push — master ( 18fd13...95cdad )
by Marius
24s
created

TimeValue::normalizeIsoTimestamp()   C

Complexity

Conditions 13
Paths 9

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 37
rs 5.1234
cc 13
eloc 24
nc 9
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace DataValues;
4
5
/**
6
 * Class representing a time value.
7
 * @see https://www.mediawiki.org/wiki/Wikibase/DataModel#Dates_and_times
8
 *
9
 * @since 0.1
10
 *
11
 * @license GPL-2.0+
12
 * @author Jeroen De Dauw < [email protected] >
13
 * @author Thiemo Mättig
14
 */
15
class TimeValue extends DataValueObject {
16
17
	/**
18
	 * @deprecated since 0.8, use PRECISION_YEAR1G instead
19
	 */
20
	const PRECISION_Ga = self::PRECISION_YEAR1G;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_Ga should be defined in uppercase
Loading history...
21
22
	/**
23
	 * @deprecated since 0.8, use PRECISION_YEAR100M instead
24
	 */
25
	const PRECISION_100Ma = self::PRECISION_YEAR100M;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_100Ma should be defined in uppercase
Loading history...
26
27
	/**
28
	 * @deprecated since 0.8, use PRECISION_YEAR10M instead
29
	 */
30
	const PRECISION_10Ma = self::PRECISION_YEAR10M;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_10Ma should be defined in uppercase
Loading history...
31
32
	/**
33
	 * @deprecated since 0.8, use PRECISION_YEAR1M instead
34
	 */
35
	const PRECISION_Ma = self::PRECISION_YEAR1M;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_Ma should be defined in uppercase
Loading history...
36
37
	/**
38
	 * @deprecated since 0.8, use PRECISION_YEAR100K instead
39
	 */
40
	const PRECISION_100ka = self::PRECISION_YEAR100K;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_100ka should be defined in uppercase
Loading history...
41
42
	/**
43
	 * @deprecated since 0.8, use PRECISION_YEAR10K instead
44
	 */
45
	const PRECISION_10ka = self::PRECISION_YEAR10K;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_10ka should be defined in uppercase
Loading history...
46
47
	/**
48
	 * @deprecated since 0.8, use PRECISION_YEAR1K instead
49
	 */
50
	const PRECISION_ka = self::PRECISION_YEAR1K;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_ka should be defined in uppercase
Loading history...
51
52
	/**
53
	 * @deprecated since 0.8, use PRECISION_YEAR100 instead
54
	 */
55
	const PRECISION_100a = self::PRECISION_YEAR100;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_100a should be defined in uppercase
Loading history...
56
57
	/**
58
	 * @deprecated since 0.8, use PRECISION_YEAR10 instead
59
	 */
60
	const PRECISION_10a = self::PRECISION_YEAR10;
0 ignored issues
show
Coding Style introduced by
Constant PRECISION_10a should be defined in uppercase
Loading history...
61
62
	/**
63
	 * @since 0.8
64
	 */
65
	const PRECISION_YEAR1G = 0;
66
67
	/**
68
	 * @since 0.8
69
	 */
70
	const PRECISION_YEAR100M = 1;
71
72
	/**
73
	 * @since 0.8
74
	 */
75
	const PRECISION_YEAR10M = 2;
76
77
	/**
78
	 * @since 0.8
79
	 */
80
	const PRECISION_YEAR1M = 3;
81
82
	/**
83
	 * @since 0.8
84
	 */
85
	const PRECISION_YEAR100K = 4;
86
87
	/**
88
	 * @since 0.8
89
	 */
90
	const PRECISION_YEAR10K = 5;
91
92
	/**
93
	 * @since 0.8
94
	 */
95
	const PRECISION_YEAR1K = 6;
96
97
	/**
98
	 * @since 0.8
99
	 */
100
	const PRECISION_YEAR100 = 7;
101
102
	/**
103
	 * @since 0.8
104
	 */
105
	const PRECISION_YEAR10 = 8;
106
107
	const PRECISION_YEAR = 9;
108
	const PRECISION_MONTH = 10;
109
	const PRECISION_DAY = 11;
110
	const PRECISION_HOUR = 12;
111
	const PRECISION_MINUTE = 13;
112
	const PRECISION_SECOND = 14;
113
114
	/**
115
	 * @since 0.7.1
116
	 */
117
	const CALENDAR_GREGORIAN = 'http://www.wikidata.org/entity/Q1985727';
118
119
	/**
120
	 * @since 0.7.1
121
	 */
122
	const CALENDAR_JULIAN = 'http://www.wikidata.org/entity/Q1985786';
123
124
	/**
125
	 * Timestamp describing a point in time. The actual format depends on the calendar model.
126
	 *
127
	 * Gregorian and Julian dates use the same YMD ordered format, resembling ISO 8601, e.g.
128
	 * +2013-01-01T00:00:00Z. In this format the year is always signed and padded with zero
129
	 * characters to have between 4 and 16 digits. Month and day can be zero, indicating they are
130
	 * unknown. The timezone suffix Z is meaningless and must be ignored. Use getTimezone() instead.
131
	 *
132
	 * @see $timezone
133
	 * @see $calendarModel
134
	 *
135
	 * @var string
136
	 */
137
	private $timestamp;
138
139
	/**
140
	 * Unit used for the getBefore() and getAfter() values. Use one of the self::PRECISION_...
141
	 * constants.
142
	 *
143
	 * @var int
144
	 */
145
	private $precision;
146
147
	/**
148
	 * If the date is uncertain, how many units after the given time could it be?
149
	 * The unit is given by the precision.
150
	 *
151
	 * @var int Amount
152
	 */
153
	private $after;
154
155
	/**
156
	 * If the date is uncertain, how many units before the given time could it be?
157
	 * The unit is given by the precision.
158
	 *
159
	 * @var int Amount
160
	 */
161
	private $before;
162
163
	/**
164
	 * Time zone information as an offset from UTC in minutes.
165
	 *
166
	 * @var int Minutes
167
	 */
168
	private $timezone;
169
170
	/**
171
	 * URI identifying the calendar model. The actual timestamp should be in this calendar model,
172
	 * but note that there is nothing this class can do to enforce this convention.
173
	 *
174
	 * @var string URI
175
	 */
176
	private $calendarModel;
177
178
	/**
179
	 * @since 0.1
180
	 *
181
	 * @param string $timestamp Timestamp in a format resembling ISO 8601.
182
	 * @param int $timezone Time zone offset from UTC in minutes.
183
	 * @param int $before Number of units given by the precision.
184
	 * @param int $after Number of units given by the precision.
185
	 * @param int $precision One of the self::PRECISION_... constants.
186
	 * @param string $calendarModel An URI identifying the calendar model.
187
	 *
188
	 * @throws IllegalValueException
189
	 */
190
	public function __construct( $timestamp, $timezone, $before, $after, $precision, $calendarModel ) {
191
		if ( !is_string( $timestamp ) || $timestamp === '' ) {
192
			throw new IllegalValueException( '$timestamp must be a non-empty string' );
193
		}
194
195
		if ( !is_int( $timezone ) ) {
196
			throw new IllegalValueException( '$timezone must be an integer' );
197
		} elseif ( $timezone < -12 * 3600 || $timezone > 14 * 3600 ) {
198
			throw new IllegalValueException( '$timezone out of allowed bounds' );
199
		}
200
201
		if ( !is_int( $before ) || $before < 0 ) {
202
			throw new IllegalValueException( '$before must be an unsigned integer' );
203
		}
204
205
		if ( !is_int( $after ) || $after < 0 ) {
206
			throw new IllegalValueException( '$after must be an unsigned integer' );
207
		}
208
209
		if ( !is_int( $precision ) ) {
210
			throw new IllegalValueException( '$precision must be an integer' );
211
		} elseif ( $precision < self::PRECISION_YEAR1G || $precision > self::PRECISION_SECOND ) {
212
			throw new IllegalValueException( '$precision out of allowed bounds' );
213
		}
214
215
		// XXX: Enforce an IRI? Or at least a size limit?
216
		if ( !is_string( $calendarModel ) || $calendarModel === '' ) {
217
			throw new IllegalValueException( '$calendarModel must be a non-empty string' );
218
		}
219
220
		$this->timestamp = $this->normalizeIsoTimestamp( $timestamp );
221
		$this->timezone = $timezone;
222
		$this->before = $before;
223
		$this->after = $after;
224
		$this->precision = $precision;
225
		$this->calendarModel = $calendarModel;
226
	}
227
228
	/**
229
	 * @param string $timestamp
230
	 *
231
	 * @throws IllegalValueException
232
	 * @return string
233
	 */
234
	private function normalizeIsoTimestamp( $timestamp ) {
235
		if ( !preg_match(
236
			'/^([-+])(\d{1,16})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/',
237
			$timestamp,
238
			$matches
239
		) ) {
240
			throw new IllegalValueException( '$timestamp must resemble ISO 8601, given ' . $timestamp );
241
		}
242
243
		list( , $sign, $year, $month, $day, $hour, $minute, $second ) = $matches;
244
245
		if ( $month > 12 ) {
246
			throw new IllegalValueException( 'Month out of allowed bounds' );
247
		} elseif ( $day > 31 ) {
248
			throw new IllegalValueException( 'Day out of allowed bounds' );
249
		} elseif ( $hour > 23 ) {
250
			throw new IllegalValueException( 'Hour out of allowed bounds' );
251
		} elseif ( $minute > 59 ) {
252
			throw new IllegalValueException( 'Minute out of allowed bounds' );
253
		} elseif ( $second > 61 ) {
254
			throw new IllegalValueException( 'Second out of allowed bounds' );
255
		}
256
257
		if ( $month < 1 && $day > 0 ) {
258
			throw new IllegalValueException( 'Can not have a day with no month' );
259
		}
260
261
		if ( $day < 1 && ( $hour > 0 || $minute > 0 || $second > 0 ) ) {
262
			throw new IllegalValueException( 'Can not have hour, minute or second with no day' );
263
		}
264
265
		// Warning, never cast the year to integer to not run into 32-bit integer overflows!
266
		$year = ltrim( $year, '0' );
267
		$year = str_pad( $year, 4, '0', STR_PAD_LEFT );
268
269
		return $sign . $year . '-' . $month . '-' . $day . 'T' . $hour . ':' . $minute .':' . $second . 'Z';
270
	}
271
272
	/**
273
	 * @see $timestamp
274
	 *
275
	 * @since 0.1
276
	 *
277
	 * @return string
278
	 */
279
	public function getTime() {
280
		return $this->timestamp;
281
	}
282
283
	/**
284
	 * @see $calendarModel
285
	 *
286
	 * @since 0.1
287
	 *
288
	 * @return string URI
289
	 */
290
	public function getCalendarModel() {
291
		return $this->calendarModel;
292
	}
293
294
	/**
295
	 * @see $before
296
	 *
297
	 * @since 0.1
298
	 *
299
	 * @return int Amount
300
	 */
301
	public function getBefore() {
302
		return $this->before;
303
	}
304
305
	/**
306
	 * @see $after
307
	 *
308
	 * @since 0.1
309
	 *
310
	 * @return int Amount
311
	 */
312
	public function getAfter() {
313
		return $this->after;
314
	}
315
316
	/**
317
	 * @see $precision
318
	 *
319
	 * @since 0.1
320
	 *
321
	 * @return int one of the self::PRECISION_... constants
322
	 */
323
	public function getPrecision() {
324
		return $this->precision;
325
	}
326
327
	/**
328
	 * @see $timezone
329
	 *
330
	 * @since 0.1
331
	 *
332
	 * @return int Minutes
333
	 */
334
	public function getTimezone() {
335
		return $this->timezone;
336
	}
337
338
	/**
339
	 * @see DataValue::getType
340
	 *
341
	 * @since 0.1
342
	 *
343
	 * @return string
344
	 */
345
	public static function getType() {
346
		return 'time';
347
	}
348
349
	/**
350
	 * @see DataValue::getSortKey
351
	 *
352
	 * @since 0.1
353
	 *
354
	 * @return string
355
	 */
356
	public function getSortKey() {
357
		return $this->timestamp;
358
	}
359
360
	/**
361
	 * @see DataValue::getValue
362
	 *
363
	 * @since 0.1
364
	 *
365
	 * @return TimeValue
366
	 */
367
	public function getValue() {
368
		return $this;
369
	}
370
371
	/**
372
	 * @see Serializable::serialize
373
	 *
374
	 * @since 0.1
375
	 *
376
	 * @return string
377
	 */
378
	public function serialize() {
379
		return json_encode( array_values( $this->getArrayValue() ) );
380
	}
381
382
	/**
383
	 * @see Serializable::unserialize
384
	 *
385
	 * @since 0.1
386
	 *
387
	 * @param string $value
388
	 *
389
	 * @throws IllegalValueException
390
	 */
391
	public function unserialize( $value ) {
392
		list( $timestamp, $timezone, $before, $after, $precision, $calendarModel ) = json_decode( $value );
393
		$this->__construct( $timestamp, $timezone, $before, $after, $precision, $calendarModel );
394
	}
395
396
	/**
397
	 * @see DataValue::getArrayValue
398
	 *
399
	 * @since 0.1
400
	 *
401
	 * @return array
402
	 */
403
	public function getArrayValue() {
404
		return array(
405
			'time' => $this->timestamp,
406
			'timezone' => $this->timezone,
407
			'before' => $this->before,
408
			'after' => $this->after,
409
			'precision' => $this->precision,
410
			'calendarmodel' => $this->calendarModel,
411
		);
412
	}
413
414
	/**
415
	 * Constructs a new instance of the DataValue from the provided data.
416
	 * This can round-trip with @see getArrayValue
417
	 *
418
	 * @since 0.1
419
	 *
420
	 * @param mixed $data
421
	 *
422
	 * @return TimeValue
423
	 * @throws IllegalValueException
424
	 */
425
	public static function newFromArray( $data ) {
426
		self::requireArrayFields( $data, array( 'time', 'timezone', 'before', 'after', 'precision', 'calendarmodel' ) );
427
428
		return new static(
429
			$data['time'],
430
			$data['timezone'],
431
			$data['before'],
432
			$data['after'],
433
			$data['precision'],
434
			$data['calendarmodel']
435
		);
436
	}
437
438
	public function __toString() {
439
		return $this->timestamp;
440
	}
441
442
}
443