TimeValue::getType()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 Kreuz
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
	 * @param string $timestamp Timestamp in a format resembling ISO 8601.
180
	 * @param int $timezone Time zone offset from UTC in minutes.
181
	 * @param int $before Number of units given by the precision.
182
	 * @param int $after Number of units given by the precision.
183
	 * @param int $precision One of the self::PRECISION_... constants.
184
	 * @param string $calendarModel An URI identifying the calendar model.
185
	 *
186
	 * @throws IllegalValueException
187
	 */
188 75
	public function __construct( $timestamp, $timezone, $before, $after, $precision, $calendarModel ) {
189 75
		if ( !is_string( $timestamp ) || $timestamp === '' ) {
190 1
			throw new IllegalValueException( '$timestamp must be a non-empty string' );
191
		}
192
193 74
		if ( !is_int( $timezone ) ) {
194 2
			throw new IllegalValueException( '$timezone must be an integer' );
195 72
		} elseif ( $timezone < -12 * 3600 || $timezone > 14 * 3600 ) {
196 1
			throw new IllegalValueException( '$timezone out of allowed bounds' );
197
		}
198
199 71
		if ( !is_int( $before ) || $before < 0 ) {
200 1
			throw new IllegalValueException( '$before must be an unsigned integer' );
201
		}
202
203 70
		if ( !is_int( $after ) || $after < 0 ) {
204 1
			throw new IllegalValueException( '$after must be an unsigned integer' );
205
		}
206
207 69
		if ( !is_int( $precision ) ) {
208
			throw new IllegalValueException( '$precision must be an integer' );
209 69
		} elseif ( $precision < self::PRECISION_YEAR1G || $precision > self::PRECISION_SECOND ) {
210 1
			throw new IllegalValueException( '$precision out of allowed bounds' );
211
		}
212
213
		// XXX: Enforce an IRI? Or at least a size limit?
214 68
		if ( !is_string( $calendarModel ) || $calendarModel === '' ) {
215
			throw new IllegalValueException( '$calendarModel must be a non-empty string' );
216
		}
217
218 68
		$this->timestamp = $this->normalizeIsoTimestamp( $timestamp );
219 55
		$this->timezone = $timezone;
220 55
		$this->before = $before;
221 55
		$this->after = $after;
222 55
		$this->precision = $precision;
223 55
		$this->calendarModel = $calendarModel;
224 55
	}
225
226
	/**
227
	 * @param string $timestamp
228
	 *
229
	 * @throws IllegalValueException
230
	 * @return string
231
	 */
232 68
	private function normalizeIsoTimestamp( $timestamp ) {
233 68
		if ( !preg_match(
234 68
			'/^([-+])(\d{1,16})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/',
235 68
			$timestamp,
236 68
			$matches
237
		) ) {
238 4
			throw new IllegalValueException( '$timestamp must resemble ISO 8601, given ' . $timestamp );
239
		}
240
241 64
		list( , $sign, $year, $month, $day, $hour, $minute, $second ) = $matches;
242
243 64
		if ( $month > 12 ) {
244 1
			throw new IllegalValueException( 'Month out of allowed bounds' );
245 63
		} elseif ( $day > 31 ) {
246 1
			throw new IllegalValueException( 'Day out of allowed bounds' );
247 62
		} elseif ( $hour > 23 ) {
248 1
			throw new IllegalValueException( 'Hour out of allowed bounds' );
249 61
		} elseif ( $minute > 59 ) {
250 1
			throw new IllegalValueException( 'Minute out of allowed bounds' );
251 60
		} elseif ( $second > 61 ) {
252 1
			throw new IllegalValueException( 'Second out of allowed bounds' );
253
		}
254
255 59
		if ( $month < 1 && $day > 0 ) {
256 1
			throw new IllegalValueException( 'Can not have a day with no month' );
257
		}
258
259 58
		if ( $day < 1 && ( $hour > 0 || $minute > 0 || $second > 0 ) ) {
260 3
			throw new IllegalValueException( 'Can not have hour, minute or second with no day' );
261
		}
262
263
		// Warning, never cast the year to integer to not run into 32-bit integer overflows!
264 55
		$year = ltrim( $year, '0' );
265 55
		$year = str_pad( $year, 4, '0', STR_PAD_LEFT );
266
267 55
		return $sign . $year . '-' . $month . '-' . $day . 'T' . $hour . ':' . $minute .':' . $second . 'Z';
268
	}
