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 © 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