Test Failed
Branch master (206474)
by Fabio
18:24
created

TSimpleDateFormatter::parse()   F

Complexity

Conditions 39
Paths 1149

Size

Total Lines 132
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 39
eloc 93
nc 1149
nop 2
dl 0
loc 132
rs 2
c 0
b 0
f 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
 * TSimpleDateFormatter class file
4
 *
5
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6
 * @link https://github.com/pradosoft/prado
7
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Util
10
 */
11
12
namespace Prado\Util;
13
14
use Prado\Prado;
15
use Prado\Exceptions\TInvalidDataValueException;
16
17
/**
18
 * TSimpleDateFormatter class.
19
 *
20
 * Formats and parses dates using the SimpleDateFormat pattern.
21
 * This pattern is compatible with the I18N and java's SimpleDateFormatter.
22
 * <code>
23
 * Pattern |      Description
24
 * ----------------------------------------------------
25
 * d       | Day of month 1 to 31, no padding
26
 * dd      | Day of monath 01 to 31, zero leading
27
 * M       | Month digit 1 to 12, no padding
28
 * MM      | Month digit 01 to 12, zero leading
29
 * yy      | 2 year digit, e.g., 96, 05
30
 * yyyy    | 4 year digit, e.g., 2005
31
 * ----------------------------------------------------
32
 * </code>
33
 *
34
 * Usage example, to format a date
35
 * <code>
36
 * $formatter = new TSimpleDateFormatter("dd/MM/yyy");
37
 * echo $formatter->format(time());
38
 * </code>
39
 *
40
 * To parse the date string into a date timestamp.
41
 * <code>
42
 * $formatter = new TSimpleDateFormatter("d-M-yyy");
43
 * echo $formatter->parse("24-6-2005");
44
 * </code>
45
 *
46
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
47
 * @package Prado\Util
48
 * @since 3.0
49
 */
50
class TSimpleDateFormatter
51
{
52
	/**
53
	 * Formatting pattern.
54
	 * @var string
55
	 */
56
	private $pattern;
57
58
	/**
59
	 * Charset, default is 'UTF-8'
60
	 * @var string
61
	 */
62
	private $charset = 'UTF-8';
63
64
	/**
65
	 * Constructor, create a new date time formatter.
66
	 * @param string $pattern formatting pattern.
67
	 * @param string $charset pattern and value charset
68
	 */
69
	public function __construct($pattern, $charset = 'UTF-8')
70
	{
71
		$this->setPattern($pattern);
72
		$this->setCharset($charset);
73
	}
74
75
	/**
76
	 * @return string formatting pattern.
77
	 */
78
	public function getPattern()
79
	{
80
		return $this->pattern;
81
	}
82
83
	/**
84
	 * @param string $pattern formatting pattern.
85
	 */
86
	public function setPattern($pattern)
87
	{
88
		$this->pattern = $pattern;
89
	}
90
91
	/**
92
	 * @return string formatting charset.
93
	 */
94
	public function getCharset()
95
	{
96
		return $this->charset;
97
	}
98
99
	/**
100
	 * @param string $charset formatting charset.
101
	 */
102
	public function setCharset($charset)
103
	{
104
		$this->charset = $charset;
105
	}
106
107
	/**
108
	 * Format the date according to the pattern.
109
	 * @param int|string $value the date to format, either integer or a string readable by strtotime.
110
	 * @return string formatted date.
111
	 */
112
	public function format($value)
113
	{
114
		$dt = $this->getDate($value);
115
		$bits['yyyy'] = $dt->format('Y');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$bits was never initialized. Although not strictly required by PHP, it is generally a good practice to add $bits = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
116
		$bits['yy'] = $dt->format('y');
117
118
		$bits['MM'] = $dt->format('m');
119
		$bits['M'] = $dt->format('n');
120
121
		$bits['dd'] = $dt->format('d');
122
		$bits['d'] = $dt->format('j');
123
124
		$pattern = preg_replace('/M{3,4}/', 'MM', $this->pattern);
125
		return str_replace(array_keys($bits), $bits, $pattern);
126
	}
127
128
	public function getMonthPattern()
129
	{
130
		if (is_int(strpos($this->pattern, 'MMMM'))) {
131
			return 'MMMM';
132
		}
133
		if (is_int(strpos($this->pattern, 'MMM'))) {
134
			return 'MMM';
135
		}
136
		if (is_int(strpos($this->pattern, 'MM'))) {
137
			return 'MM';
138
		}
139
		if (is_int(strpos($this->pattern, 'M'))) {
140
			return 'M';
141
		}
142
		return false;
143
	}
144
145 View Code Duplication
	public function getDayPattern()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
	{
147
		if (is_int(strpos($this->pattern, 'dd'))) {
148
			return 'dd';
149
		}
150
		if (is_int(strpos($this->pattern, 'd'))) {
151
			return 'd';
152
		}
153
		return false;
154
	}
155
156 View Code Duplication
	public function getYearPattern()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
157
	{
158
		if (is_int(strpos($this->pattern, 'yyyy'))) {
159
			return 'yyyy';
160
		}
161
		if (is_int(strpos($this->pattern, 'yy'))) {
162
			return 'yy';
163
		}
164
		return false;
165
	}
166
167
	public function getDayMonthYearOrdering()
168
	{
169
		$ordering = [];
170
		if (is_int($day = strpos($this->pattern, 'd'))) {
171
			$ordering['day'] = $day;
172
		}
173
		if (is_int($month = strpos($this->pattern, 'M'))) {
174
			$ordering['month'] = $month;
175
		}
176
		if (is_int($year = strpos($this->pattern, 'yy'))) {
177
			$ordering['year'] = $year;
178
		}
179
		asort($ordering);
180
		return array_keys($ordering);
181
	}
182
183
	/**
184
	 * Gets the time stamp from string or integer.
185
	 * @param int|string $value date to parse
186
	 * @return array date info array
187
	 */
188
	private function getDate($value)
189
	{
190
		if (is_numeric($value)) {
191
			$date = new \DateTime;
192
			$date->setTimeStamp($value);
193
		} else {
194
			$date = new \DateTime($value);
195
		}
196
		return $date;
197
	}
198
199
	/**
200
	 * @param mixed $value
201
	 * @return bool true if the given value matches with the date pattern.
202
	 */
203
	public function isValidDate($value)
204
	{
205
		if ($value === null) {
206
			return false;
207
		} else {
208
			return $this->parse($value, false) !== null;
209
		}
210
	}
211
212
	/**
213
	 * Parse the string according to the pattern.
214
	 * @param int|string $value date string or integer to parse
215
	 * @param bool $defaultToCurrentTime
216
	 * @throws TInvalidDataValueException if date string is malformed.
217
	 * @return int date time stamp
218
	 */
219
	public function parse($value, $defaultToCurrentTime = true)
220
	{
221
		if (is_int($value) || is_float($value)) {
222
			return $value;
223
		} elseif (!is_string($value)) {
224
			throw new TInvalidDataValueException('date_to_parse_must_be_string', $value);
225
		}
226
227
		if (empty($this->pattern)) {
228
			return time();
229
		}
230
231
		$date = time();
232
233
		if ($this->length(trim($value)) < 1) {
234
			return $defaultToCurrentTime ? $date : null;
235
		}
236
237
		$pattern = $this->pattern;
238
239
		$i_val = 0;
240
		$i_format = 0;
241
		$pattern_length = $this->length($pattern);
242
		$c = '';
0 ignored issues
show
Unused Code introduced by
$c is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
243
		$token = '';
0 ignored issues
show
Unused Code introduced by
$token is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
244
		$x = null;
245
		$y = null;
246
247
248
		if ($defaultToCurrentTime) {
249
			$year = "{$date['year']}";
250
			$month = $date['mon'];
251
			$day = $date['mday'];
252
		} else {
253
			$year = null;
254
			$month = null;
255
			$day = null;
256
		}
257
258
		while ($i_format < $pattern_length) {
259
			$c = $this->charAt($pattern, $i_format);
260
			$token = '';
261
			while ($this->charEqual($pattern, $i_format, $c)
262
						&& ($i_format < $pattern_length)) {
263
				$token .= $this->charAt($pattern, $i_format++);
264
			}
265
266
			if ($token == 'yyyy' || $token == 'yy' || $token == 'y') {
267
				if ($token == 'yyyy') {
268
					$x = 4;
269
					$y = 4;
270
				}
271
				if ($token == 'yy') {
272
					$x = 2;
273
					$y = 2;
274
				}
275
				if ($token == 'y') {
276
					$x = 2;
277
					$y = 4;
278
				}
279
				$year = $this->getInteger($value, $i_val, $x, $y);
280
				if ($year === null) {
281
					return null;
282
				}
283
				//throw new TInvalidDataValueException('Invalid year', $value);
284
				$i_val += strlen($year);
285
				if (strlen($year) == 2) {
286
					$iYear = (int) $year;
287
					if ($iYear > 70) {
288
						$year = $iYear + 1900;
289
					} else {
290
						$year = $iYear + 2000;
291
					}
292
				}
293
				$year = (int) $year;
294
			} elseif ($token == 'MM' || $token == 'M') {
295
				$month = $this->getInteger(
296
					$value,
297
					$i_val,
298
									$this->length($token),
299
					2
300
				);
301
				$iMonth = (int) $month;
302
				if ($month === null || $iMonth < 1 || $iMonth > 12) {
303
					return null;
304
				}
305
				//throw new TInvalidDataValueException('Invalid month', $value);
306
				$i_val += strlen($month);
307
				$month = $iMonth;
308
			} elseif ($token == 'dd' || $token == 'd') {
309
				$day = $this->getInteger(
310
					$value,
311
					$i_val,
312
									$this->length($token),
313
					2
314
				);
315
				$iDay = (int) $day;
316
				if ($day === null || $iDay < 1 || $iDay > 31) {
317
					return null;
318
				}
319
				//throw new TInvalidDataValueException('Invalid day', $value);
320
				$i_val += strlen($day);
321
				$day = $iDay;
322
			} else {
323
				if ($this->substring($value, $i_val, $this->length($token)) != $token) {
324
					return null;
325
				}
326
				//throw new TInvalidDataValueException("Subpattern '{$this->pattern}' mismatch", $value);
327
				else {
328
					$i_val += $this->length($token);
329
				}
330
			}
331
		}
332
		if ($i_val != $this->length($value)) {
333
			return null;
334
		}
335
		//throw new TInvalidDataValueException("Pattern '{$this->pattern}' mismatch", $value);
336
		if (!$defaultToCurrentTime && ($month === null || $day === null || $year === null)) {
337
			return null;
338
		} else {
339
			if (empty($year)) {
340
				$year = date('Y');
341
			}
342
			$day = (int) $day <= 0 ? 1 : (int) $day;
343
			$month = (int) $month <= 0 ? 1 : (int) $month;
344
345
			$s = new \DateTime;
346
			$s->setDate($year, $month, $day);
347
			$s->setTime(0, 0, 0);
348
			return $s->getTimeStamp();
349
		}
350
	}
351
352
	/**
353
	 * Calculate the length of a string, may be consider iconv_strlen?
354
	 * @param mixed $string
355
	 */
356
	private function length($string)
357
	{
358
		//use iconv_strlen or just strlen?
359
		return strlen($string);
360
	}
361
362
	/**
363
	 * Get the char at a position.
364
	 * @param mixed $string
365
	 * @param mixed $pos
366
	 */
367
	private function charAt($string, $pos)
368
	{
369
		return $this->substring($string, $pos, 1);
370
	}
371
372
	/**
373
	 * Gets a portion of a string, uses iconv_substr.
374
	 * @param mixed $string
375
	 * @param mixed $start
376
	 * @param mixed $length
377
	 */
378
	private function substring($string, $start, $length)
379
	{
380
		return iconv_substr($string, $start, $length);
381
	}
382
383
	/**
384
	 * Returns true if char at position equals a particular char.
385
	 * @param mixed $string
386
	 * @param mixed $pos
387
	 * @param mixed $char
388
	 */
389
	private function charEqual($string, $pos, $char)
390
	{
391
		return $this->charAt($string, $pos) == $char;
392
	}
393
394
	/**
395
	 * Gets integer from part of a string, allows integers of any length.
396
	 * @param string $str string to retrieve the integer from.
397
	 * @param int $i starting position
398
	 * @param int $minlength minimum integer length
399
	 * @param int $maxlength maximum integer length
400
	 * @return string integer portion of the string, null otherwise
401
	 */
402
	private function getInteger($str, $i, $minlength, $maxlength)
403
	{
404
		//match for digits backwards
405
		for ($x = $maxlength; $x >= $minlength; $x--) {
406
			$token = $this->substring($str, $i, $x);
407
			if ($this->length($token) < $minlength) {
408
				return null;
409
			}
410
			if (preg_match('/^\d+$/', $token)) {
411
				return $token;
412
			}
413
		}
414
		return null;
415
	}
416
}
417