SMWTimeValue::parseDateString()   F
last analyzed

Complexity

Conditions 46
Paths 210

Size

Total Lines 113
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 62.2269

Importance

Changes 0
Metric Value
cc 46
eloc 75
nc 210
nop 10
dl 0
loc 113
ccs 57
cts 71
cp 0.8028
crap 62.2269
rs 3.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
use SMW\DataValues\ValueFormatters\DataValueFormatter;
4
use SMW\DataValues\Time\Timezone;
5
use SMW\Localizer;
6
7
/**
8
 * @ingroup SMWDataValues
9
 */
10
11
/**
12
 * This datavalue captures values of dates and times, in many formats,
13
 * throughout history and pre-history. The implementation can handle dates
14
 * across history with full precision for storing, and substantial precision
15
 * for sorting and querying. The range of supported past dates should encompass
16
 * the Beginning of Time according to most of today's theories. The range of
17
 * supported future dates is limited more strictly, but it does also allow
18
 * year numbers in the order of 10^9.
19
 *
20
 * The implementation notices and stores whether parts of a date/time have been
21
 * omitted (as in "2008" or "May 2007"). For all exporting and sorting
22
 * purposes, incomplete dates are completed with defaults (usually using the
23
 * earliest possible time, i.e. interpreting "2008" as "Jan 1 2008 00:00:00").
24
 * The information on what was unspecified is kept internally for improving
25
 * behavior e.g. for outputs (defaults are not printed when querying for a
26
 * value). This largely uses the precision handling of SMWDITime.
27
 *
28
 *
29
 * Date formats
30
 *
31
 * Dates can be given in many formats, using numbers, month names, and
32
 * abbreviated month names. The preferred interpretation of ambiguous dates
33
 * ("1 2 2008" or even "1 2 3 BC") is controlled by the language file, as is
34
 * the local naming of months. English month names are always supported.
35
 *
36
 * Dates can be given in Gregorian or Julian calendar, set by the token "Jl"
37
 * or "Gr" in the input. If neither is set, a default is chosen: inputs after
38
 * October 15, 1582 (the time when the Gregorian calendar was first inaugurated
39
 * in some parts of the world) are considered Gr, earlier inputs are considered
40
 * Jl. In addition to Jl and Gr, we support "OS" (Old Style English dates that
41
 * refer to the use of Julian calendar with a displaced change of year on March
42
 * 24), JD (direct numerical input in Julian Day notation), and MJD (direct
43
 * numerical input in Modified Julian Day notation as used in aviation and
44
 * space flight).
45
 *
46
 * The class does not support the input of negative year numbers but uses the
47
 * markers "BC"/"BCE" and "AD"/"CE" instead. There is no year 0 in Gregorian or
48
 * Julian calendars, but the class graciously considers this input to mean year
49
 * 1 BC(E).
50
 *
51
 * For prehisoric dates before 9999 BC(E) only year numbers are allowed
52
 * (nothing else makes much sense). At this time, the years of Julian and
53
 * Gregorian calendar still overlap significantly, so the transition to a
54
 * purely solar annotation of prehistoric years is smooth. Technically, the
55
 * class will consider prehistoric dates as Gregorian but very ancient times
56
 * may be interpreted as desired (probably with reference to a physical notion
57
 * of time that is not dependent on revolutions of earth around the sun).
58
 *
59
 *
60
 * Time formats
61
 *
62
 * Times can be in formats like "23:12:45" and "12:30" possibly with additional
63
 * modifiers "am" or "pm". Timezones are supported: the class knows many
64
 * international timezone monikers (e.g. CET or GMT) and also allows time
65
 * offsets directly after a time (e.g. "10:30-3:30" or "14:45:23+2"). Such
66
 * offsets always refer to UTC. Timezones are only used on input and are not
67
 * stored as part of the value.
68
 *
69
 * Time offsets take leap years into account, e.g. the date
70
 * "Feb 28 2004 23:00+2:00" is equivalent to "29 February 2004 01:00:00", while
71
 * "Feb 28 1900 23:00+2:00" is equivalent to "1 March 1900 01:00:00".
72
 *
73
 * Military time format is supported. This consists of 4 or 6 numeric digits
74
 * followed by a one-letter timezone code (e.g. 1240Z is equivalent to 12:40
75
 * UTC).
76
 *
77
 *
78
 * I18N
79
 *
80
 * Currently, neither keywords like "BCE", "Jl", or "pm", nor timezone monikers
81
 * are internationalized. Timezone monikers may not require this, other than
82
 * possibly for Cyrillic (added when needed). Month names are fully
83
 * internationalized, but English names and abbreviations will also work in all
84
 * languages. The class also supports ordinal day-of-month annotations like
85
 * "st" and "rd", again only for English.
86
 *
87
 * I18N includes the preferred order of dates, e.g. to interpret "5 6 2010".
88
 *
89
 * @todo Theparsing process can encounter many kinds of well-defined problems
90
 * but uses only one error message. More detailed reporting should be done.
91
 * @todo Try to reuse more of MediaWiki's records, e.g. to obtain month names
92
 * or to format dates. The problem is that MW is based on SIO timestamps that
93
 * don't extend to very ancient or future dates, and that MW uses PHP functions
94
 * that are bound to UNIX time.
95
 *
96
 * @author Markus Krötzsch
97
 * @author Fabian Howahl
98
 * @author Terry A. Hurlbut
99
 * @ingroup SMWDataValues
100
 */
