Completed
Push — master ( 419e79...27e7c2 )
by mw
32:50
created

SMWDITime::getSerialization()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.0187
Metric Value
dl 0
loc 17
ccs 10
cts 11
cp 0.9091
rs 8.8571
cc 5
eloc 9
nc 16
nop 0
crap 5.0187
1
<?php
2
3
use SMW\DataItemException;
4
5
/**
6
 * This class implements time data items.
7
 * Such data items represent a unique point in time, given in either Julian or
8
 * Gregorian notation (possibly proleptic), and a precision setting that states
9
 * which of the components year, month, day, time were specified expicitly.
10
 * Even when not specified, the data item always assumes default values for the
11
 * missing parts, so the item really captures one point in time, no intervals.
12
 * Times are always assumed to be in UTC.
13
 *
14
 * "Y0K issue": Neither the Gregorian nor the Julian calendar assume a year 0,
15
 * i.e. the year 1 BC(E) was followed by 1 AD/CE. See
16
 * http://en.wikipedia.org/wiki/Year_zero
17
 * This implementation adheres to this convention and disallows year 0. The
18
 * stored year numbers use positive numbers for CE and negative numbers for
19
 * BCE. This is not just relevant for the question of how many years have
20
 * (exactly) passed since a given date, but also for the location of leap
21
 * years.
22
 *
23
 * @since 1.6
24
 *
25
 * @author Markus Krötzsch
26
 * @ingroup SMWDataItems
27
 */
