TSimpleDateFormatter::parse()   F
last analyzed

Complexity

Conditions 36
Paths 3008

Size

Total Lines 135
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 67
CRAP Score 53.6042

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 36
eloc 90
c 1
b 0
f 0
nc 3008
nop 2
dl 0
loc 135
ccs 67
cts 88
cp 0.7614
crap 53.6042
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * TSimpleDateFormatter class file
5
 *
6
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Util;
12
13
use Prado\Prado;
14
use Prado\Exceptions\TInvalidDataValueException;
15
16
/**
17
 * TSimpleDateFormatter class.
18
 *
19
 * Formats and parses dates using the SimpleDateFormat pattern.
20
 * This pattern is compatible with the I18N and java's SimpleDateFormatter.
21
 * ```
22
 * Pattern |      Description
23
 * ----------------------------------------------------
24
 * d       | Day of month 1 to 31, no padding
25
 * dd      | Day of monath 01 to 31, zero leading
26
 * M       | Month digit 1 to 12, no padding
27
 * MM      | Month digit 01 to 12, zero leading
28
 * yy      | 2 year digit, e.g., 96, 05
29
 * yyyy    | 4 year digit, e.g., 2005
30
 * ----------------------------------------------------
31
 * ```
32
 *
33
 * Usage example, to format a date
34
 * ```php
35
 * $formatter = new TSimpleDateFormatter("dd/MM/yyyy");
36
 * echo $formatter->format(time());
37
 * ```
38
 *
39
 * To parse the date string into a date timestamp.
40
 * ```php
41
 * $formatter = new TSimpleDateFormatter("d-M-yyyy");
42
 * echo $formatter->parse("24-6-2005");
43
 * ```
44
 *
45
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
46
 * @since 3.0
47
 */
