1<?php 2/** 3 * Jalali (Shamsi) DateTime Class. Supports years higher than 2038. 4 * 5 * Copyright (c) 2012 Sallar Kaboli <sallar.kaboli@gmail.com> 6 * http://sallar.me 7 * 8 * The MIT License (MIT) 9 * 10 * Permission is hereby granted, free of charge, to any person obtaining a 11 * copy of this software and associated documentation files (the "Software"), 12 * to deal in the Software without restriction, including without limitation 13 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 * and/or sell copies of the Software, and to permit persons to whom the 15 * Software is furnished to do so, subject to the following conditions: 16 * 17 * 1- The above copyright notice and this permission notice shall be included 18 * in all copies or substantial portions of the Software. 19 * 20 * 2- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 * DEALINGS IN THE SOFTWARE. 27 * 28 * Original Jalali to Gregorian (and vice versa) convertor: 29 * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi 30 * 31 * List of supported timezones can be found here: 32 * http://www.php.net/manual/en/timezones.php 33 * 34 * 35 * @package jDateTime 36 * @author Sallar Kaboli <sallar.kaboli@gmail.com> 37 * @author Omid Pilevar <omid.pixel@gmail.com> 38 * @copyright 2003-2012 Sallar Kaboli 39 * @license http://opensource.org/licenses/mit-license.php The MIT License 40 * @link https://github.com/sallar/jDateTime 41 * @see DateTime 42 * @version 2.2.0 43 */ 44class jDateTime 45{ 46 47 /** 48 * Defaults 49 */ 50 private static $jalali = true; //Use Jalali Date, If set to false, falls back to gregorian 51 private static $convert = true; //Convert numbers to Farsi characters in utf-8 52 private static $timezone = null; //Timezone String e.g Asia/Tehran, Defaults to Server Timezone Settings 53 private static $temp = array(); 54 55 /** 56 * jDateTime::Constructor 57 * 58 * Pass these parameteres when creating a new instance 59 * of this Class, and they will be used as defaults. 60 * e.g $obj = new jDateTime(false, true, 'Asia/Tehran'); 61 * To use system defaults pass null for each one or just 62 * create the object without any parameters. 63 * 64 * @author Sallar Kaboli 65 * @param $convert bool Converts numbers to Farsi 66 * @param $jalali bool Converts date to Jalali 67 * @param $timezone string Timezone string 68 */ 69 public function __construct($convert = null, $jalali = null, $timezone = null) 70 { 71 if ( $jalali !== null ) self::$jalali = (bool) $jalali; 72 if ( $convert !== null ) self::$convert = (bool) $convert; 73 if ( $timezone !== null ) self::$timezone = $timezone; 74 } 75 76 /** 77 * jDateTime::Date 78 * 79 * Formats and returns given timestamp just like php's 80 * built in date() function. 81 * e.g: 82 * $obj->date("Y-m-d H:i", time()); 83 * $obj->date("Y-m-d", time(), false, false, 'America/New_York'); 84 * 85 * @author Sallar Kaboli 86 * @param $format string Acceps format string based on: php.net/date 87 * @param $stamp int Unix Timestamp (Epoch Time) 88 * @param $convert bool (Optional) forces convert action. pass null to use system default 89 * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default 90 * @param $timezone string (Optional) forces a different timezone. pass null to use system default 91 * @return string Formatted input 92 */ 93 public static function date($format, $stamp = false, $convert = null, $jalali = null, $timezone = null) 94 { 95 //Timestamp + Timezone 96 $stamp = ($stamp !== false) ? $stamp : time(); 97 $timezone = ($timezone != null) ? $timezone : ((self::$timezone != null) ? self::$timezone : date_default_timezone_get()); 98 $obj = new DateTime('@' . $stamp, new DateTimeZone($timezone)); 99 $obj->setTimezone(new DateTimeZone($timezone)); 100 101 if ( (self::$jalali === false && $jalali === null) || $jalali === false ) { 102 return $obj->format($format); 103 } 104 else { 105 106 //Find what to replace 107 $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array(); 108 109 //Intact Keys 110 $intact = array('B','h','H','g','G','i','s','I','U','u','Z','O','P'); 111 $intact = self::filterArray($chars, $intact); 112 $intactValues = array(); 113 114 foreach ($intact as $k => $v) { 115 $intactValues[$k] = $obj->format($v); 116 } 117 //End Intact Keys 118 119 120 //Changed Keys 121 list($year, $month, $day) = array($obj->format('Y'), $obj->format('n'), $obj->format('j')); 122 list($jyear, $jmonth, $jday) = self::toJalali($year, $month, $day); 123 124 $keys = array('d','D','j','l','N','S','w','z','W','F','m','M','n','t','L','o','Y','y','a','A','c','r','e','T'); 125 $keys = self::filterArray($chars, $keys, array('z')); 126 $values = array(); 127 128 foreach ($keys as $k => $key) { 129 130 $v = ''; 131 switch ($key) { 132 //Day 133 case 'd': 134 $v = sprintf('%02d', $jday); 135 break; 136 case 'D': 137 $v = self::getDayNames($obj->format('D'), true); 138 break; 139 case 'j': 140 $v = $jday; 141 break; 142 case 'l': 143 $v = self::getDayNames($obj->format('l')); 144 break; 145 case 'N': 146 $v = self::getDayNames($obj->format('l'), false, 1, true); 147 break; 148 case 'S': 149 $v = 'ام'; 150 break; 151 case 'w': 152 $v = self::getDayNames($obj->format('l'), false, 1, true) - 1; 153 break; 154 case 'z': 155 if ($jmonth > 6) { 156 $v = 186 + (($jmonth - 6 - 1) * 30) + $jday; 157 } 158 else { 159 $v = (($jmonth - 1) * 31) + $jday; 160 } 161 self::$temp['z'] = $v; 162 break; 163 //Week 164 case 'W': 165 $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1); 166 break; 167 //Month 168 case 'F': 169 $v = self::getMonthNames($jmonth); 170 break; 171 case 'm': 172 $v = sprintf('%02d', $jmonth); 173 break; 174 case 'M': 175 $v = self::getMonthNames($jmonth, true); 176 break; 177 case 'n': 178 $v = $jmonth; 179 break; 180 case 't': 181 if ($jmonth>=1 && $jmonth<=6) $v=31; 182 else if ($jmonth>=7 && $jmonth<=11) $v=30; 183 else if($jmonth==12 && $jyear % 4 ==3) $v=30; 184 else if ($jmonth==12 && $jyear % 4 !=3) $v=29; 185 break; 186 //Year 187 case 'L': 188 $tmpObj = new DateTime('@'.(time()-31536000)); 189 $v = $tmpObj->format('L'); 190 break; 191 case 'o': 192 case 'Y': 193 $v = $jyear; 194 break; 195 case 'y': 196 $v = $jyear % 100; 197 break; 198 //Time 199 case 'a': 200 $v = ($obj->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ'; 201 break; 202 case 'A': 203 $v = ($obj->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر'; 204 break; 205 //Full Dates 206 case 'c': 207 $v = $jyear.'-'.sprintf('%02d', $jmonth).'-'.sprintf('%02d', $jday).'T'; 208 $v .= $obj->format('H').':'.$obj->format('i').':'.$obj->format('s').$obj->format('P'); 209 break; 210 case 'r': 211 $v = self::getDayNames($obj->format('D'), true).', '.sprintf('%02d', $jday).' '.self::getMonthNames($jmonth, true); 212 $v .= ' '.$jyear.' '.$obj->format('H').':'.$obj->format('i').':'.$obj->format('s').' '.$obj->format('P'); 213 break; 214 //Timezone 215 case 'e': 216 $v = $obj->format('e'); 217 break; 218 case 'T': 219 $v = $obj->format('T'); 220 break; 221 222 } 223 $values[$k] = $v; 224 225 } 226 //End Changed Keys 227 228 //Merge 229 $keys = array_merge($intact, $keys); 230 $values = array_merge($intactValues, $values); 231 232 //Return 233 $ret = strtr($format, array_combine($keys, $values)); 234 return 235 ($convert === false || 236 ($convert === null && self::$convert === false) || 237 ( $jalali === false || $jalali === null && self::$jalali === false )) 238 ? $ret : self::convertNumbers($ret); 239 240 } 241 242 } 243 244 /** 245 * jDateTime::gDate 246 * 247 * Same as jDateTime::Date method 248 * but this one works as a helper and returns Gregorian Date 249 * in case someone doesn't like to pass all those false arguments 250 * to Date method. 251 * 252 * e.g. $obj->gDate("Y-m-d") //Outputs: 2011-05-05 253 * $obj->date("Y-m-d", false, false, false); //Outputs: 2011-05-05 254 * Both return the exact same result. 255 * 256 * @author Sallar Kaboli 257 * @param $format string Acceps format string based on: php.net/date 258 * @param $stamp int Unix Timestamp (Epoch Time) 259 * @param $timezone string (Optional) forces a different timezone. pass null to use system default 260 * @return string Formatted input 261 */ 262 public static function gDate($format, $stamp = false, $timezone = null) 263 { 264 return self::date($format, $stamp, false, false, $timezone); 265 } 266 267 /** 268 * jDateTime::Strftime 269 * 270 * Format a local time/date according to locale settings 271 * built in strftime() function. 272 * e.g: 273 * $obj->strftime("%x %H", time()); 274 * $obj->strftime("%H", time(), false, false, 'America/New_York'); 275 * 276 * @author Omid Pilevar 277 * @param $format string Acceps format string based on: php.net/date 278 * @param $stamp int Unix Timestamp (Epoch Time) 279 * @param $convert bool (Optional) forces convert action. pass null to use system default 280 * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default 281 * @param $timezone string (Optional) forces a different timezone. pass null to use system default 282 * @return string Formatted input 283 */ 284 public static function strftime($format, $stamp = false, $convert = null, $jalali = null, $timezone = null) 285 { 286 $str_format_code = array( 287 '%a', '%A', '%d', '%e', '%j', '%u', '%w', 288 '%U', '%V', '%W', 289 '%b', '%B', '%h', '%m', 290 '%C', '%g', '%G', '%y', '%Y', 291 '%H', '%I', '%l', '%M', '%p', '%P', '%r', '%R', '%S', '%T', '%X', '%z', '%Z', 292 '%c', '%D', '%F', '%s', '%x', 293 '%n', '%t', '%%' 294 ); 295 296 $date_format_code = array( 297 'D', 'l', 'd', 'j', 'z', 'N', 'w', 298 'W', 'W', 'W', 299 'M', 'F', 'M', 'm', 300 'y', 'y', 'y', 'y', 'Y', 301 'H', 'h', 'g', 'i', 'A', 'a', 'h:i:s A', 'H:i', 's', 'H:i:s', 'h:i:s', 'H', 'H', 302 'D j M H:i:s', 'd/m/y', 'Y-m-d', 'U', 'd/m/y', 303 '\n', '\t', '%' 304 ); 305 306 //Change Strftime format to Date format 307 $format = str_replace($str_format_code, $date_format_code, $format); 308 309 //Convert to date 310 return self::date($format, $stamp, $convert, $jalali, $timezone); 311 } 312 313 /** 314 * jDateTime::Mktime 315 * 316 * Creates a Unix Timestamp (Epoch Time) based on given parameters 317 * works like php's built in mktime() function. 318 * e.g: 319 * $time = $obj->mktime(0,0,0,2,10,1368); 320 * $obj->date("Y-m-d", $time); //Format and Display 321 * $obj->date("Y-m-d", $time, false, false); //Display in Gregorian ! 322 * 323 * You can force gregorian mktime if system default is jalali and you 324 * need to create a timestamp based on gregorian date 325 * $time2 = $obj->mktime(0,0,0,12,23,1989, false); 326 * 327 * @author Sallar Kaboli 328 * @param $hour int Hour based on 24 hour system 329 * @param $minute int Minutes 330 * @param $second int Seconds 331 * @param $month int Month Number 332 * @param $day int Day Number 333 * @param $year int Four-digit Year number eg. 1390 334 * @param $jalali bool (Optional) pass false if you want to input gregorian time 335 * @param $timezone string (Optional) acceps an optional timezone if you want one 336 * @return int Unix Timestamp (Epoch Time) 337 */ 338 public static function mktime($hour, $minute, $second, $month, $day, $year, $jalali = null, $timezone = null) 339 { 340 //Defaults 341 $month = (intval($month) == 0) ? self::date('m') : $month; 342 $day = (intval($day) == 0) ? self::date('d') : $day; 343 $year = (intval($year) == 0) ? self::date('Y') : $year; 344 345 //Convert to Gregorian if necessary 346 if ( $jalali === true || ($jalali === null && self::$jalali === true) ) { 347 list($year, $month, $day) = self::toGregorian($year, $month, $day); 348 } 349 350 //Create a new object and set the timezone if available 351 $date = $year.'-'.sprintf('%02d', $month).'-'.sprintf('%02d', $day).' '.$hour.':'.$minute.':'.$second; 352 353 if ( self::$timezone != null || $timezone != null ) { 354 $obj = new DateTime($date, new DateTimeZone(($timezone != null) ? $timezone : self::$timezone)); 355 } 356 else { 357 $obj = new DateTime($date); 358 } 359 360 //Return 361 return $obj->format('U'); 362 } 363 364 /** 365 * jDateTime::Checkdate 366 * 367 * Checks the validity of the date formed by the arguments. 368 * A date is considered valid if each parameter is properly defined. 369 * works like php's built in checkdate() function. 370 * Leap years are taken into consideration. 371 * e.g: 372 * $obj->checkdate(10, 21, 1390); // Return true 373 * $obj->checkdate(9, 31, 1390); // Return false 374 * 375 * You can force gregorian checkdate if system default is jalali and you 376 * need to check based on gregorian date 377 * $check = $obj->checkdate(12, 31, 2011, false); 378 * 379 * @author Omid Pilevar 380 * @param $month int The month is between 1 and 12 inclusive. 381 * @param $day int The day is within the allowed number of days for the given month. 382 * @param $year int The year is between 1 and 32767 inclusive. 383 * @param $jalali bool (Optional) pass false if you want to input gregorian time 384 * @return bool 385 */ 386 public static function checkdate($month, $day, $year, $jalali = null) 387 { 388 //Defaults 389 $month = (intval($month) == 0) ? self::date('n') : intval($month); 390 $day = (intval($day) == 0) ? self::date('j') : intval($day); 391 $year = (intval($year) == 0) ? self::date('Y') : intval($year); 392 393 //Check if its jalali date 394 if ( $jalali === true || ($jalali === null && self::$jalali === true) ) 395 { 396 $epoch = self::mktime(0, 0, 0, $month, $day, $year); 397 398 if( self::date('Y-n-j', $epoch,false) == "$year-$month-$day" ) { 399 $ret = true; 400 } 401 else{ 402 $ret = false; 403 } 404 } 405 else //Gregorian Date 406 { 407 $ret = checkdate($month, $day, $year); 408 } 409 410 //Return 411 return $ret; 412 } 413 414 /** 415 * System Helpers below 416 * ------------------------------------------------------ 417 */ 418 419 /** 420 * Filters out an array 421 */ 422 private static function filterArray($needle, $heystack, $always = array()) 423 { 424 return array_intersect(array_merge($needle, $always), $heystack); 425 } 426 427 /** 428 * Returns correct names for week days 429 */ 430 private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false) 431 { 432 $days = array( 433 'sat' => array(1, 'شنبه'), 434 'sun' => array(2, 'یکشنبه'), 435 'mon' => array(3, 'دوشنبه'), 436 'tue' => array(4, 'سه شنبه'), 437 'wed' => array(5, 'چهارشنبه'), 438 'thu' => array(6, 'پنجشنبه'), 439 'fri' => array(7, 'جمعه') 440 ); 441 442 $day = substr(strtolower($day), 0, 3); 443 $day = $days[$day]; 444 445 return ($numeric) ? $day[0] : (($shorten) ? self::substr($day[1], 0, $len) : $day[1]); 446 } 447 448 /** 449 * Returns correct names for months 450 */ 451 private static function getMonthNames($month, $shorten = false, $len = 3) 452 { 453 // Convert 454 $months = array( 455 'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند' 456 ); 457 $ret = $months[$month - 1]; 458 459 // Return 460 return ($shorten) ? self::substr($ret, 0, $len) : $ret; 461 } 462 463 /** 464 * Converts latin numbers to farsi script 465 */ 466 private static function convertNumbers($matches) 467 { 468 $farsi_array = array('۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'); 469 $english_array = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); 470 471 return str_replace($english_array, $farsi_array, $matches); 472 } 473 474 /** 475 * Division 476 */ 477 private static function div($a, $b) 478 { 479 return (int) ($a / $b); 480 } 481 482 /** 483 * Substring helper 484 */ 485 private static function substr($str, $start, $len) 486 { 487 if( function_exists('mb_substr') ){ 488 return mb_substr($str, $start, $len, 'UTF-8'); 489 } 490 else{ 491 return substr($str, $start, $len * 2); 492 } 493 } 494 495 /** 496 * Gregorian to Jalali Conversion 497 * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi 498 */ 499 public static function toJalali($g_y, $g_m, $g_d) 500 { 501 502 $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 503 $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); 504 505 $gy = $g_y-1600; 506 $gm = $g_m-1; 507 $gd = $g_d-1; 508 509 $g_day_no = 365*$gy+self::div($gy+3, 4)-self::div($gy+99, 100)+self::div($gy+399, 400); 510 511 for ($i=0; $i < $gm; ++$i) 512 $g_day_no += $g_days_in_month[$i]; 513 if ($gm>1 && (($gy%4==0 && $gy%100!=0) || ($gy%400==0))) 514 $g_day_no++; 515 $g_day_no += $gd; 516 517 $j_day_no = $g_day_no-79; 518 519 $j_np = self::div($j_day_no, 12053); 520 $j_day_no = $j_day_no % 12053; 521 522 $jy = 979+33*$j_np+4*self::div($j_day_no, 1461); 523 524 $j_day_no %= 1461; 525 526 if ($j_day_no >= 366) { 527 $jy += self::div($j_day_no-1, 365); 528 $j_day_no = ($j_day_no-1)%365; 529 } 530 531 for ($i = 0; $i < 11 && $j_day_no >= $j_days_in_month[$i]; ++$i) 532 $j_day_no -= $j_days_in_month[$i]; 533 $jm = $i+1; 534 $jd = $j_day_no+1; 535 536 return array($jy, $jm, $jd); 537 538 } 539 540 /** 541 * Jalali to Gregorian Conversion 542 * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi 543 * 544 */ 545 public static function toGregorian($j_y, $j_m, $j_d) 546 { 547 548 $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 549 $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); 550 551 $jy = $j_y-979; 552 $jm = $j_m-1; 553 $jd = $j_d-1; 554 555 $j_day_no = 365*$jy + self::div($jy, 33)*8 + self::div($jy%33+3, 4); 556 for ($i=0; $i < $jm; ++$i) 557 $j_day_no += $j_days_in_month[$i]; 558 559 $j_day_no += $jd; 560 561 $g_day_no = $j_day_no+79; 562 563 $gy = 1600 + 400*self::div($g_day_no, 146097); 564 $g_day_no = $g_day_no % 146097; 565 566 $leap = true; 567 if ($g_day_no >= 36525) { 568 $g_day_no--; 569 $gy += 100*self::div($g_day_no, 36524); 570 $g_day_no = $g_day_no % 36524; 571 572 if ($g_day_no >= 365) 573 $g_day_no++; 574 else 575 $leap = false; 576 } 577 578 $gy += 4*self::div($g_day_no, 1461); 579 $g_day_no %= 1461; 580 581 if ($g_day_no >= 366) { 582 $leap = false; 583 584 $g_day_no--; 585 $gy += self::div($g_day_no, 365); 586 $g_day_no = $g_day_no % 365; 587 } 588 589 for ($i = 0; $g_day_no >= $g_days_in_month[$i] + ($i == 1 && $leap); $i++) 590 $g_day_no -= $g_days_in_month[$i] + ($i == 1 && $leap); 591 $gm = $i+1; 592 $gd = $g_day_no+1; 593 594 return array($gy, $gm, $gd); 595 596 } 597 598} 599