28
class SMWDITime extends SMWDataItem {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
29
30
	const CM_GREGORIAN = 1;
31
	const CM_JULIAN = 2;
32
33
	const PREC_Y    = 0;
34
	const PREC_YM   = 1;
35
	const PREC_YMD  = 2;
36
	const PREC_YMDT = 3;
37
38
	const PREHISTORY = -10000;
39
40
	/**
41
	 * Maximal number of days in a given month.
42
	 * @var array
43
	 */
44
	protected static $m_daysofmonths = array ( 1 => 31, 2 => 29, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 );
45
46
	/**
47
	 * Precision SMWDITime::PREC_Y, SMWDITime::PREC_YM,
48
	 * SMWDITime::PREC_YMD, or SMWDITime::PREC_YMDT.
49
	 * @var integer
50
	 */
51
	protected $m_precision;
52
	/**
53
	 * Calendar model: SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN.
54
	 * @var integer
55
	 */
56
	protected $m_model;
57
	/**
58
	 * Number of year, possibly negative.
59
	 * @var integer
60
	 */
61
	protected $m_year;
62
	/**
63
	 * Number of month.
64
	 * @var integer
65
	 */
66
	protected $m_month;
67
	/**
68
	 * Number of day.
69
	 * @var integer
70
	 */
71
	protected $m_day;
72
	/**
73
	 * Hours of the day.
74
	 * @var integer
75
	 */
76
	protected $m_hours;
77
	/**
78
	 * Minutes of the hour.
79
	 * @var integer
80
	 */
81
	protected $m_minutes;
82
	/**
83
	 * Seconds of the minute.
84
	 * @var integer
85
	 */
86
	protected $m_seconds;
87
88
	/**
89
	 * @var integer
90
	 */
91
	protected $timezone;
92
93
	/**
94
	 * @var integer|null
95
	 */
96
	protected $era = null;
97
98
	/**
99
	 * Create a time data item. All time components other than the year can
100
	 * be false to indicate that they are not specified. This will affect
101
	 * the internal precision setting. The missing values are initialised
102
	 * to minimal values (0 or 1) for internal calculations.
103
	 *
104
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
105
	 * @param $year integer number of the year (possibly negative)
106
	 * @param $month mixed integer number or false
107
	 * @param $day mixed integer number or false
108
	 * @param $hour mixed integer number or false
109
	 * @param $minute mixed integer number or false
110 144
	 * @param $second mixed integer number or false
111
	 * @param integer|false $timezone
112
	 *
113 144
	 * @todo Implement more validation here.
114
	 */
115
	public function __construct( $calendarmodel, $year, $month = false, $day = false,
116
	                             $hour = false, $minute = false, $second = false, $timezone = false ) {
117 144
118
		if ( ( $calendarmodel != self::CM_GREGORIAN ) && ( $calendarmodel != self::CM_JULIAN ) ) {
119
			throw new DataItemException( "Unsupported calendar model constant \"$calendarmodel\"." );
120
		}
121 144
122 144
		if ( $year == 0 ) {
123 144
			throw new DataItemException( "There is no year 0 in Gregorian and Julian calendars." );
124 144
		}
125 144
126 144
		$this->m_model   = $calendarmodel;
127 144
		$this->m_year    = intval( $year );
128
		$this->m_month   = $month != false ? intval( $month ) : 1;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
129 144
		$this->m_day     = $day != false ? intval( $day ) : 1;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
130
		$this->m_hours   = $hour !== false ? intval( $hour ) : 0;
131 144
		$this->m_minutes = $minute !== false ? intval( $minute ) : 0;
132 144
		$this->m_seconds = $second !== false ? floatval( $second ) : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like $second !== false ? floatval($second) : 0 can also be of type double. However, the property $m_seconds is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
133 144
134 144
		$this->timezone = $timezone !== false ? intval( $timezone ) : 0;
135
		$year = strval( $year );
136
		$this->era      = $year{0} === '+' ? 1 : ( $year{0} === '-' ? -1 : 0 );
137
138 144
139
		if ( ( $this->m_hours < 0 ) || ( $this->m_hours > 23 ) ||
140
		     ( $this->m_minutes < 0 ) || ( $this->m_minutes > 59 ) ||
141
		     ( $this->m_seconds < 0 ) || ( $this->m_seconds > 59 ) ||
142 144
		     ( $this->m_month < 1 ) || ( $this->m_month > 12 ) ) {
143 16
			throw new DataItemException( "Part of the date is out of bounds." );
144 144
		}
145 2
146 139
		if ( $this->m_day > self::getDayNumberForMonth( $this->m_month, $this->m_year, $this->m_model ) ) {
147 21
			throw new DataItemException( "Month {$this->m_month} in year {$this->m_year} did not have {$this->m_day} days in this calendar model." );
148 21
		}
149 138
150
		if ( $month === false ) {
151 144
			$this->m_precision = self::PREC_Y;
152
		} elseif ( $day === false ) {
153 146
			$this->m_precision = self::PREC_YM;
154 146
		} elseif ( $hour === false ) {
155
			$this->m_precision = self::PREC_YMD;
156
		} else {
157 20
			$this->m_precision = self::PREC_YMDT;
158 20
		}
159
	}
160
161 43
	public function getDIType() {
162 43
		return SMWDataItem::TYPE_TIME;
163
	}
164
165 43
	public function getCalendarModel() {
166 43
		return $this->m_model;
167
	}
168
169 43
	public function getPrecision() {
170 43
		return $this->m_precision;
171
	}
172
173 43
	/**
174 43
	 * Indicates whether a user explicitly used an era marker even for a positive
175
	 * year.
176
	 *
177 41
	 * - [-1] indicates BC(E)
178 41
	 * - [0]/null indicates no era marker
179
	 * - [1] indicates AD/CE was used
180
	 *
181 41
	 * @since 2.4
182 41
	 *
183
	 * @return integer
184
	 */
185 41
	public function getEra() {
186 41
		return $this->era;
187
	}
188
189
	public function getYear() {
190
		return $this->m_year;
191
	}
192
193
	public function getMonth() {
194 1
		return $this->m_month;
195
	}
196
197 1
	public function getDay() {
198 1
		return $this->m_day;
199 1
	}
200
201 1
	public function getHour() {
202
		return $this->m_hours;
203
	}
204
205
	public function getMinute() {
206
		return $this->m_minutes;
207
	}
208
209
	public function getSecond() {
210
		return $this->m_seconds;
211
	}
212
213
	/**
214
	 * @since 2.4
215
	 *
216
	 * @return string
217
	 */
218
	public function getCalendarModelLiteral() {
219
220
		$literal = array(
221
			self::CM_GREGORIAN => '',
222
			self::CM_JULIAN    => 'JL'
223
		);
224
225
		return $literal[$this->m_model];
226
	}
227
228
	/**
229
	 * @since 2.4
230
	 *
231
	 * @param DateTime $dateTime
232
	 *
233 1
	 * @return SMWDITime|false
234
	 */
235 1
	public static function newFromDateTime( DateTime $dateTime ) {
236
237
		$calendarModel = self::CM_JULIAN;
238
239 1
		$year = $dateTime->format( 'Y' );
240
		$month = $dateTime->format( 'm' );
241
		$day = $dateTime->format( 'd' );
242
243
		if ( ( $year > 1582 ) ||
244
			( ( $year == 1582 ) && ( $month > 10 ) ) ||
245 1
			( ( $year == 1582 ) && ( $month == 10 ) && ( $day > 4 ) ) ) {
246
			$calendarModel = self::CM_GREGORIAN;
247 1
		}
248 1
249 1
		return self::doUnserialize( $calendarModel . '/' . $dateTime->format( 'Y/m/d/H/i/s.u' ) );
250 1
	}
251 1
252 1
	/**
253
	 * @since 2.4
254 1
	 *
255
	 * @return DateTime
256
	 */
257
	public function asDateTime() {
258
259
		$year = str_pad( $this->m_year , 4, '0', STR_PAD_LEFT );
260
261
		// Avoid "Failed to parse time string (-900-02-02 00:00:00) at
262
		// position 7 (-): Double timezone specification"
263
		if ( $this->m_year < 0 ) {
264
			$year = '-' . str_pad( $this->m_year * -1 , 4, '0', STR_PAD_LEFT );
265
		}
266 136
267 136
		// Avoid "Failed to parse time string (1300-11-02 12:03:25.888499949) at
268
		// at position 11 (1): The timezone could not ..."
269 136
		$seconds = number_format( str_pad( $this->m_seconds, 2, '0', STR_PAD_LEFT ), 7, '.', '' );
270
271
		$time = $year . '-' .
272
			str_pad( $this->m_month, 2, '0', STR_PAD_LEFT )     . '-' .
273 136
			str_pad( $this->m_day, 2, '0', STR_PAD_LEFT )       . ' ' .
274 136
			str_pad( $this->m_hours, 2, '0', STR_PAD_LEFT )     . ':' .
275 136
			str_pad( $this->m_minutes, 2, '0', STR_PAD_LEFT )   . ':' .
276 136
			$seconds;
277 136
278 136
		return new DateTime( $time );
279 136
	}
280 136
281 136
	/**
282
	 * Creates and returns a new instance of SMWDITime from a MW timestamp.
283
	 *
284
	 * @since 1.8
285
	 *
286
	 * @param string $timestamp must be in format
287
	 *
288
	 * @return SMWDITime|false
289
	 */
290
	public static function newFromTimestamp( $timestamp ) {
291
		$timestamp = wfTimestamp( TS_MW, (string)$timestamp );
292
293 135
		if ( $timestamp === false ) {
294 135
			return false;
295 135
		}
296 135
297 135
		return new self(
298 135
			self::CM_GREGORIAN,
299 135
			substr( $timestamp, 0, 4 ),
300 135
			substr( $timestamp, 4, 2 ),
0 ignored issues
show
Documentation introduced by
substr($timestamp, 4, 2) is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
301 135
			substr( $timestamp, 6, 2 ),
0 ignored issues
show
Documentation introduced by
substr($timestamp, 6, 2) is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
302 135
			substr( $timestamp, 8, 2 ),
0 ignored issues
show
Documentation introduced by
substr($timestamp, 8, 2) is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
303 135
			substr( $timestamp, 10, 2 ),
0 ignored issues
show
Documentation introduced by
substr($timestamp, 10, 2) is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
304 135
			substr( $timestamp, 12, 2 )
0 ignored issues
show
Documentation introduced by
substr($timestamp, 12, 2) is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
305
		);
306
	}
307
308
	/**
309
	 * Returns a MW timestamp representation of the value.
310
	 *
311
	 * @since 1.6.2
312
	 *
313
	 * @param $outputtype
314
	 *
315
	 * @return mixed
316
	 */
317 41
	public function getMwTimestamp( $outputtype = TS_UNIX ) {
318 41
		return wfTimestamp(
319 41
			$outputtype,
320
			implode( '', array(
321 3
				str_pad( $this->m_year, 4, '0', STR_PAD_LEFT ),
322
				str_pad( $this->m_month, 2, '0', STR_PAD_LEFT ),
323
				str_pad( $this->m_day, 2, '0', STR_PAD_LEFT ),
324
				str_pad( $this->m_hours, 2, '0', STR_PAD_LEFT ),
325
				str_pad( $this->m_minutes, 2, '0', STR_PAD_LEFT ),
326
				str_pad( $this->m_seconds, 2, '0', STR_PAD_LEFT ),
327
			) )
328
		);
329
	}
330
331
	/**
332
	 * Get the data in the specified calendar model. This might require
333
	 * conversion.
334 142
	 * @note Conversion can be unreliable for very large absolute year
335 142
	 * numbers when the internal calculations hit floating point accuracy.
336 142
	 * Callers might want to avoid this (calendar models make little sense
337 142
	 * in such cases anyway).
338
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
339 3
	 * @return SMWDITime
340
	 */
341
	public function getForCalendarModel( $calendarmodel ) {
342
		if ( $calendarmodel == $this->m_model ) {
343 145
			return $this;
344 145
		} else {
345 145
			return self::newFromJD( $this->getJD(), $calendarmodel, $this->m_precision );
346
		}
347
	}
348 144
349 144
	/**
350
	 * Return a number that helps comparing time data items. For
351 144
	 * dates in the Julian Day era (roughly from 4713 BCE onwards), we use
352 139
	 * the Julian Day number. For earlier dates, the (negative) year number
353 139
	 * with a fraction for the date is used (times are ignored). This
354
	 * avoids calculation errors that would occur for very ancient dates
355 144
	 * if the JD number was used there.
356 139
	 * @return double sortkey
357 139
	 */
358
	public function getSortKey() {
359 144
		$jd = ( $this->m_year >= -4713 ) ? $jd = $this->getJD() : -1;
360 138
		if ( $jd > 0 ) {
361 138
			return $jd;
362
		} else {
363 144
			return $this->m_year - 1 + ( $this->m_month - 1 ) / 12 + ( $this->m_day - 1 ) / 12 / 31;
364
		}
365
	}
366
367
	public function getJD() {
368
		return self::date2JD( $this->m_year, $this->m_month, $this->m_day, $this->m_model ) +
369
		       self::time2JDoffset( $this->m_hours, $this->m_minutes, $this->m_seconds );
370
	}
371 16
372 16
	public function getSerialization() {
373 16
		$result = strval( $this->m_model ) . '/' . ( $this->era > 0 ? '+' : '' ) . strval( $this->m_year );
374
375 16
		if ( $this->m_precision >= self::PREC_YM ) {
376 16
			$result .= '/' . strval( $this->m_month );
377
		}
378 16
379
		if ( $this->m_precision >= self::PREC_YMD ) {
380
			$result .= '/' . strval( $this->m_day );
381
		}
382 16
383 16
		if ( $this->m_precision >= self::PREC_YMDT ) {
384 5
			$result .= '/' . strval( $this->m_hours ) . '/' . strval( $this->m_minutes ) . '/' . strval( $this->m_seconds ) . '/' . strval( $this->timezone );
385
		}
386 16
387
		return $result;
388 16
	}
389
390
	/**
391
	 * Create a data item from the provided serialization string.
392 16
	 *
393
	 * @return SMWDITime
394
	 */
395
	public static function doUnserialize( $serialization ) {
396
		$parts = explode( '/', $serialization, 8 );
397
		$values = array();
398
399
		for ( $i = 0; $i < 8; $i += 1 ) {
400
			if ( $i < count( $parts ) ) {
401
402
				if ( !is_numeric( $parts[$i] ) ) {
403 6
					throw new DataItemException( "Unserialization failed: the string \"$serialization\" is no valid datetime specification." );
404 6
				}
405 6
406
				$values[$i] = $i == 6 ? floatval( $parts[$i] ) : intval( $parts[$i] );
407
408
				// Find out whether the input contained an explicit AD/CE era marker
409
				if ( $i == 1 ) {
410
					$values[$i] = ( $parts[1]{0} === '+' ? '+' : '' ) . $values[$i];
411 6
				}
412 6
			} else {
413 6
				$values[$i] = false;
414 2
			}
415
		}
416 6
417
		if ( count( $parts ) <= 1 ) {
418
			throw new DataItemException( "Unserialization failed: the string \"$serialization\" is no valid URI." );
419
		}
420
421
		return new self( $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6], $values[7] );
422
	}
423
424
	/**
425
	 * Create a new time data item from the specified Julian Day number,
426
	 * calendar model, presicion, and type ID.
427
	 * @param $jdvalue double Julian Day number
428
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
429 145
	 * @param $precision integer one of SMWDITime::PREC_Y, SMWDITime::PREC_YM, SMWDITime::PREC_YMD, SMWDITime::PREC_YMDT
430 145
	 * @return SMWDITime object
431 145
	 */
432 145
	public static function newFromJD( $jdvalue, $calendarmodel, $precision ) {
433 145
		list( $year, $month, $day ) = self::JD2Date( $jdvalue, $calendarmodel );
434 145
		if ( $precision <= self::PREC_YM ) {
435 145
			$day = false;
436
			if ( $precision == self::PREC_Y ) {
437 6
				$month = false;
438 6
			}
439 6
		}
440
		if ( $precision == self::PREC_YMDT ) {
441
			list( $hour, $minute, $second ) = self::JD2Time( $jdvalue );
442
		} else {
443
			$hour = $minute = $second = false;
444
		}
445
		return new SMWDITime( $calendarmodel, $year, $month, $day, $hour, $minute, $second );
0 ignored issues
show
Bug introduced by
It seems like $month defined by self::JD2Date($jdvalue, $calendarmodel) on line 433 can also be of type double or integer; however, SMWDITime::__construct() does only seem to accept boolean, 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 $day defined by self::JD2Date($jdvalue, $calendarmodel) on line 433 can also be of type double; however, SMWDITime::__construct() does only seem to accept boolean, 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 $hour defined by self::JD2Time($jdvalue) on line 441 can also be of type double; however, SMWDITime::__construct() does only seem to accept boolean, 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 $minute defined by self::JD2Time($jdvalue) on line 441 can also be of type double; however, SMWDITime::__construct() does only seem to accept boolean, 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 $second defined by self::JD2Time($jdvalue) on line 441 can also be of type double; however, SMWDITime::__construct() does only seem to accept boolean, 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...
446
	}
447
448
	/**
449
	 * Compute the Julian Day number from a given date in the specified
450
	 * calendar model. This calculation assumes that neither calendar
451 145
	 * has a year 0.
452 145
	 * @param $year integer representing the year
453
	 * @param $month integer representing the month
454
	 * @param $day integer representing the day
455
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
456
	 * @return float Julian Day number
457
	 */
458
	static public function date2JD( $year, $month, $day, $calendarmodel ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
459
		$astroyear = ( $year < 1 ) ? ( $year + 1 ) : $year;
460
		if ( $calendarmodel == self::CM_GREGORIAN ) {
461
			$a = intval( ( 14 - $month ) / 12 );
462
			$y = $astroyear + 4800 - $a;
463
			$m = $month + 12 * $a - 3;
464
			return $day + floor( ( 153 * $m + 2 ) / 5 ) + 365 * $y + floor( $y / 4 ) - floor( $y / 100 ) + floor( $y / 400 ) - 32045.5;
465 6
		} else {
466 6
			$y2 = ( $month <= 2 ) ? ( $astroyear - 1 ) : $astroyear;
467 6
			$m2 = ( $month <= 2 ) ? ( $month + 12 ) : $month;
468 6
			return floor( ( 365.25 * ( $y2 + 4716 ) ) ) + floor( ( 30.6001 * ( $m2 + 1 ) ) ) + $day - 1524.5;
469 6
		}
470 6
	}
471 6
472 6
	/**
473 6
	 * Compute the offset for the Julian Day number from a given time.
474 6
	 * This computation is the same for all calendar models.
475 6
	 * @param $hours integer representing the hour
476 6
	 * @param $minutes integer representing the minutes
477 6
	 * @param $seconds integer representing the seconds
478 6
	 * @return float offset for a Julian Day number to get this time
479 6
	 */
480
	static public function time2JDoffset( $hours, $minutes, $seconds ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
481 6
		return ( $hours / 24 ) + ( $minutes / ( 60 * 24 ) ) + ( $seconds / ( 3600 * 24 ) );
482 6
	}
483 6
484 6
	/**
485 2
	 * Convert a Julian Day number to a date in the given calendar model.
486 2
	 * This calculation assumes that neither calendar has a year 0.
487 2
	 * @note The algorithm may fail for some cases, in particular since the
488 2
	 * conversion to Gregorian needs positive JD. If this happens, wrong
489
	 * values will be returned. Avoid date conversions before 10000 BCE.
490 2
	 * @param $jdvalue float number of Julian Days
491 2
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
492 2
	 * @return array( yearnumber, monthnumber, daynumber )
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
493
	 */
494 6
	static public function JD2Date( $jdvalue, $calendarmodel ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
495 6
		if ( $calendarmodel == self::CM_GREGORIAN ) {
496
			$jdvalue += 2921940; // add the days of 8000 years (this algorithm only works for positive JD)
497
			$j = floor( $jdvalue + 0.5 ) + 32044;
498
			$g = floor( $j / 146097 );
499
			$dg = $j % 146097;
500
			$c = floor( ( ( floor( $dg / 36524 ) + 1 ) * 3 ) / 4 );
501
			$dc = $dg - $c * 36524;
502
			$b = floor( $dc / 1461 );
503
			$db = $dc % 1461;
504 6
			$a = floor( ( ( floor( $db / 365 ) + 1 ) * 3 ) / 4 );
505 6
			$da = $db - ( $a * 365 );
506 6
			$y = $g * 400 + $c * 100 + $b * 4 + $a;
507 6
			$m = floor( ( $da * 5 + 308 ) / 153 ) - 2;
508 6
			$d = $da - floor( ( ( $m + 4 ) * 153 ) / 5 ) + 122;
509 6
510 6
			$year  = $y - 4800 + floor( ( $m + 2 ) / 12 ) - 8000;
511 6
			$month = ( ( $m + 2 ) % 12 + 1 );
512 6
			$day   = $d + 1;
513
		} else {
514
			$b = floor( $jdvalue + 0.5 ) + 1524;
515
			$c = floor( ( $b - 122.1 ) / 365.25 );
516
			$d = floor( 365.25 * $c );
517
			$e = floor( ( $b - $d ) / 30.6001 );
518
519
			$month = floor( ( $e < 14 ) ? ( $e - 1 ) : ( $e - 13 ) );
520
			$year = floor( ( $month > 2 ) ? ( $c - 4716 ) : ( $c - 4715 ) );
521
			$day   = ( $b - $d - floor( 30.6001 * $e ) );
522 137
		}
523 137
		$year  = ( $year < 1 ) ? ( $year - 1 ) : $year; // correct "year 0" to -1 (= 1 BC(E))
524 137
		return array( $year, $month, $day );
525 3
	}