101
class SMWTimeValue extends SMWDataValue {
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...
102
103
	/**
104
	 * DV identifier
105
	 */
106
	const TYPE_ID = '_dat';
107
108
	protected $m_dataitem_greg = null;
109
	protected $m_dataitem_jul = null;
110
111
	protected $m_wikivalue; // a suitable wiki input value
112
113
	// The following are constant (array-valued constants are not supported, hence the declaration as private static variable):
114
	protected static $m_months = array( 'January', 'February', 'March', 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' );
115
	protected static $m_monthsshort = array( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' );
116
	protected static $m_formats = array( SMW_Y => array( 'y' ), SMW_YM => array( 'y', 'm' ), SMW_MY => array( 'm', 'y' ), SMW_YDM => array( 'y', 'd', 'm' ), SMW_YMD => array( 'y', 'm', 'd' ), SMW_DMY => array( 'd', 'm', 'y' ), SMW_MDY => array( 'm', 'd', 'y' ) );
117
118
	/// Moment of switchover to Gregorian calendar.
119 44
	const J1582 = 2299160.5;
120
	/// Offset of Julian Days for Modified JD inputs.
121 44
	const MJD_EPOCH = 2400000.5;
122 44
	/// The year before which we do not accept anything but year numbers and largely discourage calendar models.
123
	const PREHISTORY = -10000;
124 44
125 44
	protected function parseUserValue( $value ) {
126
127 44
		$value = Localizer::convertDoubleWidth( $value );
128
		$this->m_wikivalue = $value;
129 44
130 44
		if ( $this->m_caption === false ) { // Store the caption now.
131 44
			$this->m_caption = $value;
132
		}
133 15
		$this->m_dataitem = null;
134 1
135 15
		$datecomponents = array();
136
		$calendarmodel = $era = $hours = $minutes = $seconds = $microseconds = $timeoffset = $timezone = false;
137 37
		if ( $this->isInterpretableAsYearOnly( $value ) ) {
138 3
			try {
139 37
				$this->m_dataitem = new SMWDITime( $this->getCalendarModel( null, $value, null, null ), $value );
140 37
			} catch ( SMWDataItemException $e ) {
141
				$this->addErrorMsg( array( 'smw-datavalue-time-invalid', $value, $e->getMessage() ) );
142
			}
143
		} elseif ( $this->isInterpretableAsTimestamp( $value ) ) {
144 37
			$this->m_dataitem = SMWDITime::newFromTimestamp( $value );
0 ignored issues
show
Documentation Bug introduced by
It seems like \SMWDITime::newFromTimestamp($value) can also be of type false. However, the property $m_dataitem is declared as type object<SMWDataItem>. 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...
145 1
		} elseif ( $this->parseDateString( $value, $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $microseconds, $timeoffset, $timezone ) ) {
146
			if ( ( $calendarmodel === false ) && ( $era === false ) && ( count( $datecomponents ) == 1 ) && ( intval( end( $datecomponents ) ) >= 100000 ) ) {
147 1
				$calendarmodel = 'JD'; // default to JD input if a single number was given as the date
148 1
			}
149 1
150
			if ( ( $calendarmodel == 'JD' ) || ( $calendarmodel == 'MJD' ) ) {
151 1
				if ( ( $era === false ) && ( $hours === false ) && ( $timeoffset == 0 ) ) {
152
					try {
153 1
						$jd = floatval( isset( $datecomponents[1] ) ? $datecomponents[0] . '.' . $datecomponents[1] : $datecomponents[0] );
154
						if ( $calendarmodel == 'MJD' ) {
155
							$jd += self::MJD_EPOCH;
156 1
						}
157
						$this->m_dataitem = SMWDITime::newFromJD( $jd, SMWDITime::CM_GREGORIAN, SMWDITime::PREC_YMDT, $timezone );
158
					} catch ( SMWDataItemException $e ) {
159 37
						$this->addErrorMsg( array( 'smw-datavalue-time-invalid-jd', $this->m_wikivalue, $e->getMessage() ) );
160
					}
161
				} else {
162
					$this->addErrorMsg( array( 'smw-datavalue-time-invalid-jd', $this->m_wikivalue, "NO_EXCEPTION" ) );
163 44
				}
164 6
			} else {
165
				$this->setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $microseconds, $timeoffset, $timezone );
166 44
			}
167
		}
168
169
		if ( is_null( $this->m_dataitem ) ) { // make sure that m_dataitem is set in any case
170
			$this->m_dataitem = new SMWDITime( SMWDITime::CM_GREGORIAN, 32202 );
171
		}
172
	}
173
174
	/**
175
	 * Parse the given string to check if it a date/time value.
176
	 * The function sets the provided call-by-ref values to the respective
177
	 * values. If errors are encountered, they are added to the objects
178
	 * error list and false is returned. Otherwise, true is returned.
179
	 * @param $string string input time representation, e.g. "12 May 2007 13:45:23-3:30"
180
	 * @param $datecomponents array of strings that might belong to the specification of a date
181
	 * @param $calendarmodesl string if model was set in input, otherwise false
182
	 * @param $era string '+' or '-' if provided, otherwise false
183
	 * @param $hours integer set to a value between 0 and 24
184 37
	 * @param $minutes integer set to a value between 0 and 59
185
	 * @param $seconds integer set to a value between 0 and 59, or false if not given
186 37
	 * @param $timeoffset double set to a value for time offset (e.g. 3.5), or false if not given
187 37
	 * @return boolean stating if the parsing succeeded
188
	 * @todo This method in principle allows date parsing to be internationalized further. Should be done.
189
	 */
190 37
	protected function parseDateString( $string, &$datecomponents, &$calendarmodel, &$era, &$hours, &$minutes, &$seconds, &$microseconds, &$timeoffset, &$timezone ) {
191
192 37
		$calendarmodel = $timezoneoffset = $era = $ampm = false;
193 3
		$hours = $minutes = $seconds = $microseconds = $timeoffset = $timezone = false;
194 3
195 3
		// Fetch possible "America/Argentina/Mendoza"
196
		$timzoneIdentifier = substr( $string, strrpos( $string, ' ' ) + 1 );
197
198
		if ( Timezone::isValid( $timzoneIdentifier ) ) {
199
			$string = str_replace( $timzoneIdentifier, '', $string );
200
			$timezoneoffset = Timezone::getOffsetByAbbreviation( $timzoneIdentifier ) / 3600;
201
			$timezone = Timezone::getIdByAbbreviation( $timzoneIdentifier );
202 37
		}
203
204 37
		// crude preprocessing for supporting different date separation characters;
205 37
		// * this does not allow localized time notations such as "10.34 pm"
206 37
		// * this creates problems with keywords that contain "." such as "p.m."
207 37
		// * yet "." is an essential date separation character in languages such as German
208 37
		$parsevalue = str_replace( array( '/', '.', '&nbsp;', ',', '年', '月', '日', '時', '分' ), array( '-', ' ', ' ', ' ', ' ', ' ', ' ', ':', ' ' ), $string );
209
210 37
		$matches = preg_split( "/([T]?[0-2]?[0-9]:[\:0-9]+[+\-]?[0-2]?[0-9\:]+|[\p{L}]+|[0-9]+|[ ])/u", $parsevalue, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
211 37
		$datecomponents = array();
212 37
		$unclearparts = array();
213 37
		$matchisnumber = false; // used for looking back; numbers are days/months/years by default but may be re-interpreted if certain further symbols are found
214
		$matchisdate = false; // used for ensuring that date parts are in one block
215 37
216 33
		foreach ( $matches as $match ) {
217 37
			$prevmatchwasnumber = $matchisnumber;
218 9
			$prevmatchwasdate   = $matchisdate;
219 9
			$matchisnumber = $matchisdate = false;
220 37
221 37
			if ( $match == ' ' ) {
222 36
				$matchisdate = $prevmatchwasdate; // spaces in dates do not end the date
223 36
			} elseif ( $match == '-' ) { // can only occur separately between date components
224 36
				$datecomponents[] = $match; // we check later if this makes sense
225 33
				$matchisdate = true;
226 2
			} elseif ( is_numeric( $match ) &&
227 33
			           ( $prevmatchwasdate || count( $datecomponents ) == 0 ) ) {
228 4
				$datecomponents[] = $match;
229 33
				$matchisnumber = true;
230 3
				$matchisdate = true;
231 33
			} elseif ( $era === false && in_array( $match, array( 'AD', 'CE' ) ) ) {
232 8
				$era = '+';
233 33
			} elseif ( $era === false && in_array( $match, array( 'BC', 'BCE' ) ) ) {
234
				$era = '-';
235 33
			} elseif ( $calendarmodel === false && in_array( $match, array( 'Gr', 'GR' , 'He', 'Jl', 'JL', 'MJD', 'JD', 'OS' ) ) ) {
236
				$calendarmodel = $match;
237
			} elseif ( $ampm === false && ( strtolower( $match ) === 'am' || strtolower( $match ) === 'pm' ) ) {
238
				$ampm = strtolower( $match );
239 33
			} elseif ( $hours === false && self::parseTimeString( $match, $hours, $minutes, $seconds, $timeoffset ) ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
240 33
				// nothing to do
241 33
			} elseif ( $hours !== false && $timezoneoffset === false && Timezone::isValid( $match ) ) {
242
				// only accept timezone if time has already been set
243
				$timezoneoffset = Timezone::getOffsetByAbbreviation( $match ) / 3600;
244
				$timezone = Timezone::getIdByAbbreviation( $match );
245
			} elseif ( $prevmatchwasnumber && $hours === false && $timezoneoffset === false &&
246 33
					Timezone::isMilitary( $match ) &&
247 33
					self::parseMilTimeString( end( $datecomponents ), $hours, $minutes, $seconds ) ) {
248 32
					// military timezone notation is found after a number -> re-interpret the number as military time
249 32
					array_pop( $datecomponents );
250 4
					$timezoneoffset = Timezone::getOffsetByAbbreviation( $match ) / 3600;
251
					$timezone = Timezone::getIdByAbbreviation( $match );
252
			} elseif ( ( $prevmatchwasdate || count( $datecomponents ) == 0 ) &&
253 4
				   $this->parseMonthString( $match, $monthname ) ) {
254 4
				$datecomponents[] = $monthname;
255
				$matchisdate = true;
256 37
			} elseif ( $prevmatchwasnumber && $prevmatchwasdate && in_array( $match, array( 'st', 'nd', 'rd', 'th' ) ) ) {
257
				$datecomponents[] = 'd' . strval( array_pop( $datecomponents ) ); // must be a day; add standard marker
258
				$matchisdate = true;
259
			} elseif ( count( $match ) == 1 ) {
260
				$microseconds = $match;
261
			} else {
262
				$unclearparts[] = $match;
263
			}
264
		}
265
266
267
		// Useful for debugging:
268 37
		// 		print "\n\n Results \n\n";
269
		// 		debug_zval_dump( $datecomponents );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270
		// 		print "\ncalendarmodel: $calendarmodel   \ntimezoneoffset: $timezoneoffset  \nera: $era  \nampm: $ampm  \nh: $hours  \nm: $minutes  \ns:$seconds  \ntimeoffset: $timeoffset  \n";
271
		// 		debug_zval_dump( $unclearparts );
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
272
273 37
		// Abort if we found unclear or over-specific information:
274
		if ( count( $unclearparts ) != 0 ) {
275
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-values', $this->m_wikivalue, implode( ', ', $unclearparts ) ) );
276
			return false;
277
		}
278 37
279
		if ( ( $timezoneoffset !== false && $timeoffset !== false ) ) {
280
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-offset-zone-usage', $this->m_wikivalue ) );
281
			return false;
282
		}
283 37
284
		if ( ( $timezoneoffset !== false && $timeoffset !== false ) ) {
285
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-offset-zone-usage', $this->m_wikivalue ) );
286 37
			return false;
287 1
		}
288 1
289 37
		$timeoffset = $timeoffset + $timezoneoffset;
290
		// Check if the a.m. and p.m. information is meaningful
291 37
292 8
		if ( $ampm !== false && ( $hours > 12 || $hours == 0 ) ) { // Note: the == 0 check subsumes $hours===false
293
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-ampm', $this->m_wikivalue, $hours ) );
294
			return false;
295 37
		} elseif ( $ampm == 'am' && $hours == 12 ) {
296
			$hours = 0;
297
		} elseif ( $ampm == 'pm' && $hours < 12 ) {
298
			$hours += 12;
299
		}
300
301
		return true;
302
	}
303
304
	/**
305
	 * Parse the given string to check if it encodes an international time.
306
	 * If successful, the function sets the provided call-by-ref values to
307
	 * the respective numbers and returns true. Otherwise, it returns
308
	 * false and does not set any values.
309
	 * @note This method is only temporarily public for enabling SMWCompatibilityHelpers. Do not use it directly in your code.
310
	 *
311
	 * @param $string string input time representation, e.g. "13:45:23-3:30"
312 33
	 * @param $hours integer between 0 and 24
313 33
	 * @param $minutes integer between 0 and 59
314 33
	 * @param $seconds integer between 0 and 59, or false if not given
315
	 * @param $timeoffset double for time offset (e.g. 3.5), or false if not given
316 18
	 * @return boolean stating if the parsing succeeded
317 18
	 */
318 18
	public static function parseTimeString( $string, &$hours, &$minutes, &$seconds, &$timeoffset ) {
319 8
		if ( !preg_match( "/^[T]?([0-2]?[0-9]):([0-5][0-9])(:[0-5][0-9])?(([+\-][0-2]?[0-9])(:(30|00))?)?$/u", $string, $match ) ) {
320
			return false;
321 14
		} else {
322
			$nhours = intval( $match[1] );
323 18
			$nminutes = $match[2] ? intval( $match[2] ) : false;
324 18
			if ( ( count( $match ) > 3 ) && ( $match[3] !== '' ) ) {
325 18
				$nseconds = intval( substr( $match[3], 1 ) );
326 18
			} else {
327 18
				$nseconds = false;
328 3
			}
329 3
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
330 3
				$hours = $nhours;
331
				$minutes = $nminutes;
332
				$seconds = $nseconds;
333 18
				if ( ( count( $match ) > 5 ) && ( $match[5] !== '' ) ) {
334
					$timeoffset = intval( $match[5] );
335 18
					if ( ( count( $match ) > 7 ) && ( $match[7] == '30' ) ) {
336
						$timeoffset += 0.5;
337
					}
338
				} else {
339
					$timeoffset = false;
340
				}
341
				return true;
342
			} else {
343
				return false;
344
			}
345
		}
346
	}
347
348
	/**
349
	 * Parse the given string to check if it encodes a "military time".
350
	 * If successful, the function sets the provided call-by-ref values to
351
	 * the respective numbers and returns true. Otherwise, it returns
352
	 * false and does not set any values.
353
	 * @param $string string input time representation, e.g. "134523"
354
	 * @param $hours integer between 0 and 24
355
	 * @param $minutes integer between 0 and 59
356
	 * @param $seconds integer between 0 and 59, or false if not given
357
	 * @return boolean stating if the parsing succeeded
358
	 */
359
	protected static function parseMilTimeString( $string, &$hours, &$minutes, &$seconds ) {
360
		if ( !preg_match( "/^([0-2][0-9])([0-5][0-9])([0-5][0-9])?$/u", $string, $match ) ) {
361
			return false;
362
		} else {
363
			$nhours = intval( $match[1] );
364
			$nminutes = $match[2] ? intval( $match[2] ) : false;
365
			$nseconds = ( ( count( $match ) > 3 ) && $match[3] ) ? intval( $match[3] ) : false;
366
			if ( ( $nhours < 25 ) && ( ( $nhours < 24 ) || ( $nminutes + $nseconds == 0 ) ) ) {
367
				$hours = $nhours;
368
				$minutes = $nminutes;
369
				$seconds = $nseconds;
370
				return true;
371
			} else {
372
				return false;
373
			}
374
		}
375
	}
376
377
	/**
378
	 * Parse the given string to check if it refers to the string name ot
379
	 * abbreviation of a month name. If yes, it is replaced by a normalized
380 33
	 * month name (placed in the call-by-ref parameter) and true is
381
	 * returned. Otherwise, false is returned and $monthname is not changed.
382
	 * @param $string string month name or abbreviation to parse
383 33
	 * @param $monthname string with standard 3-letter English month abbreviation
384
	 * @return boolean stating whether a month was found
385 33
	 */
386 32
	private function parseMonthString( $string, &$monthname ) {
387
388 5
		// takes precedence over English month names!
389
		$monthnum = Localizer::getInstance()->getExtraneousLanguage( $this->getOption( self::OPT_CONTENT_LANGUAGE ) )->findMonthNumberByLabel( $string );
0 ignored issues
show
Security Bug introduced by
It seems like $this->getOption(self::OPT_CONTENT_LANGUAGE) targeting SMWDataValue::getOption() can also be of type false; however, SMW\Localizer::getExtraneousLanguage() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
390
391 33
		if ( $monthnum !== false ) {
392 32
			$monthnum -= 1;
393 32
		} else {
394 4
			$monthnum = array_search( $string, self::$m_months ); // check English names
395 1
		}
396 1
397
		if ( $monthnum !== false ) {
398 3
			$monthname = self::$m_monthsshort[$monthnum];
399
			return true;
400
		} elseif ( array_search( $string, self::$m_monthsshort ) !== false ) {
401
			$monthname = $string;
402
			return true;
403
		} else {
404
			return false;
405
		}
406
	}
407
408
	/**
409
	 * Validate and interpret the date components as retrieved when parsing
410
	 * a user input. The method takes care of guessing how a list of values
411
	 * such as "10 12 13" is to be interpreted using the current language
412
	 * settings. The result is stored in the call-by-ref parameter
413
	 * $date that uses keys 'y', 'm', 'd' and contains the respective
414
	 * numbers as values, or false if not specified. If errors occur, error
415 37
	 * messages are added to the objects list of errors, and false is
416
	 * returned. Otherwise, true is returned.
417
	 * @param $datecomponents array of strings that might belong to the specification of a date
418
	 * @param $date array set to result
419
	 * @return boolean stating if successful
420
	 */
421
	protected function interpretDateComponents( $datecomponents, &$date ) {
422
423
		// The following code segment creates a bit vector to encode
424
		// which role each digit of the entered date can take (day,
425
		// year, month). The vector starts with 1 and contains three
426
		// bits per date component, set ot true whenever this component
427
		// could be a month, a day, or a year (this is the order).
428 37
		// Examples:
429 37
		//   100 component could only be a month
430 37
		//   010 component could only be a day
431 37
		//   001 component could only be a year
432 37
		//   011 component could be a day or a year but no month etc.
433 37
		// For three components, we thus get a 10 digit bit vector.
434 36
		$datevector = 1;
435 9
		$propercomponents = array();
436 1
		$justfounddash = true; // avoid two dashes in a row, or dashes at the end
437 1
		$error = false;
438
		$numvalue = 0;
439 8
		foreach ( $datecomponents as $component ) {
440
			if ( $component == "-" ) {
441 36
				if ( $justfounddash ) {
442 36
					$error = true;
443 36
					break;
444
				}
445
				$justfounddash = true;
446
			} else {
447 37
				$justfounddash = false;
448
				$datevector = ( $datevector << 3 ) | $this->checkDateComponent( $component, $numvalue );
449 4
				$propercomponents[] = $numvalue;
450
			}
451 4
		}
452 4
453
		if ( ( $error ) || ( $justfounddash ) || ( count( $propercomponents ) == 0 ) || ( count( $propercomponents ) > 3 ) ) {
454
455
			$msgKey = 'smw-datavalue-time-invalid-date-components';
456
457
			if ( $justfounddash ) {
458
				$msgKey .= '-dash';
459
			} elseif ( count( $propercomponents ) == 0 ) {
460
				$msgKey .= '-empty';
461 4
			} elseif ( count( $propercomponents ) > 3 ) {
462 4
				$msgKey .= '-three';
463
			} else{
464
				$msgKey .= '-common';
465
			}
466 36
467 36
			$this->addErrorMsg( array( $msgKey, $this->m_wikivalue ) );
468 36
			return false;
469 36
		}
470 36
471 36
		// Now use the bitvector to find the preferred interpretation of the date components:
472 36
		$dateformats = Localizer::getInstance()->getExtraneousLanguage( $this->getOption( self::OPT_CONTENT_LANGUAGE ) )->getDateFormats();
0 ignored issues
show
Security Bug introduced by
It seems like $this->getOption(self::OPT_CONTENT_LANGUAGE) targeting SMWDataValue::getOption() can also be of type false; however, SMW\Localizer::getExtraneousLanguage() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
473 36
		$date = array( 'y' => false, 'm' => false, 'd' => false );
474
		foreach ( $dateformats[count( $propercomponents ) - 1] as $formatvector ) {
475 36
			if ( !( ~$datevector & $formatvector ) ) { // check if $formatvector => $datevector ("the input supports the format")
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
476
				$i = 0;
477
				foreach ( self::$m_formats[$formatvector] as $fieldname ) {
478 36
					$date[$fieldname] = $propercomponents[$i];
479
					$i += 1;
480
				}
481
				break;
482 36
			}
483
		}
484
		if ( $date['y'] === false ) { // no band matches the entered date
485
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-date-components-sequence', $this->m_wikivalue ) );
486
			return false;
487
		}
488
		return true;
489
	}
490
491
	/**
492
	 * Initialise data from the provided intermediate results after
493
	 * parsing, assuming that a conventional date notation is used.
494
	 * If errors occur, error messages are added to the objects list of
495
	 * errors, and false is returned. Otherwise, true is returned.
496
	 * @param $datecomponents array of strings that might belong to the specification of a date
497
	 * @param $calendarmodesl string if model was set in input, otherwise false
498
	 * @param $era string '+' or '-' if provided, otherwise false
499 37
	 * @param $hours integer value between 0 and 24
500 37
	 * @param $minutes integer value between 0 and 59
501 37
	 * @param $seconds integer value between 0 and 59, or false if not given
502 4
	 * @param $timeoffset double value for time offset (e.g. 3.5), or false if not given
503
	 * @return boolean stating if successful
504
	 */
505
	protected function setDateFromParsedValues( $datecomponents, $calendarmodel, $era, $hours, $minutes, $seconds, $microseconds, $timeoffset, $timezone ) {
506 36
		$date = false;
507 4
		if ( !$this->interpretDateComponents( $datecomponents, $date ) ) {
508
			return false;
509
		}
510
511 36
		// Handle BC: the year is negative.
512 1
		if ( ( $era == '-' ) && ( $date['y'] > 0 ) ) { // see class documentation on BC, "year 0", and ISO conformance ...
513
			$date['y'] = -( $date['y'] );
514
		}
515
516 36
		// Keep information about the era
517 36
		if ( ( $era == '+' ) && ( $date['y'] > 0 ) ) {
518
			$date['y'] = $era . $date['y'];
519
		}
520
521 36
		// Old Style is a special case of Julian calendar model where the change of the year was 25 March:
522
		if ( ( $calendarmodel == 'OS' ) &&
523 36
		     ( ( $date['m'] < 3 ) || ( ( $date['m'] == 3 ) && ( $date['d'] < 25 ) ) ) ) {
524
			$date['y']++;
525
		}
526
527
		$calmod = $this->getCalendarModel( $calendarmodel, $date['y'], $date['m'], $date['d'] );
528
		try {
529
			$this->m_dataitem = new SMWDITime( $calmod, $date['y'], $date['m'], $date['d'], $hours, $minutes, $seconds . '.' . $microseconds, $timezone );
0 ignored issues
show
Documentation introduced by
$seconds . '.' . $microseconds 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...
530
		} catch ( SMWDataItemException $e ) {
531
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid', $this->m_wikivalue, $e->getMessage() ) );
532 36
			return false;
533 36
		}
534
535
		// Having more than years or specifying a calendar model does
536
		// not make sense for prehistoric dates, and our calendar
537 36
		// conversion would not be reliable if JD numbers get too huge:
538 3
		if ( ( $date['y'] <= self::PREHISTORY ) &&
539
		     ( ( $this->m_dataitem->getPrecision() > SMWDITime::PREC_Y ) || ( $calendarmodel !== false ) ) ) {
540 3
			$this->addErrorMsg( array( 'smw-datavalue-time-invalid-prehistoric', $this->m_wikivalue ) );
541
			return false;
542
		}
543
		if ( $timeoffset != 0 ) {
544
			$newjd = $this->m_dataitem->getJD() - $timeoffset / 24;
545
			try {
546 36
				$this->m_dataitem = SMWDITime::newFromJD( $newjd, $calmod, $this->m_dataitem->getPrecision(), $timezone );
547
			} catch ( SMWDataItemException $e ) {
548
				$this->addErrorMsg( array( 'smw-datavalue-time-invalid-jd', $this->m_wikivalue, $e->getMessage() ) );
549
				return false;
550
			}
551
		}
552
		return true;
553
	}
554
555
	/**
556
	 * Check which roles a string component might play in a date, and
557
	 * set the call-by-ref parameter to the proper numerical
558
	 * representation. The component string has already been normalized to
559
	 * be either a plain number, a month name, or a plain number with "d"
560 36
	 * pre-pended. The result is a bit vector to indicate the possible
561 36
	 * interpretations.
562
	 * @param $component string
563
	 * @param $numvalue integer representing the components value
564 36
	 * @return integer that encodes a three-digit bit vector
565 36
	 */
566 36
	protected static function checkDateComponent( $component, &$numvalue ) {
567 31
		if ( $component === '' ) { // should not happen
568 36
			$numvalue = 0;
569 19
			return 0;
570
		} elseif ( is_numeric( $component ) ) {
571 36
			$numvalue = intval( $component );
572
			if ( ( $numvalue >= 1 ) && ( $numvalue <= 12 ) ) {
573 32
				return SMW_DAY_MONTH_YEAR; // can be a month, day or year
574
			} elseif ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) {
575
				return SMW_DAY_YEAR; // can be day or year
576
			} else { // number can just be a year
577
				return SMW_YEAR;
578
			}
579
		} elseif ( $component { 0 } == 'd' ) { // already marked as day
580
			if ( is_numeric( substr( $component, 1 ) ) ) {
581 32
				$numvalue = intval( substr( $component, 1 ) );
582 32
				return ( ( $numvalue >= 1 ) && ( $numvalue <= 31 ) ) ? SMW_DAY : 0;
583 32
			} else {
584 32
				return 0;
585
			}
586
		} else {
587
			$monthnum = array_search( $component, self::$m_monthsshort );
588
			if ( $monthnum !== false ) {
589
				$numvalue = $monthnum + 1;
590
				return SMW_MONTH;
591
			} else {
592
				return 0;
593
			}
594
		}
595
	}
596
597
	/**
598
	 * Determine the calender model under which an input should be
599
	 * interpreted based on the given input data.
600 43
	 * @param $presetmodel mixed string related to a user input calendar model (OS, Jl, Gr) or false
601 43
	 * @param $year integer of the given year (adjusted for BC(E), i.e. possibly negative)
602
	 * @param $month mixed integer of the month or false
603
	 * @param $day mixed integer of the day or false
604 43
	 * @return integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
605 3
	 */
606 43
	protected function getCalendarModel( $presetmodel, $year, $month, $day ) {
607 3
		if ( $presetmodel == 'OS' ) { // Old Style is a notational convention of Julian dates only
608
			$presetmodel = 'Jl';
609 42
		}
610 13
		if ( $presetmodel === 'Gr' || $presetmodel === 'GR' ) {
611 42
			return SMWDITime::CM_GREGORIAN;
612 42
		} elseif (  $presetmodel === 'Jl' || $presetmodel === 'JL' ) {
613 13
			return SMWDITime::CM_JULIAN;
614 13
		}
615
		if ( ( $year > 1582 ) ||
616
		     ( ( $year == 1582 ) && ( $month > 10 ) ) ||
617
		     ( ( $year == 1582 ) && ( $month == 10 ) && ( $day > 4 ) ) ) {
618
			return SMWDITime::CM_GREGORIAN;
619 3
		} elseif ( $year > self::PREHISTORY ) {
620
			return SMWDITime::CM_JULIAN;
621
		} else {
622
			// proleptic Julian years at some point deviate from the count of complete revolutions of the earth around the sun
623
			// hence assume that earlier date years are Gregorian (where this effect is very weak only)
624
			// This is mostly for internal use since we will not allow users to specify calendar models at this scale
625
			return SMWDITime::CM_GREGORIAN;
626
		}
627
	}
628 70
629
	/**
630 70
	 * @see SMWDataValue::loadDataItem
631
	 *
632
	 * {@inheritDoc}
633
	 */
634 70
	protected function loadDataItem( SMWDataItem $dataItem ) {
635 70
636 70
		if ( $dataItem->getDIType() !== SMWDataItem::TYPE_TIME ) {
637
			return false;
638 70
		}
639
640
		$this->m_dataitem = $dataItem;
641
		$this->m_caption = false;
0 ignored issues
show
Documentation Bug introduced by
The property $m_caption was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
642
		$this->m_wikivalue = false;
643
644
		return true;
645
	}
646 34
647 34
	/**
648
	 * @see SMWDataValue::getShortWikiText
649
	 *
650
	 * {@inheritDoc}
651
	 */
652
	public function getShortWikiText( $linker = null ) {
653
		return $this->dataValueServiceFactory->getValueFormatter( $this )->format( DataValueFormatter::WIKI_SHORT, $linker );
654
	}
655 1
656 1
	/**
657
	 * @see SMWDataValue::getShortHTMLText
658
	 *
659
	 * {@inheritDoc}
660
	 */
661
	public function getShortHTMLText( $linker = null ) {
662
		return $this->dataValueServiceFactory->getValueFormatter( $this )->format( DataValueFormatter::HTML_SHORT, $linker );
663
	}
664 15
665 15
	/**
666
	 * @see SMWDataValue::getLongWikiText
667
	 *
668
	 * {@inheritDoc}
669
	 */
670
	public function getLongWikiText( $linker = null ) {
671
		return $this->dataValueServiceFactory->getValueFormatter( $this )->format( DataValueFormatter::WIKI_LONG, $linker );
672
	}
673 6
674 6
	/**
675
	 * @see SMWDataValue::getLongHTMLText
676
	 *
677
	 * {@inheritDoc}
678
	 */
679
	public function getLongHTMLText( $linker = null ) {
680
		return $this->dataValueServiceFactory->getValueFormatter( $this )->format( DataValueFormatter::HTML_LONG, $linker );
681
	}
682
683 12
	/**
684 12
	 * @todo The preferred caption may not be suitable as a wiki value (i.e. not parsable).
685
	 * @see SMWDataValue::getLongHTMLText
686
	 *
687
	 * {@inheritDoc}
688
	 */
689
	public function getWikiValue() {
690
		return $this->m_wikivalue ? $this->m_wikivalue : strip_tags( $this->getLongWikiText() );
691
	}
692
693
	/**
694
	 * @see SMWDataValue::isNumeric
695
	 *
696
	 * {@inheritDoc}
697
	 */
698
	public function isNumeric() {
699
		return true;
700
	}
701
702
	/**
703
	 * Return the year number in the given calendar model, or false if
704 1
	 * this number is not available (typically when attempting to get
705 1
	 * prehistoric Julian calendar dates). As everywhere in this class,
706 1
	 * there is no year 0.
707 1
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
708
	 * @return mixed typically a number but possibly false
709
	 */
710
	public function getYear( $calendarmodel = SMWDITime::CM_GREGORIAN ) {
711
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
712
		if ( !is_null( $di ) ) {
713
			return $di->getYear();
714
		} else {
715
			return false;
716
		}
717
	}
718
719
	/**
720
	 * Return the month number in the given calendar model, or false if
721 1
	 * this number is not available (typically when attempting to get
722 1
	 * prehistoric Julian calendar dates).
723 1
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
724 1
	 * @param $default value to return if month is not set at our level of precision
725
	 * @return mixed typically a number but possibly anything given as $default
726
	 */
727
	public function getMonth( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
728
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
729
		if ( !is_null( $di ) ) {
730
			return ( $di->getPrecision() >= SMWDITime::PREC_YM ) ? $di->getMonth() : $default;
731
		} else {
732
			return false;
733
		}
734
	}
735
736
	/**
737
	 * Return the day number in the given calendar model, or false if this
738 1
	 * number is not available (typically when attempting to get
739 1
	 * prehistoric Julian calendar dates).
740 1
	 * @param $calendarmodel integer either SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
741 1
	 * @param $default value to return if day is not set at our level of precision
742
	 * @return mixed typically a number but possibly anything given as $default
743
	 */
744
	public function getDay( $calendarmodel = SMWDITime::CM_GREGORIAN, $default = 1 ) {
745
		$di = $this->getDataItemForCalendarModel( $calendarmodel );
746
		if ( !is_null( $di ) ) {
747
			return ( $di->getPrecision() >= SMWDITime::PREC_YMD ) ? $di->getDay() : $default;
748
		} else {
749
			return false;
750
		}
751
	}
752 1
753 1
	/**
754
	 * @see TimeValueFormatter::getTimeStringFromDataItem
755
	 *
756
	 * @return
757
	 */
758
	public function getTimeString( $default = '00:00:00' ) {
759
		return $this->dataValueServiceFactory->getValueFormatter( $this )->getTimeString( $default );
760
	}
761
762
	/**
763
	 * @deprecated This method is now called getISO8601Date(). It will vanish before SMW 1.7.
764
	 */
765
	public function getXMLSchemaDate( $mindefault = true ) {
766
		return $this->getISO8601Date( $mindefault );
767
	}
768
769
	/**
770
	 * @see TimeValueFormatter::getISO8601DateFromDataItem
771
	 *
772 53
	 * @param $mindefault boolean determining whether values below the
773 53
	 * precision of our input should be completed with minimal or maximal
774
	 * conceivable values
775
	 *
776
	 * @return string
777
	 */
778
	public function getISO8601Date( $mindefault = true ) {
779
		return $this->dataValueServiceFactory->getValueFormatter( $this )->getISO8601Date( $mindefault );
780
	}
781 1
782 1
	/**
783
	 * @see TimeValueFormatter::getMediaWikiDateFromDataItem
784
	 *
785
	 * @return string
786
	 */
787
	public function getMediaWikiDate() {
788
		return $this->dataValueServiceFactory->getValueFormatter( $this )->getMediaWikiDate();
789
	}
790
791
	/**
792
	 * Get the current data in the specified calendar model. Conversion is
793
	 * not done for prehistoric dates (where it might lead to precision
794 69
	 * errors and produce results that are not meaningful). In this case,
795 69
	 * null might be returned if no data in the specified format is
796 3
	 * available.
797 69
	 * @param $calendarmodel integer one of SMWDITime::CM_GREGORIAN or SMWDITime::CM_JULIAN
798 69
	 * @return SMWDITime
799 69
	 */
800
	public function getDataItemForCalendarModel( $calendarmodel ) {
801 69
		if ( $this->m_dataitem->getYear() <= self::PREHISTORY ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getYear() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
802
			return ( $this->m_dataitem->getCalendarModel() == $calendarmodel ) ? $this->m_dataitem : null;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
803 5
		} elseif ( $calendarmodel == SMWDITime::CM_GREGORIAN ) {
804 5
			if ( is_null( $this->m_dataitem_greg ) ) {
805
				$this->m_dataitem_greg = $this->m_dataitem->getForCalendarModel( SMWDITime::CM_GREGORIAN );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getForCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
806 5
			}
807
			return $this->m_dataitem_greg;
808
		} else {
809
			if ( is_null( $this->m_dataitem_jul ) ) {
810 44
				$this->m_dataitem_jul = $this->m_dataitem->getForCalendarModel( SMWDITime::CM_JULIAN );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getForCalendarModel() does only exist in the following sub-classes of SMWDataItem: SMWDITime. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
811 44
			}
812
			return $this->m_dataitem_jul;
813
		}
814 37
	}
815
816 37
	private function isInterpretableAsYearOnly( $value ) {
817
		return strpos( $value, ' ' ) === false && is_numeric( strval( $value ) ) && ( strval( $value ) < 0 || strlen( $value ) < 6 );
818
	}
819
820
	private function isInterpretableAsTimestamp( $value ) {
821
		// 1200-11-02T12:03:25 or 20120320055913
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
822
		return ( ( strlen( $value ) > 4 && substr( $value, 10, 1 ) === 'T' ) || strlen( $value ) == 14 ) && wfTimestamp( TS_MW, $value ) !== false;
823
	}
824
825
}
826