├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Date.php ├── DateAbstract.php └── Jalali.php └── tests └── JalaliTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | test/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 mohsen shafiei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Date 2 | 3 | Fix all needs to date in php 4 | 5 | ## Install 6 | 7 | Add below line to ```require``` section in ```composer.json```: 8 | 9 | ``` 10 | "m-jch/date" : "~1" 11 | ``` 12 | 13 | Run ```composer update``` 14 | 15 | ## Tutorial 16 | 17 | This package contains 2 class, ```Jalali``` and ```Date```. ```Jalali``` class responsible for Jalali (shamsi) date time and ```Date``` class responsible for Gregorian date time. 18 | 19 | All below examples should be run in ```Jalali``` or ```Date``` class. 20 | 21 | All format listed in PHP [```date```](http://php.net/manual/en/function.date.php) function support. 22 | 23 | We want to implements most of Carbon PHP class. 24 | 25 | ### Include Classes 26 | 27 | ```php 28 | use Date\Date; 29 | use Date\Jalali; 30 | ``` 31 | 32 | ### Examples 33 | 34 | #### Creators 35 | 36 | ```php 37 | $date = new Jalali('1395/04/10 23:10:05'); 38 | $date = new Jalali('۱۳۹۵/۰۴/۱۰ ۲۳:۱۰:۰۵'); 39 | $date->format('Y-m-d H:i:s'); 40 | 41 | echo new Jalali('1395-04-10'); 42 | 43 | // You can use make static method instead of new instance 44 | // Below examples has same results 45 | $date = (new Jalali('1395/04/10 23:10:05'))->subDays(4); 46 | $date = Jalali::make('1395/04/10 23:10:05')->subDays(4); 47 | 48 | echo Jalali::now(); 49 | echo Jalali::yesterday(); 50 | echo Jalali::tomorrow(); 51 | 52 | echo Jalali::create(1394, 05, 04, 12, 45, 23); 53 | echo Jalali::createDate(1394, 05, 04); 54 | echo Jalali::createTime(12, 45, 23); 55 | ``` 56 | 57 | #### Converters 58 | 59 | ```php 60 | // Jalali to Gregorian 61 | $date = new Jalali('1373/06/05 23:10:05'); 62 | echo $date->toGregorian(); 63 | 64 | $date = new Jalali('1373/06/05 23:10:05'); 65 | echo $date->tog()->format('Y-m'); // An aliases for toGregorian method 66 | 67 | // Gregorian to Jalali 68 | $date = new Date('2012-06-05 20:05:01'); 69 | echo $date->toJalali(); 70 | 71 | $date = new Date('2012-06-05 20:05:01'); 72 | echo $date->toj(); // An aliases for toJalali method 73 | ``` 74 | 75 | #### Modifiers 76 | 77 | Consider you can send negative number to ```add*``` methods, instead of using ```sub*``` methods. 78 | 79 | ```php 80 | echo Jalali::now()->startOfMonth(); 81 | echo Jalali::now()->endOfMonth(); 82 | 83 | echo Jalali::now()->startOfDay(); 84 | echo Jalali::now()->endOfDay(); 85 | 86 | echo Jalali::now()->addYears(2); 87 | echo Jalali::now()->subYears(1); 88 | 89 | echo Jalali::now()->addMonths(2); 90 | echo Jalali::now()->subMonths(1); 91 | 92 | echo Jalali::now()->addWeeks(3); 93 | echo Date::now()->subWeeks(3); 94 | 95 | echo Jalali::now()->addDays(1); 96 | echo Jalali::now()->subDays(5); 97 | 98 | echo Jalali::now()->addHours(2); 99 | echo Jalali::now()->subHours(1); 100 | 101 | echo Jalali::now()->addMinutes(10); 102 | echo Jalali::now()->subMinutes(5); 103 | 104 | echo Jalali::now()->addSeconds(14); 105 | echo Jalali::now()->subSeconds(50); 106 | ``` 107 | 108 | #### Customize 109 | 110 | ```php 111 | // echo as farsi numbers 112 | echo Jalali::now()->fa()->subDays(4); 113 | echo (new Jalali)->addDays(5)->fa('Y-m-d l'); // Can use just fa() instead of fa()->format() 114 | ``` 115 | 116 | #### Comparisons 117 | 118 | All comparisons based on Gregorian date, so you can compare two date with different type of class. 119 | 120 | ```php 121 | $date1 = new Jalali('1395-07-12'); 122 | $date2 = new Jalali('1395-10-05'); 123 | 124 | $date1->equalTo($date2); 125 | $date1->eq($date2); 126 | 127 | $date1->notEqualTo($date2); 128 | $date1->ne($date2); 129 | 130 | $date1->greaterThan($date2); 131 | $date1->gt($date2); 132 | 133 | $date1->greaterThanOrEqualTo($date2); 134 | $date1->gte($date2); 135 | 136 | $date1->lessThan($date2); 137 | $date1->lt($date2); 138 | 139 | $date1->lessThanOrEqualTo($date2); 140 | $date1->lte($date2); 141 | 142 | $now->between($date1, $date2); 143 | $now->bw($date1, $date2); 144 | 145 | $now->betweenEqual($date1, $date2); 146 | $now->bwe($date1, $date2); 147 | ``` 148 | 149 | #### Differences 150 | 151 | You can not send first parameter or send it null, for calculate difference from current datetime. 152 | 153 | If second parameter is true, return base on absolute, otherwise base on difference, default is true 154 | 155 | We're not sure about ```diffInYears```, ```diffInMonths```, ```diffInWeeks``` and ```diffInDays``` in Jalali date, if you have any bug, please report it. 156 | 157 | ```php 158 | $date1->diffInYears($date2); 159 | $date1->diffInMonths($date2); 160 | $date1->diffInWeeks($date2); 161 | $date1->diffInDays(); 162 | $date1->diffInHours($date2); 163 | $date1->diffInMinutes($date2, true); 164 | $date1->diffInSeconds($date2, false); 165 | ``` 166 | 167 | #### Timestamp 168 | 169 | ```php 170 | // Create from timestamp 171 | $date = new Jalali(1466664181); 172 | $date = new Date(1466664181); 173 | $date = Jalali::createFromTimestamp(1466664181); 174 | 175 | // Get timestamp 176 | $date->getTimestamp(); 177 | $date->format('U'); 178 | 179 | // Set timestamp 180 | $date->setTimestamp(1466664181); 181 | ``` 182 | 183 | ## Frameworks 184 | 185 | ### Laravel5 186 | 187 | Add below codes to ```aliases``` array in ```app.php``` config file. 188 | 189 | ```php 190 | 'Jalali' => Date\Jalali::class, 191 | 'Date' => Date\Date::class 192 | ``` 193 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "m-jch/date", 3 | "description": "Fix all needs to date in php", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "mohsen shafiei", 8 | "email": "mohsen.jch@gmail.com" 9 | } 10 | ], 11 | "require": {}, 12 | "autoload": { 13 | "psr-4": { 14 | "Date\\": "src/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Date.php: -------------------------------------------------------------------------------- 1 | newFromTimestamp($time)) { 12 | parent::__construct(null, self::safeCreateDateTimeZone($tz)); 13 | $this->setTimestamp($time); 14 | } else { 15 | parent::__construct($time, self::safeCreateDateTimeZone($tz)); 16 | } 17 | } 18 | 19 | /** 20 | * Check if class initialize from timestamp 21 | * 22 | * @param mixed $time 23 | * @return bool 24 | */ 25 | protected function newFromTimestamp($time) 26 | { 27 | $timestampRegex = '/\A\d+\z/'; 28 | preg_match($timestampRegex, $time, $output); 29 | if (!empty($output)) { 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | /** 37 | * @return \Date\Jalali 38 | */ 39 | public function toJalali() 40 | { 41 | list($year, $month, $day) = $this->gregorianToJalali($this->format('Y'), $this->format('m'), $this->format('d')); 42 | list($hour, $minute, $second) = array($this->format('H'), $this->format('i'), $this->format('s')); 43 | 44 | return new Jalali("$year-$month-$day $hour:$minute:$second", $this->getTimezone()); 45 | } 46 | 47 | /** 48 | * An aliases for toJalali method 49 | * 50 | * @return \Date\Jalali 51 | */ 52 | public function toj() 53 | { 54 | return $this->toJalali(); 55 | } 56 | 57 | /** 58 | * @param string $name 59 | * @return mixed 60 | */ 61 | public function __get($name) 62 | { 63 | switch (true) { 64 | case array_key_exists($name, $formats = array( 65 | 'year' => 'Y', 66 | 'month' => 'm', 67 | 'day' => 'd', 68 | 'daysInMonth' => 't' 69 | )): 70 | return $this->format($formats[$name]); 71 | break; 72 | } 73 | } 74 | 75 | /** 76 | * Equivalent to new Date() 77 | * 78 | * @return \Date\Date 79 | */ 80 | public static function make($time) 81 | { 82 | return new Date($time); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/DateAbstract.php: -------------------------------------------------------------------------------- 1 | diff($dt, $abs)->format('%r%y'); 30 | } 31 | 32 | /** 33 | * Get the difference in months 34 | * 35 | * @param DateAbstract|null $dt 36 | * @param bool $abs Get the absolute of the difference 37 | * @return int 38 | */ 39 | public function diffInMonths(DateAbstract $dt = null, $abs = true) 40 | { 41 | $dt = $dt ?: static::now(); 42 | 43 | return $this->diffInYears($dt, $abs) * 12 + (int) $this->diff($dt, $abs)->format('%r%m'); 44 | } 45 | 46 | /** 47 | * Get the difference in weeks 48 | * 49 | * @param DateAbstract|null $dt 50 | * @param bool $abs Get the absolute of the difference 51 | * @return int 52 | */ 53 | public function diffInWeeks(DateAbstract $dt = null, $abs = true) 54 | { 55 | return (int) ($this->diffInDays($dt, $abs) / 7); 56 | } 57 | 58 | /** 59 | * Get the difference in days 60 | * 61 | * @param DateAbstract|null $dt 62 | * @param bool $abs Get the absolute of the difference 63 | * @return int 64 | */ 65 | public function diffInDays(DateAbstract $dt = null, $abs = true) 66 | { 67 | $dt = $dt ?: static::now(); 68 | 69 | return (int) $this->diff($dt, $abs)->format('%r%a'); 70 | } 71 | 72 | /** 73 | * Get the difference in hours 74 | * 75 | * @param DateAbstract|null $dt 76 | * @param bool $abs Get the absolute of the difference 77 | * @return int 78 | */ 79 | public function diffInHours(DateAbstract $dt = null, $abs = true) 80 | { 81 | return (int) ($this->diffInSeconds($dt, $abs) / 120); 82 | } 83 | 84 | /** 85 | * Get the difference in minutes 86 | * 87 | * @param DateAbstract|null $dt 88 | * @param bool $abs Get the absolute of the difference 89 | * @return int 90 | */ 91 | public function diffInMinutes(DateAbstract $dt = null, $abs = true) 92 | { 93 | return (int) ($this->diffInSeconds($dt, $abs) / 60); 94 | } 95 | 96 | /** 97 | * Get the difference in seconds 98 | * 99 | * @param DateAbstract|null $dt 100 | * @param bool $abs Get the absolute of the difference 101 | * @return int 102 | */ 103 | public function diffInSeconds(DateAbstract $dt = null, $abs = true) 104 | { 105 | $dt = $dt ?: static::now(); 106 | 107 | $value = $dt->getTimestamp() - $this->getTimestamp(); 108 | return $abs ? abs($value) : $value; 109 | } 110 | 111 | /** 112 | * Determines if the instance is equal to another 113 | * 114 | * @param DateAbstract $dt 115 | * 116 | * @return bool 117 | */ 118 | public function eq(DateAbstract $dt) 119 | { 120 | return $this == $dt; 121 | } 122 | 123 | /** 124 | * Determines if the instance is equal to another 125 | * 126 | * @param DateAbstract $dt 127 | * 128 | * @see eq() 129 | * 130 | * @return bool 131 | */ 132 | public function equalTo(DateAbstract $dt) 133 | { 134 | return $this->eq($dt); 135 | } 136 | 137 | /** 138 | * Determines if the instance is not equal to another 139 | * 140 | * @param DateAbstract $dt 141 | * 142 | * @return bool 143 | */ 144 | public function ne(DateAbstract $dt) 145 | { 146 | return !$this->eq($dt); 147 | } 148 | 149 | /** 150 | * Determines if the instance is not equal to another 151 | * 152 | * @param DateAbstract $dt 153 | * 154 | * @see ne() 155 | * 156 | * @return bool 157 | */ 158 | public function notEqualTo(DateAbstract $dt) 159 | { 160 | return $this->ne($dt); 161 | } 162 | 163 | /** 164 | * Determines if the instance is greater (after) than another 165 | * 166 | * @param DateAbstract $dt 167 | * 168 | * @return bool 169 | */ 170 | public function gt(DateAbstract $dt) 171 | { 172 | return $this > $dt; 173 | } 174 | 175 | /** 176 | * Determines if the instance is greater (after) than another 177 | * 178 | * @param DateAbstract $dt 179 | * 180 | * @see gt() 181 | * 182 | * @return bool 183 | */ 184 | public function greaterThan(DateAbstract $dt) 185 | { 186 | return $this->gt($dt); 187 | } 188 | 189 | /** 190 | * Determines if the instance is greater (after) than or equal to another 191 | * 192 | * @param DateAbstract $dt 193 | * 194 | * @return bool 195 | */ 196 | public function gte(DateAbstract $dt) 197 | { 198 | return $this >= $dt; 199 | } 200 | 201 | /** 202 | * Determines if the instance is greater (after) than or equal to another 203 | * 204 | * @param DateAbstract $dt 205 | * 206 | * @see gte() 207 | * 208 | * @return bool 209 | */ 210 | public function greaterThanOrEqualTo(DateAbstract $dt) 211 | { 212 | return $this->gte($dt); 213 | } 214 | 215 | /** 216 | * Determines if the instance is less (before) than another 217 | * 218 | * @param DateAbstract $dt 219 | * 220 | * @return bool 221 | */ 222 | public function lt(DateAbstract $dt) 223 | { 224 | return $this < $dt; 225 | } 226 | 227 | /** 228 | * Determines if the instance is less (before) than another 229 | * 230 | * @param DateAbstract $dt 231 | * 232 | * @see lt() 233 | * 234 | * @return bool 235 | */ 236 | public function lessThan(DateAbstract $dt) 237 | { 238 | return $this->lt($dt); 239 | } 240 | 241 | /** 242 | * Determines if the instance is less (before) or equal to another 243 | * 244 | * @param DateAbstract $dt 245 | * 246 | * @return bool 247 | */ 248 | public function lte(DateAbstract $dt) 249 | { 250 | return $this <= $dt; 251 | } 252 | 253 | /** 254 | * Determines if the instance is less (before) or equal to another 255 | * 256 | * @param DateAbstract $dt 257 | * 258 | * @see lte() 259 | * 260 | * @return bool 261 | */ 262 | public function lessThanOrEqualTo(DateAbstract $dt) 263 | { 264 | return $this->lte($dt); 265 | } 266 | 267 | /** 268 | * Determines if the instance is between start and end dates 269 | * 270 | * @param DateAbstract $startDate 271 | * @param DateAbstract $endDate 272 | * 273 | * @return bool 274 | */ 275 | public function bw(DateAbstract $startDate, DateAbstract $endDate) 276 | { 277 | return $this->gt($startDate) && $this->lt($endDate); 278 | } 279 | 280 | /** 281 | * Determines if the instance is between start and end dates 282 | * 283 | * @param DateAbstract $startDate 284 | * @param DateAbstract $endDate 285 | * 286 | * @see bw() 287 | * 288 | * @return bool 289 | */ 290 | public function between(DateAbstract $startDate, DateAbstract $endDate) 291 | { 292 | return $this->bw($startDate, $endDate); 293 | } 294 | 295 | /** 296 | * Determines if the instance is between start and end dates or equals to 297 | * 298 | * @param DateAbstract $startDate 299 | * @param DateAbstract $endDate 300 | * 301 | * @return bool 302 | */ 303 | public function bwe(DateAbstract $startDate, DateAbstract $endDate) 304 | { 305 | return $this->gte($startDate) && $this->lte($endDate); 306 | } 307 | 308 | /** 309 | * Determines if the instance is between start and end dates or equals to 310 | * 311 | * @param DateAbstract $startDate 312 | * @param DateAbstract $endDate 313 | * 314 | * @see bwe() 315 | * 316 | * @return bool 317 | */ 318 | public function betweenEqual(DateAbstract $startDate, DateAbstract $endDate) 319 | { 320 | return $this->bwe($startDate, $endDate); 321 | } 322 | 323 | /** 324 | * Set the date and time all together 325 | * 326 | * @param int $year 327 | * @param int $month 328 | * @param int $day 329 | * @param int $hour 330 | * @param int $minute 331 | * @param int $second 332 | * 333 | * @return static 334 | */ 335 | public function setDateTime($year, $month, $day, $hour, $minute, $second = 0) 336 | { 337 | return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); 338 | } 339 | 340 | /** 341 | * @return $this 342 | */ 343 | public function startOfMonth() 344 | { 345 | return $this->setDateTime($this->year, $this->month, 1, 0, 0, 0); 346 | } 347 | 348 | /** 349 | * @return $this 350 | */ 351 | public function endOfMonth() 352 | { 353 | return $this->setDateTime($this->year, $this->month, $this->daysInMonth, 0, 0, 0); 354 | } 355 | 356 | /** 357 | * @return $this 358 | */ 359 | public function startOfDay() 360 | { 361 | $this->setTime(0, 0, 0); 362 | 363 | return $this; 364 | } 365 | 366 | /** 367 | * @return $this 368 | */ 369 | public function endOfDay() 370 | { 371 | $this->setTime(23, 59, 59); 372 | 373 | return $this; 374 | } 375 | 376 | /** 377 | * @param int $value 378 | * @return $this 379 | */ 380 | public function subYears($value = 1) 381 | { 382 | $this->addYears(-1 * $value); 383 | 384 | return $this; 385 | } 386 | 387 | /** 388 | * @param int $value 389 | * @return $this 390 | */ 391 | public function addYears($value = 1) 392 | { 393 | $this->modify((int) $value." year"); 394 | 395 | return $this; 396 | } 397 | 398 | /** 399 | * @param int $value 400 | * @return $this 401 | */ 402 | public function subMonths($value = 1) 403 | { 404 | $this->addMonths(-1 * $value); 405 | 406 | return $this; 407 | } 408 | 409 | /** 410 | * @param int $value 411 | * @return $this 412 | */ 413 | public function addMonths($value = 1) 414 | { 415 | $this->modify((int) $value." month"); 416 | 417 | return $this; 418 | } 419 | 420 | /** 421 | * @param int $value 422 | * @return $this 423 | */ 424 | public function subWeeks($value = 1) 425 | { 426 | $this->addWeeks(-1 * $value); 427 | 428 | return $this; 429 | } 430 | 431 | /** 432 | * @param int $value 433 | * @return $this 434 | */ 435 | public function addWeeks($value = 1) 436 | { 437 | $this->modify((int) $value." week"); 438 | 439 | return $this; 440 | } 441 | 442 | /** 443 | * @param int $value 444 | * @return $this 445 | */ 446 | public function subDays($value = 1) 447 | { 448 | $this->addDays(-1 * $value); 449 | 450 | return $this; 451 | } 452 | 453 | /** 454 | * @param int $value 455 | * @return $this 456 | */ 457 | public function addDays($value = 1) 458 | { 459 | $this->modify((int) $value." day"); 460 | 461 | return $this; 462 | } 463 | 464 | /** 465 | * @param int $value 466 | * @return $this 467 | */ 468 | public function subHours($value = 1) 469 | { 470 | $this->addHours(-1 * $value); 471 | 472 | return $this; 473 | } 474 | 475 | /** 476 | * @param int $value 477 | * @return $this 478 | */ 479 | public function addHours($value = 1) 480 | { 481 | $this->modify((int) $value.' hour'); 482 | 483 | return $this; 484 | } 485 | 486 | /** 487 | * @param int $value 488 | * @return $this 489 | */ 490 | public function subMinutes($value = 1) 491 | { 492 | $this->addMinutes(-1 * $value); 493 | 494 | return $this; 495 | } 496 | 497 | /** 498 | * @param int $value 499 | * @return $this 500 | */ 501 | public function addMinutes($value = 1) 502 | { 503 | $this->modify((int) $value.' minute'); 504 | 505 | return $this; 506 | } 507 | 508 | /** 509 | * @param int $value 510 | * @return $this 511 | */ 512 | public function subSeconds($value = 1) 513 | { 514 | $this->addSeconds(-1 * $value); 515 | 516 | return $this; 517 | } 518 | 519 | /** 520 | * @param int $value 521 | * @return $this 522 | */ 523 | public function addSeconds($value = 1) 524 | { 525 | $this->modify((int) $value.' second'); 526 | 527 | return $this; 528 | } 529 | 530 | /** 531 | * Return string datetime wherever echo object 532 | * 533 | * @return string 534 | */ 535 | public function __toString() 536 | { 537 | return $this->format('Y/m/d H:i:s'); 538 | } 539 | 540 | /** 541 | * Create an instance object for current datetime 542 | * 543 | * @param mixed $tz 544 | * @return \Date\Jalali|\Date\Date 545 | */ 546 | public static function now($tz = null) 547 | { 548 | return new static(null, $tz); 549 | } 550 | 551 | /** 552 | * @return $this 553 | */ 554 | public static function yesterday() 555 | { 556 | return self::now()->subDays(1); 557 | } 558 | 559 | /** 560 | * @return $this 561 | */ 562 | public static function tomorrow() 563 | { 564 | return self::now()->addDays(1); 565 | } 566 | 567 | /** 568 | * Create base datetime 569 | * 570 | * @param int $year 571 | * @param int $month 572 | * @param int $day 573 | * @param int $hour 574 | * @param int $minute 575 | * @param int $second 576 | * @param string $timezone 577 | * @return mixed 578 | */ 579 | public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) 580 | { 581 | return new static("$year-$month-$day $hour:$minute:$second", $tz); 582 | } 583 | 584 | /** 585 | * Create base date 586 | * 587 | * @param int $year 588 | * @param int $month 589 | * @param int $day 590 | * @param string $timezone 591 | * @return mixed 592 | */ 593 | public static function createDate($year = null, $month = null, $day = null, $tz = null) 594 | { 595 | return new static("$year-$month-$day", $tz); 596 | } 597 | 598 | /** 599 | * Create base time 600 | * 601 | * @param int $hour 602 | * @param int $minute 603 | * @param int $second 604 | * @param string $timezone 605 | * @return mixed 606 | */ 607 | public static function createTime($hour = null, $minute = null, $second = null, $tz = null) 608 | { 609 | return new static("$hour:$minute:$second", $tz); 610 | } 611 | 612 | /** 613 | * Create a DateAbstract instance from a timestamp. 614 | * 615 | * @param int $timestamp 616 | * @param \DateTimeZone|string|null $tz 617 | * 618 | * @return static 619 | */ 620 | public static function createFromTimestamp($timestamp, $tz = null) 621 | { 622 | return static::now($tz)->setTimestamp($timestamp); 623 | } 624 | 625 | /** 626 | * Creates a DateTimeZone from a string, DateTimeZone or integer offset. 627 | * 628 | * @param \DateTimeZone|string|int|null $object 629 | * @return \DateTimeZone 630 | * 631 | * @throws InvalidArgumentException 632 | * 633 | * @source https://github.com/briannesbitt/DateAbstract 634 | */ 635 | protected static function safeCreateDateTimeZone($object) 636 | { 637 | if ($object === null) { 638 | // Don't return null... avoid Bug #52063 in PHP <5.3.6 639 | return new DateTimeZone(date_default_timezone_get()); 640 | } 641 | if ($object instanceof DateTimeZone) { 642 | return $object; 643 | } 644 | if (is_numeric($object)) { 645 | $timezone_offset = $object * 3600; 646 | $tzName = timezone_name_from_abbr(null, $timezone_offset, true); 647 | if ($tzName === false) { 648 | throw new InvalidArgumentException("Unknown or bad timezone ($object)"); 649 | } 650 | $object = $tzName; 651 | } 652 | $tz = @timezone_open((string) $object); 653 | if ($tz === false) { 654 | throw new InvalidArgumentException("Unknown or bad timezone ($object)"); 655 | } 656 | return $tz; 657 | } 658 | 659 | /** 660 | * Convert english numbers to farsi 661 | * 662 | * @param string $text 663 | * @return string 664 | */ 665 | public static function enToFa($text) 666 | { 667 | $farsiNumbers = array('۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'); 668 | $englishNumbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); 669 | 670 | return str_replace($englishNumbers, $farsiNumbers, $text); 671 | } 672 | 673 | /** 674 | * Convert farsi numbers to english 675 | * 676 | * @param string $text 677 | * @return string 678 | */ 679 | public static function faToEn($text) 680 | { 681 | $farsiNumbers = array('۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'); 682 | $englishNumbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'); 683 | 684 | return str_replace($farsiNumbers, $englishNumbers, $text); 685 | } 686 | 687 | /** 688 | * Convert jalali to gregorian date 689 | * 690 | * @param int $gYear 691 | * @param int $gMonth 692 | * @param int $gDay 693 | * @return array 694 | * 695 | * @source https://github.com/sallar/jDateTime 696 | * @author Roozbeh Pournader and Mohammad Toossi 697 | */ 698 | protected function jalaliToGregorian($jYear, $jMonth, $jDay) 699 | { 700 | $gDaysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 701 | $jDaysInMonth = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); 702 | 703 | $jYear = $jYear - 979; 704 | $jMonth = $jMonth - 1; 705 | $jDay = $jDay - 1; 706 | $jDayNo = 365 * $jYear + self::div($jYear, 33) * 8 + self::div($jYear % 33 + 3, 4); 707 | 708 | for ($i = 0; $i < $jMonth; ++$i) 709 | $jDayNo += $jDaysInMonth[$i]; 710 | 711 | $jDayNo += $jDay; 712 | $gDayNo = $jDayNo + 79; 713 | $gYear = 1600 + 400 * self::div($gDayNo, 146097); 714 | $gDayNo = $gDayNo % 146097; 715 | $this->leap = true; 716 | 717 | if ($gDayNo >= 36525) { 718 | $gDayNo--; 719 | $gYear += 100 * self::div($gDayNo, 36524); 720 | $gDayNo = $gDayNo % 36524; 721 | if ($gDayNo >= 365) 722 | $gDayNo++; 723 | else 724 | $this->leap = false; 725 | } 726 | 727 | $gYear += 4 * self::div($gDayNo, 1461); 728 | $gDayNo %= 1461; 729 | 730 | if ($gDayNo >= 366) { 731 | $this->leap = false; 732 | $gDayNo--; 733 | $gYear += self::div($gDayNo, 365); 734 | $gDayNo = $gDayNo % 365; 735 | } 736 | 737 | for ($i = 0; $gDayNo >= $gDaysInMonth[$i] + ($i == 1 && $this->leap); $i++) 738 | $gDayNo -= $gDaysInMonth[$i] + ($i == 1 && $this->leap); 739 | 740 | $gMonth = $i + 1; 741 | $gDay = $gDayNo + 1; 742 | 743 | return array($gYear, $gMonth, $gDay); 744 | } 745 | 746 | 747 | /** 748 | * Convert gregorian to jalali date 749 | * 750 | * @param int $gYear 751 | * @param int $gMonth 752 | * @param int $gDay 753 | * @return array 754 | * 755 | * @source https://github.com/sallar/jDateTime 756 | * @author Roozbeh Pournader and Mohammad Toossi 757 | */ 758 | public function gregorianToJalali($gYear, $gMonth, $gDay) 759 | { 760 | $gDaysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 761 | $jDaysInMonth = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); 762 | 763 | $gYear = $gYear - 1600; 764 | $gMonth = $gMonth - 1; 765 | $gDay = $gDay - 1; 766 | $gDayNo = 365 * $gYear + self::div($gYear + 3, 4) - self::div($gYear + 99, 100) + self::div($gYear + 399, 400); 767 | 768 | for ($i = 0; $i < $gMonth; ++$i) 769 | $gDayNo += $gDaysInMonth[$i]; 770 | 771 | if ($gMonth > 1 && (($gYear % 4 == 0 && $gYear % 100 != 0) || ($gYear % 400 == 0))) 772 | $gDayNo++; 773 | 774 | $gDayNo += $gDay; 775 | $jDayNo = $gDayNo - 79; 776 | $jNp = self::div($jDayNo, 12053); 777 | $jDayNo = $jDayNo % 12053; 778 | $jYear = 979 + 33 * $jNp + 4 * self::div($jDayNo, 1461); 779 | $jDayNo %= 1461; 780 | 781 | if ($jDayNo >= 366) { 782 | $jYear += self::div($jDayNo - 1, 365); 783 | $jDayNo = ($jDayNo - 1) % 365; 784 | } 785 | for ($i = 0; $i < 11 && $jDayNo >= $jDaysInMonth[$i]; ++$i) 786 | $jDayNo -= $jDaysInMonth[$i]; 787 | 788 | $jMonth = $i + 1; 789 | $jDay = $jDayNo + 1; 790 | 791 | return array($jYear, $jMonth, $jDay); 792 | } 793 | 794 | /** 795 | * @param int $va1 796 | * @param int $va2 797 | * @return int 798 | */ 799 | protected static function div($var1, $var2) 800 | { 801 | return intval($var1 / $var2); 802 | } 803 | } 804 | -------------------------------------------------------------------------------- /src/Jalali.php: -------------------------------------------------------------------------------- 1 | 'یکشنبه', self::MONDAY => 'دوشنبه', self::TUESDAY => 'سه‌شنبه', self::WEDNESDAY => 'چهارشنبه', self::THURSDAY => 'پنج‌شنبه', self::FRIDAY => 'جمعه', self::SATURDAY => 'شنبه'); 52 | 53 | /** 54 | * Day names based on D symobl 55 | * 56 | * @var array 57 | */ 58 | protected $format_D = array(self::SUNDAY => 'یکش', self::MONDAY => 'دوش', self::TUESDAY => 'سشن', self::WEDNESDAY => 'چها', self::THURSDAY => 'پنج', self::FRIDAY => 'جمع', self::SATURDAY => 'شنب'); 59 | 60 | /** 61 | * Month names 62 | * 63 | * @var array 64 | */ 65 | protected $format_F = array('فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'); 66 | 67 | /** 68 | * Month names 69 | * 70 | * @var array 71 | */ 72 | protected $format_M = array('فرو', 'ارد', 'خرد', 'تیر', 'مرد', 'شهر', 'مهر', 'آبا', 'آذر', 'دی', 'بهم', 'اسف'); 73 | 74 | /** 75 | * @param string|null $time 76 | * @param mixed $tz 77 | */ 78 | public function __construct($time = null, $tz = null) 79 | { 80 | parent::__construct(null, self::safeCreateDateTimeZone($tz)); 81 | $this->decode($this->faToEn($time)); 82 | } 83 | 84 | /** 85 | * @param int $year 86 | * @param int $month 87 | * @param int $day 88 | * @return void 89 | */ 90 | public function setDate($year, $month, $day = 1) 91 | { 92 | $this->jYear = (int) $year; 93 | $this->jMonth = (int) $month; 94 | $this->jDay = (int) $day; 95 | 96 | list($year, $month, $day) = $this->jalaliToGregorian($year, $month, $day); 97 | parent::setDate($year, $month, $day); 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Decode a datetime string 104 | * Supported formats: 105 | * (1395/05/04) (1395-5-04) (1395-01-04 23:54) (1395-01-04 23:54:10) (23:54) (23:54:10) 106 | * 107 | * @param string $time 108 | * @return void 109 | */ 110 | protected function decode($time) 111 | { 112 | $datetimeDetailRegex = '/(\d{1,4})[\/|-](\d{1,2})[\/|-](\d{1,2})\s*(\d{1,2}):(\d{1,2}):(\d{1,2})/'; 113 | $datetimeRegex = '/(\d{4})[\/|-](\d{1,2})[\/|-](\d{1,2})\s*(\d{1,2}):(\d{1,2})/'; 114 | $dateRegex = '/(\d{1,4})[\/|-](\d{1,2})[\/|-](\d{1,2})/'; 115 | $timeDetailRegex = '/(\d{1,2}):(\d{1,2}):(\d{1,2})/'; 116 | $timeRegex = '/(\d{1,2}):(\d{1,2})/'; 117 | $timestampRegex = '/\A\d+\z/'; 118 | 119 | preg_match($datetimeDetailRegex, $time, $output); 120 | if (!empty($output)) { 121 | $this->setDate($output[1], $output[2], $output[3]); 122 | $this->setTime($output[4], $output[5], $output[6]); 123 | return; 124 | } 125 | 126 | preg_match($datetimeRegex, $time, $output); 127 | if (!empty($output)) { 128 | $this->setDate($output[1], $output[2], $output[3]); 129 | $this->setTime($output[4], $output[5]); 130 | return; 131 | } 132 | 133 | preg_match($dateRegex, $time, $output); 134 | if (!empty($output)) { 135 | $this->setDate($output[1], $output[2], $output[3]); 136 | $this->setTime(0, 0, 0); 137 | return; 138 | } 139 | 140 | preg_match($timeDetailRegex, $time, $output); 141 | if (!empty($output)) { 142 | $this->setTime($output[1], $output[2], $output[3]); 143 | $this->refreshJalali(); 144 | return; 145 | } 146 | 147 | preg_match($timeRegex, $time, $output); 148 | if (!empty($output)) { 149 | $this->setTime($output[1], $output[2]); 150 | $this->refreshJalali(); 151 | return; 152 | } 153 | 154 | preg_match($timestampRegex, $time, $output); 155 | if (!empty($output)) { 156 | $this->setTimestamp($output[0]); 157 | $this->refreshJalali(); 158 | return; 159 | } 160 | 161 | $this->refreshJalali(); 162 | } 163 | 164 | /** 165 | * Refresh jalali date based on parent date 166 | * 167 | * @return void 168 | */ 169 | protected function refreshJalali() 170 | { 171 | list($this->jYear, $this->jMonth, $this->jDay) = $this->gregorianToJalali(parent::format('Y'), parent::format('m'), parent::format('d')); 172 | } 173 | 174 | /** 175 | * @return \Date\Date 176 | */ 177 | public function toGregorian() 178 | { 179 | return new Date(parent::format(parent::ATOM), parent::getTimezone()); 180 | } 181 | 182 | /** 183 | * An aliases for toGregorian method 184 | * 185 | * @return \Date\Date 186 | */ 187 | public function tog() 188 | { 189 | return $this->toGregorian(); 190 | } 191 | 192 | /** 193 | * @param string $name 194 | * @return mixed 195 | */ 196 | public function __get($name) 197 | { 198 | switch (true) { 199 | case array_key_exists($name, $formats = array( 200 | 'year' => 'Y', 201 | 'month' => 'm', 202 | 'day' => 'd', 203 | 'daysInMonth' => 't' 204 | )): 205 | return $this->format($formats[$name]); 206 | break; 207 | } 208 | } 209 | 210 | /** 211 | * Convert datetime format to its actual values 212 | * 213 | * @param string $format 214 | * @return mixed 215 | */ 216 | public function format($format) 217 | { 218 | $symbols = array('Y', 'm', 'd', 'D', 'H', 'i', 's', 'l', 'j', 'N', 'w', 'z', 'W', 'F', 'M', 'n', 't', 'L', 'o', 'y', 'a', 'A', 'B', 'g', 'G', 'h', 's', 'u', 'e', 'i', 'I', 'O', 'P', 'T', 'U', 'c', 'r'); 219 | $intactSymbols = array('H', 'i', 's', 'N', 'w', 'B', 'g', 'G', 'h', 's', 'u', 'e', 'i', 'I', 'O', 'P', 'T', 'U', 'c', 'r'); 220 | 221 | $findSymbolsRegex = '/('.implode('|', $symbols).')(-|:|\s|\d|\z|\/)/'; 222 | $symbols = preg_match_all($findSymbolsRegex, $format, $symbols) ? $symbols[1] : array(); 223 | 224 | foreach ($symbols as $symbol) { 225 | $v = ''; 226 | switch ($symbol) { 227 | case 'Y': 228 | $v = sprintf('%04d', $this->jYear); 229 | break; 230 | 231 | case 'y': 232 | $v = $this->jYear % 100; 233 | break; 234 | 235 | case 'm': 236 | $v = sprintf('%02d', $this->jMonth); 237 | break; 238 | 239 | case 'd': 240 | $v = sprintf('%02d', $this->jDay); 241 | break; 242 | 243 | case 'D': 244 | $v = $this->format_D[parent::format('w')]; 245 | break; 246 | 247 | case 'l': 248 | $v = $this->format_l[parent::format('w')]; 249 | break; 250 | 251 | case 'j': 252 | $v = sprintf('%01d', $this->jDay); 253 | break; 254 | 255 | case 'z': 256 | $v = $this->dayOfYear(); 257 | break; 258 | 259 | case 'W': 260 | $v = $this->weekOfYear(); 261 | break; 262 | 263 | case 'F': 264 | $v = $this->format_F[$this->jMonth - 1]; 265 | break; 266 | 267 | case 'M': 268 | $v = $this->format_M[$this->jMonth - 1]; 269 | break; 270 | 271 | case 'n': 272 | $v = sprintf('%01d', $this->jMonth); 273 | break; 274 | 275 | case 't': 276 | if ($this->jMonth < 7) $v = 31; 277 | elseif ($this->jMonth == 12 || $this->leap) $v = 30; 278 | else $v = 29; 279 | break; 280 | 281 | case 'L': 282 | $v = (int) $this->leap; 283 | break; 284 | 285 | case 'o': 286 | $v = $this->jYear; 287 | break; 288 | 289 | case 'a': 290 | $v = parent::format('H') > 12 ? 'ب.ظ' : 'ق.ظ'; 291 | break; 292 | 293 | case 'A': 294 | $v = parent::format('H') > 12 ? 'بعد از ظهر' : 'قبل از ظهر'; 295 | break; 296 | 297 | default: 298 | if (in_array($symbol, $intactSymbols)) { 299 | $v = parent::format($symbol); 300 | } 301 | break; 302 | } 303 | 304 | $format = preg_replace("/$symbol/", $v, $format); 305 | } 306 | 307 | if ($this->outputFormat == self::FA) 308 | return $this->enToFa($format); 309 | 310 | return $format; 311 | } 312 | 313 | /** 314 | * Return day of year 315 | * 316 | * @return int 317 | */ 318 | protected function dayOfYear() 319 | { 320 | if ($this->jMonth > 6) { 321 | return 186 + (($this->jMonth - 6 - 1) * 30) + $this->jDay; 322 | } 323 | else { 324 | return (($this->jMonth - 1) * 31) + $this->jDay; 325 | } 326 | } 327 | 328 | /** 329 | * Return week of year 330 | * 331 | * @return int 332 | */ 333 | protected function weekOfYear() 334 | { 335 | $dayOfYear = $this->dayOfYear(); 336 | if (is_int($dayOfYear / 7)) { 337 | return $dayOfYear / 7; 338 | } else { 339 | return intval($dayOfYear / 7) + 1; 340 | } 341 | } 342 | 343 | /** 344 | * Set output format to fa 345 | * 346 | * @return $this 347 | */ 348 | public function fa($format = null) 349 | { 350 | $this->outputFormat = self::FA; 351 | 352 | if (is_null($format)) return $this; 353 | 354 | return $this->format($format); 355 | } 356 | 357 | /** 358 | * @param int $value 359 | * @return $this 360 | */ 361 | public function subYears($value = 1) 362 | { 363 | parent::subYears($value); 364 | $this->refreshJalali(); 365 | 366 | return $this; 367 | } 368 | 369 | /** 370 | * @param int $value 371 | * @return $this 372 | */ 373 | public function addYears($value = 1) 374 | { 375 | parent::addYears($value); 376 | $this->refreshJalali(); 377 | 378 | return $this; 379 | } 380 | 381 | /** 382 | * @param int $value 383 | * @return $this 384 | */ 385 | public function subMonths($value = 1) 386 | { 387 | parent::subMonths($value); 388 | $this->refreshJalali(); 389 | 390 | return $this; 391 | } 392 | 393 | /** 394 | * @param int $value 395 | * @return $this 396 | */ 397 | public function addMonths($value = 1) 398 | { 399 | parent::addMonths($value); 400 | $this->refreshJalali(); 401 | 402 | return $this; 403 | } 404 | 405 | /** 406 | * @param int $value 407 | * @return $this 408 | */ 409 | public function subWeeks($value = 1) 410 | { 411 | parent::subWeeks($value); 412 | $this->refreshJalali(); 413 | 414 | return $this; 415 | } 416 | 417 | /** 418 | * @param int $value 419 | * @return $this 420 | */ 421 | public function addWeeks($value = 1) 422 | { 423 | parent::addWeeks($value); 424 | $this->refreshJalali(); 425 | 426 | return $this; 427 | } 428 | 429 | /** 430 | * @param int $value 431 | * @return $this 432 | */ 433 | public function subDays($value = 1) 434 | { 435 | parent::subDays($value); 436 | $this->refreshJalali(); 437 | 438 | return $this; 439 | } 440 | 441 | /** 442 | * @param int $value 443 | * @return $this 444 | */ 445 | public function addDays($value = 1) 446 | { 447 | parent::addDays($value); 448 | $this->refreshJalali(); 449 | 450 | return $this; 451 | } 452 | 453 | /** 454 | * @param int $value 455 | * @return $this 456 | */ 457 | public function subHours($value = 1) 458 | { 459 | parent::subHours($value); 460 | $this->refreshJalali(); 461 | 462 | return $this; 463 | } 464 | 465 | /** 466 | * @param int $value 467 | * @return $this 468 | */ 469 | public function addHours($value = 1) 470 | { 471 | parent::addHours($value); 472 | $this->refreshJalali(); 473 | 474 | return $this; 475 | } 476 | 477 | /** 478 | * @param int $value 479 | * @return $this 480 | */ 481 | public function subMinutes($value = 1) 482 | { 483 | parent::subMinutes($value); 484 | $this->refreshJalali(); 485 | 486 | return $this; 487 | } 488 | 489 | /** 490 | * @param int $value 491 | * @return $this 492 | */ 493 | public function addMinutes($value = 1) 494 | { 495 | parent::addMinutes($value); 496 | $this->refreshJalali(); 497 | 498 | return $this; 499 | } 500 | 501 | /** 502 | * @param int $value 503 | * @return $this 504 | */ 505 | public function subSeconds($value = 1) 506 | { 507 | parent::subSeconds($value); 508 | $this->refreshJalali(); 509 | 510 | return $this; 511 | } 512 | 513 | /** 514 | * @param int $value 515 | * @return $this 516 | */ 517 | public function addSeconds($value = 1) 518 | { 519 | parent::addSeconds($value); 520 | $this->refreshJalali(); 521 | 522 | return $this; 523 | } 524 | 525 | /** 526 | * Equivalent to new Jalali() 527 | * 528 | * @return \Date\Jalali 529 | */ 530 | public static function make($time) 531 | { 532 | return new Jalali($time); 533 | } 534 | 535 | /** 536 | * Create a DateAbstract instance from a timestamp. 537 | * 538 | * @param int $timestamp 539 | * @param \DateTimeZone|string|null $tz 540 | * 541 | * @return static 542 | */ 543 | public static function createFromTimestamp($timestamp, $tz = null) 544 | { 545 | $date = static::now($tz)->setTimestamp($timestamp); 546 | $date->refreshJalali(); 547 | 548 | return $date; 549 | } 550 | } 551 | -------------------------------------------------------------------------------- /tests/JalaliTest.php: -------------------------------------------------------------------------------- 1 | assertSame(5, $date2->diffInYears($date1)); 18 | } 19 | 20 | public function testDiffInYearsBaseOnDifference() 21 | { 22 | $date1 = new Jalali('1390-05-06', 'UTC'); 23 | $date2 = new Jalali('1395-12-07', 'UTC'); 24 | 25 | $this->assertSame(-5, $date2->diffInYears($date1, false)); 26 | } 27 | 28 | /**************************** 29 | * Timestamp 30 | *****************************/ 31 | 32 | public function testTimestampInitialize() 33 | { 34 | $date = new Jalali(1466664181, 'UTC'); 35 | 36 | $this->assertSame('1395-04-03 06:43:01', $date->format('Y-m-d H:i:s')); 37 | } 38 | 39 | public function testTimestampCreator() 40 | { 41 | $date = Jalali::createFromTimestamp(1466664181, 'UTC'); 42 | 43 | $this->assertSame('1395-04-03 06:43:01', $date->format('Y-m-d H:i:s')); 44 | } 45 | 46 | public function testTimestampGet() 47 | { 48 | $date = new Jalali(1466664492, 'UTC'); 49 | 50 | $this->assertSame(1466664492, $date->getTimestamp()); 51 | } 52 | 53 | public function testTimestampFormat() 54 | { 55 | $date = new Jalali(1466664492, 'UTC'); 56 | 57 | $this->assertSame('1466664492', $date->format('U')); 58 | } 59 | 60 | public function testFrotmatJ() 61 | { 62 | $date = new Jalali('1398/1/5', 'Asia/tehran'); 63 | 64 | $this->assertSame('5', $date->format('j')); 65 | } 66 | } 67 | --------------------------------------------------------------------------------