Completed
Branch master (4dc390)
by Fabio
30:44
created

TSimpleDateFormatter::charEqual()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 3
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-2015 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
9
 * @package System.Util
10
 */
11
12
/**
13
 * TSimpleDateFormatter class.
14
 *
15
 * Formats and parses dates using the SimpleDateFormat pattern.
16
 * This pattern is compatible with the I18N and java's SimpleDateFormatter.
17
 * <code>
18
 * Pattern |      Description
19
 * ----------------------------------------------------
20
 * d       | Day of month 1 to 31, no padding
21
 * dd      | Day of monath 01 to 31, zero leading
22
 * M       | Month digit 1 to 12, no padding
23
 * MM      | Month digit 01 to 12, zero leading
24
 * yy      | 2 year digit, e.g., 96, 05
25
 * yyyy    | 4 year digit, e.g., 2005
26
 * ----------------------------------------------------
27
 * </code>
28
 *
29
 * Usage example, to format a date
30
 * <code>
31
 * $formatter = new TSimpleDateFormatter("dd/MM/yyy");
32
 * echo $formatter->format(time());
33
 * </code>
34
 *
35
 * To parse the date string into a date timestamp.
36
 * <code>
37
 * $formatter = new TSimpleDateFormatter("d-M-yyy");
38
 * echo $formatter->parse("24-6-2005");
39
 * </code>
40
 *
41
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
42
 * @package System.Util
43
 * @since 3.0
44
 */
45
class TSimpleDateFormatter
46
{
47
	/**
48
	 * Formatting pattern.
49
	 * @var string
50
	 */
51
	private $pattern;
52
53
	/**
54
	 * Charset, default is 'UTF-8'
55
	 * @var string
56
	 */
57
	private $charset = 'UTF-8';
58
59
	/**
60
	 * Constructor, create a new date time formatter.
61
	 * @param string formatting pattern.
62
	 * @param string pattern and value charset
63
	 */
64
	public function __construct($pattern, $charset='UTF-8')
65
	{
66
		$this->setPattern($pattern);
67
		$this->setCharset($charset);
68
	}
69
70
	/**
71
	 * @return string formatting pattern.
72
	 */
73
	public function getPattern()
74
	{
75
		return $this->pattern;
76
	}
77
78
	/**
79
	 * @param string formatting pattern.
80
	 */
81
	public function setPattern($pattern)
82
	{
83
		$this->pattern = $pattern;
84
	}
85
86
	/**
87
	 * @return string formatting charset.
88
	 */
89
	public function getCharset()
90
	{
91
		return $this->charset;
92
	}
93
94
	/**
95
	 * @param string formatting charset.
96
	 */
97
	public function setCharset($charset)
98
	{
99
		$this->charset = $charset;
100
	}
101
102
	/**
103
	 * Format the date according to the pattern.
104
	 * @param string|int the date to format, either integer or a string readable by strtotime.
105
	 * @return string formatted date.
106
	 */
107
	public function format($value)
108
	{
109
		$date = $this->getDate($value);
110
		$bits['yyyy'] = $date['year'];
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...
111
		$bits['yy'] = substr("{$date['year']}", -2);
112
113
		$bits['MM'] = str_pad("{$date['mon']}", 2, '0', STR_PAD_LEFT);
114
		$bits['M'] = $date['mon'];
115
116
		$bits['dd'] = str_pad("{$date['mday']}", 2, '0', STR_PAD_LEFT);
117
		$bits['d'] = $date['mday'];
118
119
		$pattern = preg_replace('/M{3,4}/', 'MM', $this->pattern);
120
		return str_replace(array_keys($bits), $bits, $pattern);
121
	}
122
123
	public function getMonthPattern()
124
	{
125
		if(is_int(strpos($this->pattern, 'MMMM')))
126
			return 'MMMM';
127
		if(is_int(strpos($this->pattern, 'MMM')))
128
			return 'MMM';
129
		if(is_int(strpos($this->pattern, 'MM')))
130
			return 'MM';
131
		if(is_int(strpos($this->pattern, 'M')))
132
			return 'M';
133
		return false;
134
	}
135
136
	public function getDayPattern()
137
	{
138
		if(is_int(strpos($this->pattern, 'dd')))
139
			return 'dd';
140
		if(is_int(strpos($this->pattern, 'd')))
141
			return 'd';
142
		return false;
143
	}
144
145
	public function getYearPattern()
146
	{
147
		if(is_int(strpos($this->pattern, 'yyyy')))
148
			return 'yyyy';
149
		if(is_int(strpos($this->pattern, 'yy')))
150
			return 'yy';
151
		return false;
152
	}
153
154
	public function getDayMonthYearOrdering()
155
	{
156
		$ordering = array();
157
		if(is_int($day= strpos($this->pattern, 'd')))
158
			$ordering['day'] = $day;
159
		if(is_int($month= strpos($this->pattern, 'M')))
160
			$ordering['month'] = $month;
161
		if(is_int($year= strpos($this->pattern, 'yy')))
162
			$ordering['year'] = $year;
163
		asort($ordering);
164
		return array_keys($ordering);
165
	}
166
167
	/**
168
	 * Gets the time stamp from string or integer.
169
	 * @param string|int date to parse
170
	 * @return array date info array
171
	 */
172
	private function getDate($value)
173
	{
174
		$s = Prado::createComponent('System.Util.TDateTimeStamp');
175
		if(is_numeric($value))
176
			return $s->getDate($value);
177
		else
178
			return $s->parseDate($value);
179
	}
180
181
	/**
182
	 * @return boolean true if the given value matches with the date pattern.
183
	 */
184
	public function isValidDate($value)
185
	{
186
		if($value === null) {
187
			return false;
188
		} else {
189
			return $this->parse($value, false) !== null;
190
		}
191
	}
192
193
	/**
194
	 * Parse the string according to the pattern.
195
	 * @param string|int date string or integer to parse
196
	 * @return int date time stamp
197
	 * @throws TInvalidDataValueException if date string is malformed.
198
	 */
199
	public function parse($value,$defaultToCurrentTime=true)
200
	{
201
		if(is_int($value) || is_float($value))
202
			return $value;
203
		else if(!is_string($value))
204
			throw new TInvalidDataValueException('date_to_parse_must_be_string', $value);
205
206
		if(empty($this->pattern)) return time();
207
208
		$date = time();
209
210
		if($this->length(trim($value)) < 1)
211
			return $defaultToCurrentTime ? $date : null;
212
213
		$pattern = $this->pattern;
214
215
		$i_val = 0;
216
		$i_format = 0;
217
		$pattern_length = $this->length($pattern);
218
		$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...
219
		$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...
220
		$x=null; $y=null;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
221
222
223
		if($defaultToCurrentTime)
224
		{
225
			$year = "{$date['year']}";
226
			$month = $date['mon'];
227
			$day = $date['mday'];
228
		}
229
		else
230
		{
231
			$year = null;
232
			$month = null;
233
			$day = null;
234
		}
235
236
		while ($i_format < $pattern_length)
237
		{
238
			$c = $this->charAt($pattern,$i_format);
239
			$token='';
240
			while ($this->charEqual($pattern, $i_format, $c)
241
						&& ($i_format < $pattern_length))
242
			{
243
				$token .= $this->charAt($pattern, $i_format++);
244
			}
245
246
			if ($token=='yyyy' || $token=='yy' || $token=='y')
247
			{
248
				if ($token=='yyyy') { $x=4;$y=4; }
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
249
				if ($token=='yy')   { $x=2;$y=2; }
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
250
				if ($token=='y')    { $x=2;$y=4; }
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
251
				$year = $this->getInteger($value,$i_val,$x,$y);
252
				if($year === null)
253
					return null;
254
					//throw new TInvalidDataValueException('Invalid year', $value);
255
				$i_val += strlen($year);
256
				if(strlen($year) == 2)
257
				{
258
					$iYear = (int)$year;
259
					if($iYear > 70)
260
						$year = $iYear + 1900;
261
					else
262
						$year = $iYear + 2000;
263
				}
264
				$year = (int)$year;
265
			}
266
			elseif($token=='MM' || $token=='M')
267
			{
268
				$month=$this->getInteger($value,$i_val,
269
									$this->length($token),2);
270
				$iMonth = (int)$month;
271
				if($month === null || $iMonth < 1 || $iMonth > 12 )
272
					return null;
273
					//throw new TInvalidDataValueException('Invalid month', $value);
274
				$i_val += strlen($month);
275
				$month = $iMonth;
276
			}
277
			elseif ($token=='dd' || $token=='d')
278
			{
279
				$day = $this->getInteger($value,$i_val,
280
									$this->length($token), 2);
281
				$iDay = (int)$day;
282
				if($day === null || $iDay < 1 || $iDay >31)
283
					return null;
284
					//throw new TInvalidDataValueException('Invalid day', $value);
285
				$i_val += strlen($day);
286
				$day = $iDay;
287
			}
288
			else
289
			{
290
				if($this->substring($value, $i_val, $this->length($token)) != $token)
291
					return null;
292
					//throw new TInvalidDataValueException("Subpattern '{$this->pattern}' mismatch", $value);
293
				else
294
					$i_val += $this->length($token);
295
			}
296
		}
297
		if ($i_val != $this->length($value))
298
			return null;
299
			//throw new TInvalidDataValueException("Pattern '{$this->pattern}' mismatch", $value);
300
		if(!$defaultToCurrentTime && ($month === null || $day === null || $year === null))
301
			return null;
302
		else
303
		{
304
			if(empty($year)) {
305
				$year = date('Y');
306
			}
307
			$day = (int)$day <= 0 ? 1 : (int)$day;
308
			$month = (int)$month <= 0 ? 1 : (int)$month;
309
			$s = Prado::createComponent('System.Util.TDateTimeStamp');
310
			return $s->getTimeStamp(0, 0, 0, $month, $day, $year);
311
		}
312
	}
313
314
	/**
315
	 * Calculate the length of a string, may be consider iconv_strlen?
316
	 */
317
	private function length($string)
318
	{
319
		//use iconv_strlen or just strlen?
320
		return strlen($string);
321
	}
322
323
	/**
324
	 * Get the char at a position.
325
	 */
326
	private function charAt($string, $pos)
327
	{
328
		return $this->substring($string, $pos, 1);
329
	}
330
331
	/**
332
	 * Gets a portion of a string, uses iconv_substr.
333
	 */
334
	private function substring($string, $start, $length)
335
	{
336
		return iconv_substr($string, $start, $length);
337
	}
338
339
	/**
340
	 * Returns true if char at position equals a particular char.
341
	 */
342
	private function charEqual($string, $pos, $char)
343
	{
344
		return $this->charAt($string, $pos) == $char;
345
	}
346
347
	/**
348
	 * Gets integer from part of a string, allows integers of any length.
349
	 * @param string string to retrieve the integer from.
350
	 * @param int starting position
351
	 * @param int minimum integer length
352
	 * @param int maximum integer length
353
	 * @return string integer portion of the string, null otherwise
354
	 */
355
	private function getInteger($str,$i,$minlength,$maxlength)
356
	{
357
		//match for digits backwards
358
		for ($x = $maxlength; $x >= $minlength; $x--)
359
		{
360
			$token= $this->substring($str, $i,$x);
361
			if ($this->length($token) < $minlength)
362
				return null;
363
			if (preg_match('/^\d+$/', $token))
364
				return $token;
365
		}
366
		return null;
367
	}
368
}
369
370