1: <?php
2: /**
3: * CTimestamp class file.
4: *
5: * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CTimestamp represents a timestamp.
13: *
14: * Part of this class was adapted from the ADOdb Date Library
15: * {@link http://phplens.com/phpeverywhere/ ADOdb abstraction library}.
16: * The original source code was released under both BSD and GNU Lesser GPL
17: * library license, with the following copyright notice:
18: * Copyright (c) 2000, 2001, 2002, 2003, 2004 John Lim
19: * All rights reserved.
20: *
21: * This class is provided to support UNIX timestamp that is beyond the range
22: * of 1901-2038 on Unix and1970-2038 on Windows. Except {@link getTimestamp},
23: * all other methods in this class can work with the extended timestamp range.
24: * For {@link getTimestamp}, because it is merely a wrapper of
25: * {@link mktime http://php.net/manual/en/function.mktime.php}, it may still
26: * be subject to the limit of timestamp range on certain platforms. Please refer
27: * to the PHP manual for more information.
28: *
29: * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
30: * @package system.utils
31: * @since 1.0
32: */
33: class CTimestamp
34: {
35: /**
36: * Gets day of week, 0 = Sunday,... 6=Saturday.
37: * Algorithm from PEAR::Date_Calc
38: * @param integer $year year
39: * @param integer $month month
40: * @param integer $day day
41: * @return integer day of week
42: */
43: public static function getDayofWeek($year, $month, $day)
44: {
45: /*
46: Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
47: proclaimed that from that time onwards 3 days would be dropped from the calendar
48: every 400 years.
49:
50: Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
51: */
52: if ($year <= 1582)
53: {
54: if ($year < 1582 ||
55: ($year == 1582 && ($month < 10 || ($month == 10 && $day < 15))))
56: {
57: $greg_correction = 3;
58: }
59: else
60: {
61: $greg_correction = 0;
62: }
63: }
64: else
65: {
66: $greg_correction = 0;
67: }
68:
69: if($month > 2)
70: $month -= 2;
71: else
72: {
73: $month += 10;
74: $year--;
75: }
76:
77: $day = floor((13 * $month - 1) / 5) +
78: $day + ($year % 100) +
79: floor(($year % 100) / 4) +
80: floor(($year / 100) / 4) - 2 *
81: floor($year / 100) + 77 + $greg_correction;
82:
83: return $day - 7 * floor($day / 7);
84: }
85:
86: /**
87: * Checks for leap year, returns true if it is. No 2-digit year check. Also
88: * handles julian calendar correctly.
89: * @param integer $year year to check
90: * @return boolean true if is leap year
91: */
92: public static function isLeapYear($year)
93: {
94: $year = self::digitCheck($year);
95: if ($year % 4 != 0)
96: return false;
97:
98: if ($year % 400 == 0)
99: return true;
100: // if gregorian calendar (>1582), century not-divisible by 400 is not leap
101: elseif ($year > 1582 && $year % 100 == 0 )
102: return false;
103: return true;
104: }
105:
106: /**
107: * Fix 2-digit years. Works for any century.
108: * Assumes that if 2-digit is more than 30 years in future, then previous century.
109: * @param integer $y year
110: * @return integer change two digit year into multiple digits
111: */
112: protected static function digitCheck($y)
113: {
114: if ($y < 100){
115: $yr = (integer) date("Y");
116: $century = (integer) ($yr /100);
117:
118: if ($yr%100 > 50) {
119: $c1 = $century + 1;
120: $c0 = $century;
121: } else {
122: $c1 = $century;
123: $c0 = $century - 1;
124: }
125: $c1 *= 100;
126: // if 2-digit year is less than 30 years in future, set it to this century
127: // otherwise if more than 30 years in future, then we set 2-digit year to the prev century.
128: if (($y + $c1) < $yr+30) $y = $y + $c1;
129: else $y = $y + $c0*100;
130: }
131: return $y;
132: }
133:
134: /**
135: * Returns 4-digit representation of the year.
136: * @param integer $y year
137: * @return integer 4-digit representation of the year
138: */
139: public static function get4DigitYear($y)
140: {
141: return self::digitCheck($y);
142: }
143:
144: /**
145: * @return integer get local time zone offset from GMT
146: */
147: public static function getGMTDiff()
148: {
149: static $TZ;
150: if (isset($TZ)) return $TZ;
151:
152: $TZ = mktime(0,0,0,1,2,1970) - gmmktime(0,0,0,1,2,1970);
153: return $TZ;
154: }
155:
156: /**
157: * Returns the getdate() array.
158: * @param integer|boolean $d original date timestamp. False to use the current timestamp.
159: * @param boolean $fast false to compute the day of the week, default is true
160: * @param boolean $gmt true to calculate the GMT dates
161: * @return array an array with date info.
162: */
163: public static function getDate($d=false,$fast=false,$gmt=false)
164: {
165: if($d===false)
166: $d=time();
167: if($gmt)
168: {
169: $tz = date_default_timezone_get();
170: date_default_timezone_set('GMT');
171: $result = getdate($d);
172: date_default_timezone_set($tz);
173: }
174: else
175: {
176: $result = getdate($d);
177: }
178: return $result;
179: }
180:
181: /**
182: * Checks to see if the year, month, day are valid combination.
183: * @param integer $y year
184: * @param integer $m month
185: * @param integer $d day
186: * @return boolean true if valid date, semantic check only.
187: */
188: public static function isValidDate($y,$m,$d)
189: {
190: return checkdate($m, $d, $y);
191: }
192:
193: /**
194: * Checks to see if the hour, minute and second are valid.
195: * @param integer $h hour
196: * @param integer $m minute
197: * @param integer $s second
198: * @param boolean $hs24 whether the hours should be 0 through 23 (default) or 1 through 12.
199: * @return boolean true if valid date, semantic check only.
200: */
201: public static function isValidTime($h,$m,$s,$hs24=true)
202: {
203: if($hs24 && ($h < 0 || $h > 23) || !$hs24 && ($h < 1 || $h > 12)) return false;
204: if($m > 59 || $m < 0) return false;
205: if($s > 59 || $s < 0) return false;
206: return true;
207: }
208:
209: /**
210: * Formats a timestamp to a date string.
211: * @param string $fmt format pattern
212: * @param integer|boolean $d timestamp
213: * @param boolean $is_gmt whether this is a GMT timestamp
214: * @return string formatted date based on timestamp $d
215: */
216: public static function formatDate($fmt,$d=false,$is_gmt=false)
217: {
218: if ($d === false)
219: return ($is_gmt)? @gmdate($fmt): @date($fmt);
220:
221: // check if number in 32-bit signed range
222: if ((abs($d) <= 0x7FFFFFFF))
223: {
224: // if windows, must be +ve integer
225: if ($d >= 0)
226: return ($is_gmt)? @gmdate($fmt,$d): @date($fmt,$d);
227: }
228:
229: $_day_power = 86400;
230:
231: $arr = self::getDate($d,true,$is_gmt);
232:
233: $year = $arr['year'];
234: $month = $arr['mon'];
235: $day = $arr['mday'];
236: $hour = $arr['hours'];
237: $min = $arr['minutes'];
238: $secs = $arr['seconds'];
239:
240: $max = strlen($fmt);
241: $dates = '';
242:
243: /*
244: at this point, we have the following integer vars to manipulate:
245: $year, $month, $day, $hour, $min, $secs
246: */
247: for ($i=0; $i < $max; $i++)
248: {
249: switch($fmt[$i])
250: {
251: case 'T': $dates .= date('T');break;
252: // YEAR
253: case 'L': $dates .= $arr['leap'] ? '1' : '0'; break;
254: case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
255:
256: // 4.3.11 uses '04 Jun 2004'
257: // 4.3.8 uses ' 4 Jun 2004'
258: $dates .= gmdate('D',$_day_power*(3+self::getDayOfWeek($year,$month,$day))).', '
259: . ($day<10?'0'.$day:$day) . ' '.date('M',mktime(0,0,0,$month,2,1971)).' '.$year.' ';
260:
261: if ($hour < 10) $dates .= '0'.$hour; else $dates .= $hour;
262:
263: if ($min < 10) $dates .= ':0'.$min; else $dates .= ':'.$min;
264:
265: if ($secs < 10) $dates .= ':0'.$secs; else $dates .= ':'.$secs;
266:
267: $gmt = self::getGMTDiff();
268: $dates .= sprintf(' %s%04d',($gmt<=0)?'+':'-',abs($gmt)/36);
269: break;
270:
271: case 'Y': $dates .= $year; break;
272: case 'y': $dates .= substr($year,strlen($year)-2,2); break;
273: // MONTH
274: case 'm': if ($month<10) $dates .= '0'.$month; else $dates .= $month; break;
275: case 'Q': $dates .= ($month+3)>>2; break;
276: case 'n': $dates .= $month; break;
277: case 'M': $dates .= date('M',mktime(0,0,0,$month,2,1971)); break;
278: case 'F': $dates .= date('F',mktime(0,0,0,$month,2,1971)); break;
279: // DAY
280: case 't': $dates .= $arr['ndays']; break;
281: case 'z': $dates .= $arr['yday']; break;
282: case 'w': $dates .= self::getDayOfWeek($year,$month,$day); break;
283: case 'l': $dates .= gmdate('l',$_day_power*(3+self::getDayOfWeek($year,$month,$day))); break;
284: case 'D': $dates .= gmdate('D',$_day_power*(3+self::getDayOfWeek($year,$month,$day))); break;
285: case 'j': $dates .= $day; break;
286: case 'd': if ($day<10) $dates .= '0'.$day; else $dates .= $day; break;
287: case 'S':
288: $d10 = $day % 10;
289: if ($d10 == 1) $dates .= 'st';
290: elseif ($d10 == 2 && $day != 12) $dates .= 'nd';
291: elseif ($d10 == 3) $dates .= 'rd';
292: else $dates .= 'th';
293: break;
294:
295: // HOUR
296: case 'Z':
297: $dates .= ($is_gmt) ? 0 : -self::getGMTDiff(); break;
298: case 'O':
299: $gmt = ($is_gmt) ? 0 : self::getGMTDiff();
300:
301: $dates .= sprintf('%s%04d',($gmt<=0)?'+':'-',abs($gmt)/36);
302: break;
303:
304: case 'H':
305: if ($hour < 10) $dates .= '0'.$hour;
306: else $dates .= $hour;
307: break;
308: case 'h':
309: if ($hour > 12) $hh = $hour - 12;
310: else {
311: if ($hour == 0) $hh = '12';
312: else $hh = $hour;
313: }
314:
315: if ($hh < 10) $dates .= '0'.$hh;
316: else $dates .= $hh;
317: break;
318:
319: case 'G':
320: $dates .= $hour;
321: break;
322:
323: case 'g':
324: if ($hour > 12) $hh = $hour - 12;
325: else {
326: if ($hour == 0) $hh = '12';
327: else $hh = $hour;
328: }
329: $dates .= $hh;
330: break;
331: // MINUTES
332: case 'i': if ($min < 10) $dates .= '0'.$min; else $dates .= $min; break;
333: // SECONDS
334: case 'U': $dates .= $d; break;
335: case 's': if ($secs < 10) $dates .= '0'.$secs; else $dates .= $secs; break;
336: // AM/PM
337: // Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
338: case 'a':
339: if ($hour>=12) $dates .= 'pm';
340: else $dates .= 'am';
341: break;
342: case 'A':
343: if ($hour>=12) $dates .= 'PM';
344: else $dates .= 'AM';
345: break;
346: default:
347: $dates .= $fmt[$i]; break;
348: // ESCAPE
349: case "\\":
350: $i++;
351: if ($i < $max) $dates .= $fmt[$i];
352: break;
353: }
354: }
355: return $dates;
356: }
357:
358: /**
359: * Generates a timestamp.
360: * This is the same as the PHP function {@link mktime http://php.net/manual/en/function.mktime.php}.
361: * @param integer $hr hour
362: * @param integer $min minute
363: * @param integer $sec second
364: * @param integer|boolean $mon month
365: * @param integer|boolean $day day
366: * @param integer|boolean $year year
367: * @param boolean $is_gmt whether this is GMT time. If true, gmmktime() will be used.
368: * @return integer|float a timestamp given a local time.
369: */
370: public static function getTimestamp($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_gmt=false)
371: {
372: if ($mon === false)
373: return $is_gmt? @gmmktime($hr,$min,$sec): @mktime($hr,$min,$sec);
374: return $is_gmt ? @gmmktime($hr,$min,$sec,$mon,$day,$year) : @mktime($hr,$min,$sec,$mon,$day,$year);
375: }
376: }
377: