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