48
class TSimpleDateFormatter
49
{
50
	/**
51
	 * Formatting pattern.
52
	 * @var string
53
	 */
54
	private $pattern;
55
56
	/**
57
	 * Charset, default is 'UTF-8'
58
	 * @var string
59
	 */
60
	private $charset = 'UTF-8';
61
62
	/**
63
	 * Constructor, create a new date time formatter.
64
	 * @param string $pattern formatting pattern.
65
	 * @param string $charset pattern and value charset
66
	 */
67
	public function __construct($pattern, $charset = 'UTF-8')
68 2
	{
69
		$this->setPattern($pattern);
70 2
		$this->setCharset($charset);
71 2
	}
72 2
73
	/**
74
	 * @return string formatting pattern.
75
	 */
76
	public function getPattern()
77
	{
78
		return $this->pattern;
79
	}
80
81
	/**
82
	 * @param string $pattern formatting pattern.
83
	 */
84
	public function setPattern($pattern)
85 2
	{
86
		$this->pattern = $pattern;
87 2
	}
88 2
89
	/**
90
	 * @return string formatting charset.
91
	 */
92
	public function getCharset()
93
	{
94
		return $this->charset;
95
	}
96
97
	/**
98
	 * @param string $charset formatting charset.
99
	 */
100
	public function setCharset($charset)
101 2
	{
102
		$this->charset = $charset;
103 2
	}
104 2
105
	/**
106
	 * Format the date according to the pattern.
107
	 * @param int|string $value the date to format, either integer or a string readable by strtotime.
108
	 * @return string formatted date.
109
	 */
110
	public function format($value)
111
	{
112
		$dt = $this->getDate($value);
113
		$bits['yyyy'] = $dt->format('Y');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$bits was never initialized. Although not strictly required by PHP, it is generally a good practice to add $bits = array(); before regardless.
Loading history...
114
		$bits['yy'] = $dt->format('y');
115
116
		$bits['MM'] = $dt->format('m');
117
		$bits['M'] = $dt->format('n');
118
119
		$bits['dd'] = $dt->format('d');
120
		$bits['d'] = $dt->format('j');
121
122
		$pattern = preg_replace('/M{3,4}/', 'MM', $this->pattern);
123
		return str_replace(array_keys($bits), $bits, $pattern);
124
	}
125
126
	public function getMonthPattern()
127
	{
128
		if (is_int(strpos($this->pattern, 'MMMM'))) {
0 ignored issues
show
introduced by
The condition is_int(strpos($this->pattern, 'MMMM')) is always true.
Loading history...
129
			return 'MMMM';
130
		}
131
		if (is_int(strpos($this->pattern, 'MMM'))) {
132
			return 'MMM';
133
		}
134
		if (is_int(strpos($this->pattern, 'MM'))) {
135
			return 'MM';
136
		}
137
		if (is_int(strpos($this->pattern, 'M'))) {
138
			return 'M';
139
		}
140
		return false;
141
	}
142
143
	public function getDayPattern()
144
	{
145
		if (is_int(strpos($this->pattern, 'dd'))) {
0 ignored issues
show
introduced by
The condition is_int(strpos($this->pattern, 'dd')) is always true.
Loading history...
146
			return 'dd';
147
		}
148
		if (is_int(strpos($this->pattern, 'd'))) {
149
			return 'd';
150
		}
151
		return false;
152
	}
153
154
	public function getYearPattern()
155
	{
156
		if (is_int(strpos($this->pattern, 'yyyy'))) {
0 ignored issues
show
introduced by
The condition is_int(strpos($this->pattern, 'yyyy')) is always true.
Loading history...
157
			return 'yyyy';
158
		}
159
		if (is_int(strpos($this->pattern, 'yy'))) {
160
			return 'yy';
161
		}
162
		return false;
163
	}
164
165
	public function getDayMonthYearOrdering()
166
	{
167
		$ordering = [];
168
		if (is_int($day = strpos($this->pattern, 'd'))) {
0 ignored issues
show
introduced by
The condition is_int($day = strpos($this->pattern, 'd')) is always true.
Loading history...
169
			$ordering['day'] = $day;
170
		}
171
		if (is_int($month = strpos($this->pattern, 'M'))) {
0 ignored issues
show
introduced by
The condition is_int($month = strpos($this->pattern, 'M')) is always true.
Loading history...
172
			$ordering['month'] = $month;
173
		}
174
		if (is_int($year = strpos($this->pattern, 'yy'))) {
0 ignored issues
show
introduced by
The condition is_int($year = strpos($this->pattern, 'yy')) is always true.
Loading history...
175
			$ordering['year'] = $year;
176
		}
177
		asort($ordering);
178
		return array_keys($ordering);
179
	}
180
181
	/**
182
	 * Gets the time stamp from string or integer.
183
	 * @param int|string $value date to parse
184
	 * @return \DateTime date info
185
	 */
186
	private function getDate($value)
187
	{
188
		if (is_numeric($value)) {
189
			$date = new \DateTime();
190
			$date->setTimeStamp($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $timestamp of DateTime::setTimestamp() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
			$date->setTimeStamp(/** @scrutinizer ignore-type */ $value);
Loading history...
191
		} else {
192
			$date = new \DateTime($value);
193
		}
194
		return $date;
195
	}
196
197
	/**
198
	 * @param mixed $value
199
	 * @return bool true if the given value matches with the date pattern.
200
	 */
201
	public function isValidDate($value)
202
	{
203
		if ($value === null) {
204
			return false;
205
		} else {
206
			return $this->parse($value, false) !== null;
207
		}
208
	}
209
210
	/**
211
	 * Parse the string according to the pattern.
212
	 * @param int|string $value date string or integer to parse
213
	 * @param bool $defaultToCurrentTime
214
	 * @throws TInvalidDataValueException if date string is malformed.
215
	 * @return int date time stamp
216
	 */
217
	public function parse($value, $defaultToCurrentTime = true)
218 2
	{
219
		if (is_int($value) || is_float($value)) {
220 2
			return $value;
221
		}
222 2
		if (!is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
223
			throw new TInvalidDataValueException('date_to_parse_must_be_string', Prado::varDump($value));
224
		}
225
226 2
		if (empty($this->pattern)) {
227
			return time();
228
		}
229
230 2
		if ($this->length(trim($value)) < 1) {
231
			return $defaultToCurrentTime ? time() : null;
232 2
		}
233
234
		$pattern = $this->pattern;
235
236 2
		$i_val = 0;
237
		$i_format = 0;
238 2
		$pattern_length = $this->length($pattern);
239 2
		$token = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $token is dead and can be removed.
Loading history...
240 2
		$x = null;
241 2
		$y = null;
242 2
243 2
		$year = null;
244 2
		$month = null;
245
		$day = null;
246
247 2
		while ($i_format < $pattern_length) {
248 2
			$c = $this->charAt($pattern, $i_format);
249 2
			$token = '';
250 2
			while ($this->charEqual($pattern, $i_format, $c)
251
						&& ($i_format < $pattern_length)) {
252
				$token .= $this->charAt($pattern, $i_format++);
253
			}
254
255
			switch ($token) {
256
				case 'yyyy':
257 2
				case 'yy':
258 2
				case 'y':
259 2
					{
260 2
						if ($token == 'yyyy') {
261 2
							$x = 4;
262 2
							$y = 4;
263
						}
264
						if ($token == 'yy') {
265 2
							$x = 2;
266 1
							$y = 2;
267 1
						}
268 1
						if ($token == 'y') {
269
							$x = 2;
270 1
							$y = 4;
271
						}
272
						$year = $this->getInteger($value, $i_val, $x, $y);
273
						if ($year === null) {
274 1
							return null;
275
						}
276
						$i_val += strlen($year);
277
						if (strlen($year) == 2) {
278 1
							$iYear = (int) $year;
279 1
							if ($iYear > 70) {
280
								$year = $iYear + 1900;
281
							} else {
282
								$year = $iYear + 2000;
283 1
							}
284 1
						}
285
						$year = (int) $year;
286
						break;
287
					}
288
				case 'MM':
289
				case 'M':
290
					{
291
						$month = $this->getInteger(
292 1
							$value,
293 1
							$i_val,
294 1
							$this->length($token),
295 1
							2
296 1
						);
297 1
						$iMonth = (int) $month;
298 1
						if ($month === null || $iMonth < 1 || $iMonth > 12) {
299
							return null;
300 1
						}
301 1
						$i_val += strlen($month);
302
						$month = $iMonth;
303
						break;
304
					}
305 1
				case 'dd':
306 1
				case 'd':
307 1
					{
308 1
						$day = $this->getInteger(
309 1
							$value,
310 1
							$i_val,
311 1
							$this->length($token),
312 1
							2
313
						);
314 1
						$iDay = (int) $day;
315 1
						if ($day === null || $iDay < 1 || $iDay > 31) {
316
							return null;
317
						}
318
						$i_val += strlen($day);
319 1
						$day = $iDay;
320 1
						break;
321
					}
322 1
				default:
323
					{
324
						if ($this->substring($value, $i_val, $this->length($token)) != $token) {
325
							return null;
326
						}
327 1
						$i_val += $this->length($token);
328
						break;
329
					}
330
			}
331 2
		}
332
333
		if ($i_val != $this->length($value)) {
334
			return null;
335 2
		}
336
337
		if ($year === null) {
338 2
			// always default to current year if empty
339 1
			$year = date('Y');
340
		}
341 2
		if ($month === null) {
342 2
			$month = $defaultToCurrentTime ? date('m') : 1;
343
		}
344 2
		if ($day === null) {
345 2
			$day = $defaultToCurrentTime ? date('d') : 1;
346 2
		}
347 2
348
		$s = new \DateTime();
349
		$s->setDate($year, $month, $day);
0 ignored issues
show
Bug introduced by
It seems like $month can also be of type string; however, parameter $month of DateTime::setDate() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
		$s->setDate($year, /** @scrutinizer ignore-type */ $month, $day);
Loading history...
Bug introduced by
It seems like $day can also be of type string; however, parameter $day of DateTime::setDate() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
		$s->setDate($year, $month, /** @scrutinizer ignore-type */ $day);
Loading history...
Bug introduced by
It seems like $year can also be of type string; however, parameter $year of DateTime::setDate() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
		$s->setDate(/** @scrutinizer ignore-type */ $year, $month, $day);
Loading history...
350
		$s->setTime(0, 0, 0);
351
		return $s->getTimeStamp();
352
	}
353
354
	/**
355 2
	 * Calculate the length of a string, may be consider iconv_strlen?
356
	 * @param mixed $string
357
	 */
358 2
	private function length($string)
359
	{
360
		//use iconv_strlen or just strlen?
361
		return strlen($string);
362
	}
363
364
	/**
365
	 * Get the char at a position.
366 2
	 * @param mixed $string
367
	 * @param mixed $pos
368 2
	 */
369
	private function charAt($string, $pos)
370
	{
371
		return $this->substring($string, $pos, 1);
372
	}
373
374
	/**
375
	 * Gets a portion of a string, uses iconv_substr.
376
	 * @param mixed $string
377 2
	 * @param mixed $start
378
	 * @param mixed $length
379 2
	 */
380
	private function substring($string, $start, $length)
381
	{
382
		return iconv_substr($string, $start, $length);
383
	}
384
385
	/**
386
	 * Returns true if char at position equals a particular char.
387
	 * @param mixed $string
388 2
	 * @param mixed $pos
389
	 * @param mixed $char
390 2
	 */
391
	private function charEqual($string, $pos, $char)
392
	{
393
		return $this->charAt($string, $pos) == $char;
394
	}
395
396
	/**
397
	 * Gets integer from part of a string, allows integers of any length.
398
	 * @param string $str string to retrieve the integer from.
399
	 * @param int $i starting position
400
	 * @param int $minlength minimum integer length
401 2
	 * @param int $maxlength maximum integer length
402
	 * @return string integer portion of the string, null otherwise
403
	 */
404 2
	private function getInteger($str, $i, $minlength, $maxlength)
405 2
	{
406 2
		//match for digits backwards
407
		for ($x = $maxlength; $x >= $minlength; $x--) {
408
			$token = $this->substring($str, $i, $x);
409 2
			if ($this->length($token) < $minlength) {
410 2
				return null;
411
			}
412
			if (preg_match('/^\d+$/', $token)) {
413
				return $token;
414
			}
415
		}
416
		return null;
417
	}
418
}
419