526
527 137
	/**
528 137
	 * Extract the time from a Julian Day number and return it as a string.
529
	 * This conversion is the same for all calendar models.
530
	 * @param $jdvalue float number of Julian Days
531
	 * @return array( hours, minutes, seconds )
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
532
	 */
533
	static public function JD2Time( $jdvalue ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
534
		$wjd = $jdvalue + 0.5;
535
		$fraction = $wjd - floor( $wjd );
536
		$time = round( $fraction * 3600 * 24 );
537
		$hours = floor( $time / 3600 );
538
		$time = $time - $hours * 3600;
539
		$minutes = floor( $time / 60 );
540
		$seconds = floor( $time - $minutes * 60 );
541 144
		return array( $hours, $minutes, $seconds );
542 144
	}
543 34
544 137
	/**
545 135
	 * Find out whether the given year number is a leap year.
546
	 * This calculation assumes that neither calendar has a year 0.
547 3
	 * @param $year integer year number
548
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
549
	 * @return boolean
550
	 */
551
	static public function isLeapYear( $year, $calendarmodel ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
552
		$astroyear = ( $year < 1 ) ? ( $year + 1 ) : $year;
553
		if ( $calendarmodel == self::CM_JULIAN ) {
554
			return ( $astroyear % 4 ) == 0;
555
		} else {
556
			return ( ( $astroyear % 400 ) == 0 ) ||
557
			       ( ( ( $astroyear % 4 ) == 0 ) && ( ( $astroyear % 100 ) != 0 ) );
558
		}
559
	}
560
561
	/**
562
	 * Find out how many days the given month had in the given year
563
	 * based on the specified calendar model.
564
	 * This calculation assumes that neither calendar has a year 0.
565
	 * @param $month integer month number
566
	 * @param $year integer year number
567
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
568
	 * @return boolean
569
	 */
570
	static public function getDayNumberForMonth( $month, $year, $calendarmodel ) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
571
		if ( $month !== 2 ) {
572
			return self::$m_daysofmonths[$month];
573
		} elseif ( self::isLeapYear( $year, $calendarmodel ) ) {
574
			return 29;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 29; (integer) is incompatible with the return type documented by SMWDITime::getDayNumberForMonth of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
575
		} else {
576
			return 28;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 28; (integer) is incompatible with the return type documented by SMWDITime::getDayNumberForMonth of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
577
		}
578
	}
579
580
	public function equals( SMWDataItem $di ) {
581
		if ( $di->getDIType() !== SMWDataItem::TYPE_TIME ) {
582
			return false;
583
		}
584
585
		return $di->getSortKey() === $this->getSortKey();
586
	}
587
}
588