269
270
	/**
271
	 * @see $timestamp
272
	 *
273
	 * @return string
274
	 */
275 19
	public function getTime() {
276 19
		return $this->timestamp;
277
	}
278
279
	/**
280
	 * @see $calendarModel
281
	 *
282
	 * @return string URI
283
	 */
284 12
	public function getCalendarModel() {
285 12
		return $this->calendarModel;
286
	}
287
288
	/**
289
	 * @see $before
290
	 *
291
	 * @return int Amount
292
	 */
293 12
	public function getBefore() {
294 12
		return $this->before;
295
	}
296
297
	/**
298
	 * @see $after
299
	 *
300
	 * @return int Amount
301
	 */
302 12
	public function getAfter() {
303 12
		return $this->after;
304
	}
305
306
	/**
307
	 * @see $precision
308
	 *
309
	 * @return int one of the self::PRECISION_... constants
310
	 */
311 12
	public function getPrecision() {
312 12
		return $this->precision;
313
	}
314
315
	/**
316
	 * @see $timezone
317
	 *
318
	 * @return int Minutes
319
	 */
320 12
	public function getTimezone() {
321 12
		return $this->timezone;
322
	}
323
324
	/**
325
	 * @see DataValue::getType
326
	 *
327
	 * @return string
328
	 */
329 24
	public static function getType() {
330 24
		return 'time';
331
	}
332
333
	/**
334
	 * @see DataValue::getSortKey
335
	 *
336
	 * @return string
337
	 */
338
	public function getSortKey() {
339
		return $this->timestamp;
340
	}
341
342
	/**
343
	 * @see DataValue::getValue
344
	 *
345
	 * @return self
346
	 */
347 24
	public function getValue() {
348 24
		return $this;
349
	}
350
351
	/**
352
	 * @see Serializable::serialize
353
	 *
354
	 * @return string
355
	 */
356 36
	public function serialize() {
357 36
		return json_encode( array_values( $this->getArrayValue() ) );
358
	}
359
360
	/**
361
	 * @see Serializable::unserialize
362
	 *
363
	 * @param string $value
364
	 *
365
	 * @throws IllegalValueException
366
	 */
367 36
	public function unserialize( $value ) {
368 36
		list( $timestamp, $timezone, $before, $after, $precision, $calendarModel ) = json_decode( $value );
369 36
		$this->__construct( $timestamp, $timezone, $before, $after, $precision, $calendarModel );
370 36
	}
371
372
	/**
373
	 * @see DataValue::getArrayValue
374
	 *
375
	 * @return array
376
	 */
377 60
	public function getArrayValue() {
378
		return array(
379 60
			'time' => $this->timestamp,
380 60
			'timezone' => $this->timezone,
381 60
			'before' => $this->before,
382 60
			'after' => $this->after,
383 60
			'precision' => $this->precision,
384 60
			'calendarmodel' => $this->calendarModel,
385
		);
386
	}
387
388
	/**
389
	 * Constructs a new instance from the provided data. Required for @see DataValueDeserializer.
390
	 * This is expected to round-trip with @see getArrayValue.
391
	 *
392
	 * @deprecated since 0.8.6. Static DataValue::newFromArray constructors like this are
393
	 *  underspecified (not in the DataValue interface), and misleadingly named (should be named
394
	 *  newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.
395
	 *
396
	 * @param mixed $data Warning! Even if this is expected to be a value as returned by
397
	 *  @see getArrayValue, callers of this specific newFromArray implementation can not guarantee
398
	 *  this. This is not even guaranteed to be an array!
399
	 *
400
	 * @throws IllegalValueException if $data is not in the expected format. Subclasses of
401
	 *  InvalidArgumentException are expected and properly handled by @see DataValueDeserializer.
402
	 * @return self
403
	 */
404
	public static function newFromArray( $data ) {
405
		self::requireArrayFields(
406
			$data,
407
			array( 'time', 'timezone', 'before', 'after', 'precision', 'calendarmodel' )
408
		);
409
410
		return new static(
411
			$data['time'],
412
			$data['timezone'],
413
			$data['before'],
414
			$data['after'],
415
			$data['precision'],
416
			$data['calendarmodel']
417
		);
418
	}
419
420
	public function __toString() {
421
		return $this->timestamp;
422
	}
423
424
}
425