├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── CalendarUtils.php ├── Converter.php ├── Jalalian.php └── helpers.php └── tests ├── CalendarUtilsTest.php ├── HelperTest.php └── JalalianTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | issuehunt: morilog 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-22.04 10 | 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | php: [ 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3 ] 15 | 16 | name: PHP ${{ matrix.php }} 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php }} 26 | ini-values: error_reporting=E_ALL 27 | tools: composer:v2 28 | coverage: none 29 | 30 | - name: Install dependencies 31 | run: | 32 | composer update --prefer-dist --no-interaction --no-progress 33 | 34 | - name: Execute tests 35 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .idea 6 | .phpunit.* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | - 7.3 8 | - 7.4 9 | before_script: 10 | - curl -s http://getcomposer.org/installer | php 11 | - php composer.phar install --dev 12 | 13 | script: ./vendor/bin/phpunit 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Morilog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/morilog/jalali.svg?branch=master)](https://travis-ci.org/morilog/jalali) 2 | morilog/jalali 3 | ====== 4 | - Jalali calendar is a solar calendar that was used in Persia, variants of which today are still in use in Iran as well as Afghanistan. [Read more on Wikipedia](http://en.wikipedia.org/wiki/Jalali_calendar) or see [Calendar Converter](http://www.fourmilab.ch/documents/calendar/). 5 | 6 | - Calendar conversion is based on the [algorithm provided by Kazimierz M. Borkowski](http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm) and has a very good performance. 7 | 8 | - CalendarUtils class was ported from [jalaali/jalaali-js](https://github.com/jalaali/jalaali-js) 9 | 10 | ## Version 3 features 11 | - High human readable API 12 | - DateTime manipulating API 13 | - DateTime comparing API 14 | - Immutable 15 | 16 | ## Installation Version 3.* 17 | > If you are using version <= 2.*, please read [old docs](https://github.com/morilog/jalali/blob/v2.3.0/README.md) 18 | #### Requirements: 19 | - `php >= 7.0` 20 | 21 | Run the Composer update command 22 | 23 | $ composer require morilog/jalali:3.* 24 | 25 | 26 | ## Basic Usage 27 | In the current version, I introduced `Jalalian` class for manipulating Jalali date time 28 | ### Jalalian 29 | In version >= 1.1, you can use `jdate()` instead of `Jalalian::forge()`; 30 | #### `now([$timestamp = null])` 31 | ``` php 32 | // the default timestamp is Now 33 | $date = \Morilog\Jalali\Jalalian::now() 34 | // OR 35 | $date = jdate(); 36 | 37 | // pass timestamps 38 | $date = Jalalian::forge(1333857600); 39 | // OR 40 | $date = jdate(1333857600); 41 | 42 | // pass human readable strings to make timestamps 43 | $date = Jalalian::forge('last sunday'); 44 | 45 | // get the timestamp 46 | $date = Jalalian::forge('last sunday')->getTimestamp(); // 1333857600 47 | 48 | // format the timestamp 49 | $date = Jalalian::forge('last sunday')->format('%B %d، %Y'); // دی 02، 1391 50 | $date = Jalalian::forge('today')->format('%A, %d %B %y'); // جمعه، 23 اسفند 97 51 | 52 | // get a predefined format 53 | $date = Jalalian::forge('last sunday')->format('datetime'); // 1391-10-02 00:00:00 54 | $date = Jalalian::forge('last sunday')->format('date'); // 1391-10-02 55 | $date = Jalalian::forge('last sunday')->format('time'); // 00:00:00 56 | 57 | // get relative 'ago' format 58 | $date = Jalalian::forge('now - 10 minutes')->ago() // 10 دقیقه پیش 59 | ``` 60 | 61 | #### Methods api 62 | --- 63 | 64 | 65 | ```php 66 | public static function now(?\DateTimeZone $timeZone = null): Jalalian 67 | 68 | $jDate = Jalalian::now(); 69 | ``` 70 | 71 | --- 72 | ```php 73 | public static function fromCarbon(Carbon $carbon): Jalalian 74 | 75 | $jDate = Jalalian::fromCarbon(Carbon::now()); 76 | ``` 77 | 78 | --- 79 | ```php 80 | public static function fromFormat(string $format, string $timestamp, ?\DateTimeZone$timeZone = null): Jalalian 81 | 82 | $jDate = Jalalian::fromFormat('Y-m-d H:i:s', '1397-01-18 12:00:40'); 83 | ``` 84 | 85 | 86 | --- 87 | ```php 88 | public static function forge($timestamp, ?\DateTimeZone $timeZone = null): Jalalian 89 | 90 | // Alias fo fromDatetime 91 | ``` 92 | 93 | --- 94 | ```php 95 | public static function fromDateTime($dateTime, ?\DateTimeZone $timeZone = null): Jalalian 96 | 97 | $jDate = Jalalian::fromDateTime(Carbon::now()) 98 | // OR 99 | $jDate = Jalalian::fromDateTime(new \DateTime()); 100 | // OR 101 | $jDate = Jalalian::fromDateTime('yesterday'); 102 | 103 | ``` 104 | 105 | 106 | --- 107 | ```php 108 | public function getMonthDays(): int 109 | 110 | $date = (new Jalalian(1397, 1, 18))->getMonthDays() 111 | // output: 31 112 | ``` 113 | 114 | --- 115 | ```php 116 | public function getMonth(): int 117 | 118 | $date = (new Jalalian(1397, 1, 18))->getMonth() 119 | // output: 1 120 | ``` 121 | 122 | --- 123 | ```php 124 | public function isLeapYear(): bool 125 | 126 | $date = (new Jalalian(1397, 1, 18))->isLeapYear() 127 | // output: false 128 | 129 | ``` 130 | 131 | --- 132 | ```php 133 | public function getYear(): int 134 | 135 | $date = (new Jalalian(1397, 1, 18))->getYear() 136 | // output: 1397 137 | ``` 138 | 139 | --- 140 | ```php 141 | public function subMonths(int $months = 1): Jalalian 142 | 143 | $date = (new Jalalian(1397, 1, 18))->subMonths(1)->toString() 144 | // output: 1396-12-18 00:00:00 145 | 146 | ``` 147 | 148 | --- 149 | ```php 150 | public function subYears(int $years = 1): Jalalian 151 | 152 | $date = (new Jalalian(1397, 1, 18))->subYears(1)->toString() 153 | // output: 1396-01-18 00:00:00 154 | ``` 155 | 156 | --- 157 | ```php 158 | public function getDay(): int 159 | 160 | $date = (new Jalalian(1397, 1, 18))->getDay() 161 | // output: 18 162 | 163 | ``` 164 | 165 | --- 166 | ```php 167 | public function getHour(): int 168 | 169 | $date = (new Jalalian(1397, 1, 18, 12, 0, 0))->getHour() 170 | // output: 12 171 | 172 | 173 | ``` 174 | 175 | --- 176 | ```php 177 | public function getMinute(): int 178 | 179 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->getMinute() 180 | // output: 10 181 | 182 | ``` 183 | 184 | --- 185 | ```php 186 | public function getSecond(): int 187 | 188 | $date = (new Jalalian(1397, 1, 18, 12, 10, 45))->getSecond() 189 | // output: 45 190 | ``` 191 | 192 | --- 193 | ```php 194 | public function getTimezone(): \DateTimeZone 195 | 196 | // Get current timezone 197 | ``` 198 | 199 | --- 200 | ```php 201 | public function addMonths(int $months = 1): Jalalian 202 | 203 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addMonths(1)->format('m') 204 | // output: 02 205 | 206 | ``` 207 | 208 | --- 209 | ```php 210 | public function addYears(int $years = 1): Jalalian 211 | 212 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addYears(1)->format('Y') 213 | // output: 1398 214 | 215 | ``` 216 | 217 | --- 218 | ```php 219 | public function getDaysOf(int $monthNumber = 1): int 220 | 221 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->getDaysOf(1) 222 | // output: 31 223 | ``` 224 | 225 | --- 226 | ```php 227 | public function addDays(int $days = 1): Jalalian 228 | 229 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addDays(1)->format('d') 230 | // output: 18 231 | 232 | ``` 233 | 234 | --- 235 | ```php 236 | public function toCarbon(): Carbon 237 | 238 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->toCarbon()->toDateTimeString() 239 | // output: 2018-04-07 12:10:00 240 | ``` 241 | 242 | --- 243 | ```php 244 | public function subDays(int $days = 1): Jalalian 245 | 246 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subDays(10)->format('d') 247 | // output: 08 248 | ``` 249 | 250 | --- 251 | ```php 252 | public function addHours(int $hours = 1): Jalalian 253 | 254 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addHours(1)->format('H') 255 | // output: 13 256 | 257 | ``` 258 | 259 | --- 260 | ```php 261 | public function subHours(int $hours = 1): Jalalian 262 | 263 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subHours(1)->format('H') 264 | // output: 11 265 | 266 | ``` 267 | 268 | --- 269 | ```php 270 | public function addMinutes(int $minutes = 1): Jalalian 271 | 272 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addMinutes(10)->format('i') 273 | // output: 22 274 | 275 | ``` 276 | 277 | --- 278 | ```php 279 | public function subMinutes(int $minutes = 1): Jalalian 280 | 281 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subMinutes(10)->format('i') 282 | // output: 02 283 | 284 | ``` 285 | 286 | --- 287 | ```php 288 | public function addSeconds(int $secs = 1): Jalalian 289 | 290 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addSeconds(10)->format('s') 291 | // output: 10 292 | 293 | ``` 294 | 295 | --- 296 | ```php 297 | public function subSeconds(int $secs = 1): Jalalian 298 | 299 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subSeconds(10)->format('i:s') 300 | // output: 11:40 301 | 302 | 303 | ``` 304 | 305 | --- 306 | ```php 307 | public function equalsTo(Jalalian $other): bool 308 | 309 | $date = (new Jalalian(1397, 1, 18, 12, 10, 0))->equalsTo(Jalalian::now()) 310 | // output: false 311 | 312 | $date = Jalalian::now()->equalsTo(Jalalian::now()) 313 | // output: true 314 | 315 | ``` 316 | 317 | --- 318 | ```php 319 | public function equalsToCarbon(Carbon $carbon): bool 320 | 321 | $date = Jalalian::now()->equalsToCarbon(Carbon::now()) 322 | // output: true 323 | ``` 324 | 325 | --- 326 | ```php 327 | public function greaterThan(Jalalian $other): bool 328 | 329 | $date = Jalalian::now()->greaterThan(Jalalian::now()->subDays(1))) 330 | // output: true 331 | ``` 332 | 333 | --- 334 | ```php 335 | public function greaterThanCarbon(Carbon $carbon): bool 336 | 337 | $date = Jalalian::now()->greaterThanCarbon(Carbon::now()->subDays(1))) 338 | // output: true 339 | 340 | ``` 341 | 342 | --- 343 | ```php 344 | public function lessThan(Jalalian $other): bool 345 | 346 | $date = Jalalian::now()->lessThan(Jalalian::now()->addDays(1))) 347 | // output: true 348 | 349 | ``` 350 | 351 | --- 352 | ```php 353 | public function lessThanCarbon(Carbon $carbon): bool 354 | 355 | $date = Jalalian::now()->lessThanCarbon(Carbon::now()->addDays(1))) 356 | // output: true 357 | 358 | ``` 359 | 360 | --- 361 | ```php 362 | public function greaterThanOrEqualsTo(Jalalian $other): bool 363 | 364 | $date = Jalalian::now()->greaterThan(Jalalian::now()->subDays(1))) 365 | // output: true 366 | 367 | ``` 368 | 369 | --- 370 | ```php 371 | public function greaterThanOrEqualsToCarbon(Carbon $carbon): bool 372 | 373 | $date = Jalalian::now()->greaterThanOrEqualsToCarbon(Carbon::now())) 374 | // output: true 375 | 376 | ``` 377 | 378 | --- 379 | ```php 380 | public function lessThanOrEqualsTo(Jalalian $other): bool 381 | 382 | $date = Jalalian::now()->lessThanOrEqualsTo(Jalalian::now())) 383 | // output: true 384 | 385 | ``` 386 | 387 | --- 388 | ```php 389 | public function lessThanOrEqualsToCarbon(Carbon $carbon): bool 390 | 391 | $date = Jalalian::now()->lessThanOrEqualsToCarbon(Carbon::now())) 392 | // output: true 393 | 394 | ``` 395 | 396 | --- 397 | ```php 398 | public function isStartOfWeek(): bool 399 | 400 | $date = (new Jalalian(1397, 6, 24))->isStartOfWeek() 401 | // output: true 402 | 403 | ``` 404 | --- 405 | ```php 406 | public function getEndDayOfYear(): bool 407 | 408 | $date = (new Jalalian(1397, 6, 24))->getEndDayOfYear() 409 | // output: 1397, 12, 29 410 | 411 | ``` 412 | --- 413 | ```php 414 | public function getFirstDayOfMonth(): bool 415 | 416 | $date = (new Jalalian(1397, 6, 24))->getFirstDayOfMonth() 417 | // output: 1397, 6, 1 418 | 419 | ``` 420 | --- 421 | ```php 422 | public function getEndDayOfMonth(): bool 423 | 424 | $date = (new Jalalian(1397, 6, 24))->getEndDayOfMonth() 425 | // output: 1397, 6, 30 426 | 427 | ``` 428 | --- 429 | ```php 430 | public function isSaturday(): bool 431 | 432 | $date = (new Jalalian(1397, 6, 24))->isSaturday() 433 | // output: true 434 | 435 | ``` 436 | 437 | --- 438 | ```php 439 | public function isDayOfWeek(int $day): bool 440 | 441 | $date = (new Jalalian(1397, 6, 24))->isDayOfWeek(0) 442 | // output: true 443 | 444 | ``` 445 | 446 | --- 447 | ```php 448 | public function isEndOfWeek(): bool 449 | 450 | $date = (new Jalalian(1397, 6, 24))->isEndOfWeek() 451 | // output: false 452 | 453 | ``` 454 | 455 | --- 456 | ```php 457 | public function isFriday(): bool 458 | 459 | $date = (new Jalalian(1397, 6, 24))->isFriday() 460 | // output: false 461 | 462 | ``` 463 | 464 | --- 465 | ```php 466 | public function isToday(): bool 467 | 468 | $date = (new Jalalian(1397, 6, 24))->isToday() 469 | // output: (!maybe) true 470 | 471 | ``` 472 | 473 | --- 474 | ```php 475 | public function isTomorrow(): bool 476 | 477 | $date = (new Jalalian(1397, 6, 25))->isTomorrow() 478 | // output: true 479 | 480 | ``` 481 | 482 | --- 483 | ```php 484 | public function isYesterday(): bool 485 | 486 | $date = (new Jalalian(1397, 6, 23))->isYesterday() 487 | // output: true 488 | 489 | ``` 490 | 491 | --- 492 | ```php 493 | public function isFuture(): bool 494 | 495 | $date = (new Jalalian(1397, 6, 26))->isFuture() 496 | // output: true 497 | 498 | ``` 499 | 500 | --- 501 | ```php 502 | public function isPast(): bool 503 | 504 | $date = (new Jalalian(1397, 5, 24))->isPast() 505 | // output: true 506 | 507 | ``` 508 | 509 | --- 510 | ```php 511 | public function toArray(): array 512 | $date = (new Jalalian(1397, 6, 24))->toArray() 513 | // output: ( 514 | // [year] => 1397 515 | // [month] => 6 516 | // [day] => 24 517 | // [dayOfWeek] => 0 518 | // [dayOfYear] => 179 519 | // [hour] => 0 520 | // [minute] => 0 521 | // [second] => 0 522 | // [micro] => 0 523 | // [timestamp] => 1536969600 524 | // [formatted] => 1397-06-24 00:00:00 525 | // [timezone] => 526 | // ) 527 | ``` 528 | 529 | --- 530 | ```php 531 | public function getDayOfWeek(): int 532 | 533 | $date = (new Jalalian(1397, 5, 24))->getDayOfWeek() 534 | // output: 0 535 | 536 | ``` 537 | 538 | --- 539 | ```php 540 | public function isSunday(): bool 541 | 542 | $date = (new Jalalian(1397, 6, 24))->isSunday() 543 | // output: false 544 | 545 | ``` 546 | 547 | --- 548 | ```php 549 | public function isMonday(): bool 550 | 551 | $date = (new Jalalian(1397, 6, 26))->isMonday() 552 | // output: true 553 | 554 | ``` 555 | 556 | --- 557 | ```php 558 | public function isTuesday(): bool 559 | 560 | $date = (new Jalalian(1397, 6, 24))->isTuesday() 561 | // output: false 562 | 563 | ``` 564 | 565 | --- 566 | ```php 567 | public function isWednesday(): bool 568 | 569 | $date = (new Jalalian(1397, 6, 24))->isWednesday() 570 | // output: false 571 | 572 | ``` 573 | 574 | --- 575 | ```php 576 | public function isThursday(): bool 577 | 578 | $date = (new Jalalian(1397, 6, 22))->isThursday() 579 | // output: true 580 | 581 | ``` 582 | 583 | --- 584 | ```php 585 | public function getDayOfYear(): int 586 | 587 | $date = (new Jalalian(1397, 5, 24))->getDayOfYear() 588 | // output: 179 589 | 590 | ``` 591 | 592 | --- 593 | ```php 594 | public function toString(): string 595 | $date = (new Jalalian(1397, 5, 24))->isPast() 596 | // output: 1397-05-24 00:00:00 597 | 598 | ``` 599 | 600 | --- 601 | ```php 602 | public function format(string $format): string 603 | 604 | $date = (new Jalalian(1397, 5, 24))->format('y') 605 | // output: 1397 606 | // see php date formats 607 | 608 | ``` 609 | 610 | --- 611 | ```php 612 | public function __toString(): string 613 | 614 | // Alias of toString() 615 | ``` 616 | 617 | --- 618 | ```php 619 | public function ago(): string 620 | 621 | ``` 622 | 623 | --- 624 | ```php 625 | public function getTimestamp(): int 626 | 627 | ``` 628 | 629 | --- 630 | ```php 631 | public function getNextWeek(): Jalalian 632 | 633 | ``` 634 | 635 | --- 636 | ```php 637 | public function getNextMonth(): Jalalian 638 | 639 | ``` 640 | 641 | --- 642 | 643 | ### CalendarUtils 644 | --- 645 | 646 | 647 | #### `checkDate($year, $month, $day, [$isJalali = true])` 648 | ```php 649 | // Check jalali date 650 | \Morilog\Jalali\CalendarUtils::checkDate(1391, 2, 30, true); // true 651 | 652 | // Check jalali date 653 | \Morilog\Jalali\CalendarUtils::checkDate(2016, 5, 7); // false 654 | 655 | // Check gregorian date 656 | \Morilog\Jalali\CalendarUtils::checkDate(2016, 5, 7, false); // true 657 | ``` 658 | --- 659 | #### `toJalali($gYear, $gMonth, $gDay)` 660 | ```php 661 | \Morilog\Jalali\CalendarUtils::toJalali(2016, 5, 7); // [1395, 2, 18] 662 | ``` 663 | --- 664 | #### `toGregorian($jYear, $jMonth, $jDay)` 665 | ```php 666 | \Morilog\Jalali\CalendarUtils::toGregorian(1395, 2, 18); // [2016, 5, 7] 667 | ``` 668 | --- 669 | #### `strftime($format, [$timestamp = false, $timezone = null])` 670 | ```php 671 | CalendarUtils::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19 672 | ``` 673 | --- 674 | #### `createDateTimeFromFormat($format, $jalaiTimeString)` 675 | ```php 676 | $Jalalian = '1394/11/25 15:00:00'; 677 | 678 | // get instance of \DateTime 679 | $dateTime = \Morilog\Jalali\CalendarUtils::createDatetimeFromFormat('Y/m/d H:i:s', $Jalalian); 680 | 681 | ``` 682 | --- 683 | #### `createCarbonFromFormat($format, $jalaiTimeString)` 684 | ```php 685 | $Jalalian = '1394/11/25 15:00:00'; 686 | 687 | // get instance of \Carbon\Carbon 688 | $carbon = \Morilog\Jalali\CalendarUtils::createCarbonFromFormat('Y/m/d H:i:s', $Jalalian); 689 | 690 | ``` 691 | --- 692 | #### `convertNumbers($string)` 693 | ```php 694 | // convert latin to persian 695 | $date = \Morilog\Jalali\CalendarUtils::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19 696 | \Morilog\Jalali\CalendarUtils::convertNumbers($date); // ۱۳۹۵-۰۲-۱۹ 697 | 698 | // convert persian to latin 699 | $dateString = \Morilog\Jalali\CalendarUtils::convertNumbers('۱۳۹۵-۰۲-۱۹', true); // 1395-02-19 700 | \Morilog\Jalali\CalendarUtils::createCarbonFromFormat('Y-m-d', $dateString)->format('Y-m-d'); //2016-05-8 701 | ``` 702 | 703 | --- 704 | #### `Carbon api-difference` 705 | 706 | You can convert date/time to [briannesbitt/carbon](https://github.com/briannesbitt/carbon), thus being able to use it's [API](https://carbon.nesbot.com/docs/) to work with PHP DateTime class. 707 | 708 | ##### [Difference](https://carbon.nesbot.com/docs/#api-difference) in months: 709 | 710 | ```php 711 | // convert persian to Carbon 712 | $date = \Morilog\Jalali\Jalalian::fromFormat('Y-m-d', "1395-02-19")->toCarbon(); 713 | // ->toString() => Sun May 08 2016 00:00:00 GMT+0000 714 | 715 | // Add 4 months to Carbon 716 | $dateAdd4Months = $date->addMonths(4); 717 | 718 | // Difference in months 719 | $dateAdd4Months->DiffInMonths($date); //4 720 | $dateAdd4Months->floatDiffInMonths($date); //4.0 721 | ``` 722 | 723 | --- 724 | ## Formatting ## 725 | 726 | For help in building your formats, checkout the [PHP strftime() docs](http://php.net/manual/en/function.strftime.php). 727 | 728 | ## Notes ## 729 | 730 | The class relies on ``strtotime()`` to make sense of your strings, and ``strftime()`` to handle the formatting. Always check the ``time()`` output to see if you get false timestamps, it which case, means the class couldn't understand what you were asking it to do. 731 | 732 | ## License ## 733 | - This bundle is created based on [Laravel-Date](https://github.com/swt83/laravel-date) by [Scott Travis](https://github.com/swt83) (MIT Licensed). 734 | - [Jalali (Shamsi) DateTime](https://github.com/sallar/CalendarUtils) class included in the package is created by [Sallar Kaboli](http://sallar.me) and is released under the MIT License. 735 | - This package is created and modified by [Morteza Parvini](http://morilog.ir) for Laravel >= 5 and is released under the MIT License. 736 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moree-dev/jalali", 3 | "description": "This Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in PHP applications, based on Jalali (Shamsi) DateTime class.", 4 | "license": "MIT", 5 | "type": "library", 6 | "authors": [ 7 | { 8 | "name": "Milad Rey", 9 | "email": "miladr@gmail.com" 10 | }, { 11 | "name": "Morteza Parvini", 12 | "email": "m.parvini@outlook.com" 13 | } 14 | ], 15 | "keywords": ["Laravel","Date","Datetime","Jalali", "Morilog"], 16 | "require": { 17 | "php": "^7.0 | ^8.0", 18 | "nesbot/carbon": "^1.21 || ^2.0 || ^3.0", 19 | "beberlei/assert": "^3.0" 20 | }, 21 | "require-dev" : { 22 | "phpunit/phpunit": ">4.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Morilog\\Jalali\\": "src" 27 | }, 28 | "files": [ 29 | "src/helpers.php" 30 | ] 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Morilog\\Jalali\\Tests\\": "tests/" 35 | } 36 | }, 37 | "minimum-stablity": "dev" 38 | } 39 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/CalendarUtils.php: -------------------------------------------------------------------------------- 1 | setDate($year, $month, $day); 89 | 90 | 91 | return $georgianDate; 92 | } 93 | 94 | /** 95 | * Checks whether a Jalaali date is valid or not. 96 | * 97 | * @param int $jy 98 | * @param int $jm 99 | * @param int $jd 100 | * @return bool 101 | */ 102 | public static function isValidateJalaliDate($jy, $jm, $jd) 103 | { 104 | return $jy >= -61 && $jy <= 3177 105 | && $jm >= 1 && $jm <= 12 106 | && $jd >= 1 && $jd <= self::jalaliMonthLength($jy, $jm); 107 | } 108 | 109 | /** 110 | * Checks whether a date is valid or not. 111 | * 112 | * @param $year 113 | * @param $month 114 | * @param $day 115 | * @param bool $isJalali 116 | * @return bool 117 | */ 118 | public static function checkDate($year, $month, $day, $isJalali = true) 119 | { 120 | return $isJalali === true ? self::isValidateJalaliDate($year, $month, $day) : checkdate($month, $day, $year); 121 | } 122 | 123 | /** 124 | * Is this a leap year or not? 125 | * 126 | * @param $jy 127 | * @return bool 128 | */ 129 | public static function isLeapJalaliYear($jy) 130 | { 131 | return self::jalaliCal($jy)['leap'] === 0; 132 | } 133 | 134 | /** 135 | * Number of days in a given month in a Jalaali year. 136 | * 137 | * @param int $jy 138 | * @param int $jm 139 | * @return int 140 | */ 141 | public static function jalaliMonthLength($jy, $jm) 142 | { 143 | if ($jm <= 6) { 144 | return 31; 145 | } 146 | 147 | if ($jm <= 11) { 148 | return 30; 149 | } 150 | 151 | return self::isLeapJalaliYear($jy) ? 30 : 29; 152 | } 153 | 154 | 155 | /** 156 | * This function determines if the Jalaali (Persian) year is 157 | * leap (366-day long) or is the common year (365 days), and 158 | * finds the day in March (Gregorian calendar) of the first 159 | * day of the Jalaali year (jy). 160 | * 161 | * @param int $jy Jalaali calendar year (-61 to 3177) 162 | * @return array 163 | * leap: number of years since the last leap year (0 to 4) 164 | * gy: Gregorian year of the beginning of Jalaali year 165 | * march: the March day of Farvardin the 1st (1st day of jy) 166 | * @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm 167 | * @see: http://www.fourmilab.ch/documents/calendar/ 168 | */ 169 | public static function jalaliCal($jy) 170 | { 171 | $breaks = [ 172 | -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178 173 | ]; 174 | 175 | $breaksCount = count($breaks); 176 | 177 | $gy = $jy + 621; 178 | $leapJ = -14; 179 | $jp = $breaks[0]; 180 | 181 | if ($jy < $jp || $jy >= $breaks[$breaksCount - 1]) { 182 | throw new \InvalidArgumentException('Invalid Jalali year : ' . $jy); 183 | } 184 | 185 | $jump = 0; 186 | 187 | for ($i = 1; $i < $breaksCount; $i += 1) { 188 | $jm = $breaks[$i]; 189 | $jump = $jm - $jp; 190 | 191 | if ($jy < $jm) { 192 | break; 193 | } 194 | 195 | $leapJ = $leapJ + self::div($jump, 33) * 8 + self::div(self::mod($jump, 33), 4); 196 | 197 | $jp = $jm; 198 | } 199 | 200 | $n = $jy - $jp; 201 | 202 | $leapJ = $leapJ + self::div($n, 33) * 8 + self::div(self::mod($n, 33) + 3, 4); 203 | 204 | if (self::mod($jump, 33) === 4 && $jump - $n === 4) { 205 | $leapJ += 1; 206 | } 207 | 208 | $leapG = self::div($gy, 4) - self::div((self::div($gy, 100) + 1) * 3, 4) - 150; 209 | 210 | $march = 20 + $leapJ - $leapG; 211 | 212 | if ($jump - $n < 6) { 213 | $n = $n - $jump + self::div($jump + 4, 33) * 33; 214 | } 215 | 216 | $leap = self::mod(self::mod($n + 1, 33) - 1, 4); 217 | 218 | if ($leap === -1) { 219 | $leap = 4; 220 | } 221 | 222 | return [ 223 | 'leap' => $leap, 224 | 'gy' => $gy, 225 | 'march' => $march 226 | ]; 227 | } 228 | 229 | /** 230 | * @param $a 231 | * @param $b 232 | */ 233 | public static function div($a, $b): int 234 | { 235 | return intdiv($a, $b); 236 | } 237 | 238 | /** 239 | * @param $a 240 | * @param $b 241 | * @return mixed 242 | */ 243 | public static function mod($a, $b): int 244 | { 245 | return $a - intdiv($a, $b) * $b; 246 | } 247 | 248 | /** 249 | * @param $jdn 250 | * @return array 251 | */ 252 | public static function d2g($jdn) 253 | { 254 | $j = 4 * $jdn + 139361631; 255 | $j += self::div(self::div(4 * $jdn + 183187720, 146097) * 3, 4) * 4 - 3908; 256 | $i = self::div(self::mod($j, 1461), 4) * 5 + 308; 257 | 258 | $gd = self::div(self::mod($i, 153), 5) + 1; 259 | $gm = self::mod(self::div($i, 153), 12) + 1; 260 | $gy = self::div($j, 1461) - 100100 + self::div(8 - $gm, 6); 261 | 262 | return [$gy, $gm, $gd]; 263 | } 264 | 265 | /** 266 | * Calculates the Julian Day number from Gregorian or Julian 267 | * calendar dates. This integer number corresponds to the noon of 268 | * the date (i.e. 12 hours of Universal Time). 269 | * The procedure was tested to be good since 1 March, -100100 (of both 270 | * calendars) up to a few million years into the future. 271 | * 272 | * @param int $gy Calendar year (years BC numbered 0, -1, -2, ...) 273 | * @param int $gm Calendar month (1 to 12) 274 | * @param int $gd Calendar day of the month (1 to 28/29/30/31) 275 | * @return int Julian Day number 276 | */ 277 | public static function g2d($gy, $gm, $gd) 278 | { 279 | return (self::div(($gy + self::div($gm - 8, 6) + 100100) * 1461, 4) 280 | + self::div(153 * self::mod($gm + 9, 12) + 2, 5) 281 | + $gd - 34840408 282 | ) - self::div(self::div($gy + 100100 + self::div($gm - 8, 6), 100) * 3, 4) + 752; 283 | } 284 | 285 | /** 286 | * Converts a date of the Jalaali calendar to the Julian Day number. 287 | * 288 | * @param int $jy Jalaali year (1 to 3100) 289 | * @param int $jm Jalaali month (1 to 12) 290 | * @param int $jd Jalaali day (1 to 29/31) 291 | * @return int Julian Day number 292 | */ 293 | public static function j2d($jy, $jm, $jd) 294 | { 295 | $jCal = self::jalaliCal($jy); 296 | 297 | return self::g2d($jCal['gy'], 3, $jCal['march']) + ($jm - 1) * 31 - self::div($jm, 7) * ($jm - 7) + $jd - 1; 298 | } 299 | 300 | 301 | /** 302 | * Converts the Julian Day number to a date in the Jalaali calendar. 303 | * 304 | * @param int $jdn Julian Day number 305 | * @return array 306 | * 0: Jalaali year (1 to 3100) 307 | * 1: Jalaali month (1 to 12) 308 | * 2: Jalaali day (1 to 29/31) 309 | */ 310 | public static function d2j($jdn) 311 | { 312 | $gy = self::d2g($jdn)[0]; 313 | $jy = $gy - 621; 314 | $jCal = self::jalaliCal($jy); 315 | $jdn1f = self::g2d($gy, 3, $jCal['march']); 316 | 317 | $k = $jdn - $jdn1f; 318 | 319 | if ($k >= 0) { 320 | if ($k <= 185) { 321 | $jm = 1 + self::div($k, 31); 322 | $jd = self::mod($k, 31) + 1; 323 | 324 | return [$jy, $jm, $jd]; 325 | } else { 326 | $k -= 186; 327 | } 328 | } else { 329 | $jy -= 1; 330 | $k += 179; 331 | 332 | if ($jCal['leap'] === 1) { 333 | $k += 1; 334 | } 335 | } 336 | 337 | $jm = 7 + self::div($k, 30); 338 | $jd = self::mod($k, 30) + 1; 339 | 340 | return [$jy, $jm, $jd]; 341 | } 342 | 343 | /** 344 | * @param $format 345 | * @param bool $stamp 346 | * @param bool $timezone 347 | * @return mixed 348 | */ 349 | public static function date($format, $stamp = false, $timezone = null) 350 | { 351 | $stamp = ($stamp !== false) ? $stamp : time(); 352 | $dateTime = static::createDateTime($stamp, $timezone); 353 | 354 | 355 | //Find what to replace 356 | $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array(); 357 | 358 | //Intact Keys 359 | $intact = array('B', 'h', 'H', 'g', 'G', 'i', 's', 'I', 'U', 'u', 'Z', 'O', 'P'); 360 | $intact = self::filterArray($chars, $intact); 361 | $intactValues = array(); 362 | 363 | foreach ($intact as $k => $v) { 364 | $intactValues[$k] = $dateTime->format($v); 365 | } 366 | //End Intact Keys 367 | 368 | //Changed Keys 369 | list($year, $month, $day) = array($dateTime->format('Y'), $dateTime->format('n'), $dateTime->format('j')); 370 | list($jYear, $jMonth, $jDay) = self::toJalali($year, $month, $day); 371 | 372 | $keys = array( 373 | 'd', 374 | 'D', 375 | 'j', 376 | 'l', 377 | 'N', 378 | 'S', 379 | 'w', 380 | 'z', 381 | 'W', 382 | 'F', 383 | 'm', 384 | 'M', 385 | 'n', 386 | 't', 387 | 'L', 388 | 'o', 389 | 'Y', 390 | 'y', 391 | 'a', 392 | 'A', 393 | 'c', 394 | 'r', 395 | 'e', 396 | 'T' 397 | ); 398 | $keys = self::filterArray($chars, $keys, array('z')); 399 | $values = array(); 400 | 401 | foreach ($keys as $k => $key) { 402 | $v = ''; 403 | switch ($key) { 404 | //Day 405 | case 'd': 406 | $v = sprintf("%02d", $jDay); 407 | break; 408 | case 'D': 409 | $v = self::getDayNames($dateTime->format('D'), true); 410 | break; 411 | case 'j': 412 | $v = $jDay; 413 | break; 414 | case 'l': 415 | $v = self::getDayNames($dateTime->format('l')); 416 | break; 417 | case 'N': 418 | $v = self::getDayNames($dateTime->format('l'), false, 1, true); 419 | break; 420 | case 'S': 421 | $v = 'ام'; 422 | break; 423 | case 'w': 424 | $v = self::getDayNames($dateTime->format('l'), false, 1, true) - 1; 425 | break; 426 | case 'z': 427 | if ($jMonth > 6) { 428 | $v = 186 + (($jMonth - 6 - 1) * 30) + $jDay; 429 | } else { 430 | $v = (($jMonth - 1) * 31) + $jDay; 431 | } 432 | self::$temp['z'] = $v; 433 | break; 434 | //Week 435 | case 'W': 436 | $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1); 437 | break; 438 | //Month 439 | case 'F': 440 | $v = self::getMonthName($jMonth); 441 | break; 442 | case 'm': 443 | $v = sprintf("%02d", $jMonth); 444 | break; 445 | case 'M': 446 | $v = self::getMonthName($jMonth, true); 447 | break; 448 | case 'n': 449 | $v = $jMonth; 450 | break; 451 | case 't': 452 | $v = ($jMonth == 12) ? (self::isLeapJalaliYear($jYear) ? 30 : 29) : ($jMonth > 6 ? 30 : 31); 453 | break; 454 | //Year 455 | case 'L': 456 | $tmpObj = static::createDateTime(time() - 31536000, $timezone); 457 | $v = $tmpObj->format('L'); 458 | break; 459 | case 'o': 460 | case 'Y': 461 | $v = $jYear; 462 | break; 463 | case 'y': 464 | $v = $jYear % 100; 465 | if ($v < 10) { 466 | $v = '0' . $v; 467 | } 468 | break; 469 | //Time 470 | case 'a': 471 | $v = ($dateTime->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ'; 472 | break; 473 | case 'A': 474 | $v = ($dateTime->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر'; 475 | break; 476 | //Full Dates 477 | case 'c': 478 | $v = $jYear . '-' . sprintf("%02d", $jMonth) . '-' . sprintf("%02d", $jDay) . 'T'; 479 | $v .= $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . $dateTime->format('P'); 480 | break; 481 | case 'r': 482 | $v = self::getDayNames($dateTime->format('D'), true) . ', ' . sprintf( 483 | "%02d", 484 | $jDay 485 | ) . ' ' . self::getMonthName($jMonth, true); 486 | $v .= ' ' . $jYear . ' ' . $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . ' ' . $dateTime->format('P'); 487 | break; 488 | //Timezone 489 | case 'e': 490 | $v = $dateTime->format('e'); 491 | break; 492 | case 'T': 493 | $v = $dateTime->format('T'); 494 | break; 495 | } 496 | $values[$k] = $v; 497 | } 498 | //End Changed Keys 499 | 500 | //Merge 501 | $keys = array_merge($intact, $keys); 502 | $values = array_merge($intactValues, $values); 503 | 504 | return strtr($format, array_combine($keys, $values)); 505 | } 506 | 507 | /** 508 | * @param $format 509 | * @param bool $stamp 510 | * @param null $timezone 511 | * @return mixed 512 | */ 513 | public static function strftime($format, $stamp = false, $timezone = null) 514 | { 515 | $str_format_code = array( 516 | "%a", 517 | "%A", 518 | "%d", 519 | "%e", 520 | "%j", 521 | "%u", 522 | "%w", 523 | "%U", 524 | "%V", 525 | "%W", 526 | "%b", 527 | "%B", 528 | "%h", 529 | "%m", 530 | "%C", 531 | "%g", 532 | "%G", 533 | "%y", 534 | "%Y", 535 | "%H", 536 | "%I", 537 | "%l", 538 | "%M", 539 | "%p", 540 | "%P", 541 | "%r", 542 | "%R", 543 | "%S", 544 | "%T", 545 | "%X", 546 | "%z", 547 | "%Z", 548 | "%c", 549 | "%D", 550 | "%F", 551 | "%s", 552 | "%x", 553 | "%n", 554 | "%t", 555 | "%%", 556 | ); 557 | 558 | $date_format_code = array( 559 | "D", 560 | "l", 561 | "d", 562 | "j", 563 | "z", 564 | "N", 565 | "w", 566 | "W", 567 | "W", 568 | "W", 569 | "M", 570 | "F", 571 | "M", 572 | "m", 573 | "y", 574 | "y", 575 | "y", 576 | "y", 577 | "Y", 578 | "H", 579 | "h", 580 | "g", 581 | "i", 582 | "A", 583 | "a", 584 | "h:i:s A", 585 | "H:i", 586 | "s", 587 | "H:i:s", 588 | "h:i:s", 589 | "H", 590 | "H", 591 | "D j M H:i:s", 592 | "d/m/y", 593 | "Y-m-d", 594 | "U", 595 | "d/m/y", 596 | "\n", 597 | "\t", 598 | "%", 599 | ); 600 | 601 | //Change Strftime format to Date format 602 | $format = str_replace($str_format_code, $date_format_code, $format); 603 | 604 | //Convert to date 605 | return self::date($format, $stamp, $timezone); 606 | } 607 | 608 | private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false) 609 | { 610 | switch (strtolower($day)) { 611 | case 'sat': 612 | case 'saturday': 613 | $ret = 'شنبه'; 614 | $n = 1; 615 | break; 616 | case 'sun': 617 | case 'sunday': 618 | $ret = 'یکشنبه'; 619 | $n = 2; 620 | break; 621 | case 'mon': 622 | case 'monday': 623 | $ret = 'دوشنبه'; 624 | $n = 3; 625 | break; 626 | case 'tue': 627 | case 'tuesday': 628 | $ret = 'سه‌شنبه'; 629 | $n = 4; 630 | break; 631 | case 'wed': 632 | case 'wednesday': 633 | $ret = 'چهارشنبه'; 634 | $n = 5; 635 | break; 636 | case 'thu': 637 | case 'thursday': 638 | $ret = 'پنج‌شنبه'; 639 | $n = 6; 640 | break; 641 | case 'fri': 642 | case 'friday': 643 | $ret = 'جمعه'; 644 | $n = 7; 645 | break; 646 | default: 647 | $ret = ''; 648 | $n = -1; 649 | } 650 | 651 | return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret); 652 | } 653 | 654 | private static function getMonthName($month, $shorten = false, $len = 3) 655 | { 656 | $monthIndex = ((int)$month) -1 ; 657 | $monthName = static::$monthNames[$monthIndex]; 658 | return ($shorten) ? mb_substr($monthName, 0, $len, 'UTF-8') : $monthName; 659 | } 660 | 661 | private static function filterArray($needle, $haystack, $always = array()) 662 | { 663 | foreach ($haystack as $k => $v) { 664 | if (!in_array($v, $needle) && !in_array($v, $always)) { 665 | unset($haystack[$k]); 666 | } 667 | } 668 | 669 | 670 | return $haystack; 671 | } 672 | 673 | 674 | /** 675 | * @param $format 676 | * @param $date 677 | * @return array 678 | */ 679 | public static function parseFromFormat($format, $date) 680 | { 681 | // reverse engineer date formats 682 | $keys = array( 683 | 'Y' => array('year', '\d{4}'), 684 | 'y' => array('year', '\d{2}'), 685 | 'm' => array('month', '\d{2}'), 686 | 'n' => array('month', '\d{1,2}'), 687 | 'M' => array('month', '[A-Z][a-z]{3}'), 688 | 'F' => array('month', '[A-Z][a-z]{2,8}'), 689 | 'd' => array('day', '\d{2}'), 690 | 'j' => array('day', '\d{1,2}'), 691 | 'D' => array('day', '[A-Z][a-z]{2}'), 692 | 'l' => array('day', '[A-Z][a-z]{6,9}'), 693 | 'u' => array('hour', '\d{1,6}'), 694 | 'h' => array('hour', '\d{2}'), 695 | 'H' => array('hour', '\d{2}'), 696 | 'g' => array('hour', '\d{1,2}'), 697 | 'G' => array('hour', '\d{1,2}'), 698 | 'i' => array('minute', '\d{2}'), 699 | 's' => array('second', '\d{2}'), 700 | ); 701 | 702 | // convert format string to regex 703 | $regex = ''; 704 | $chars = str_split($format); 705 | foreach ($chars as $n => $char) { 706 | $lastChar = isset($chars[$n - 1]) ? $chars[$n - 1] : ''; 707 | $skipCurrent = '\\' == $lastChar; 708 | if (!$skipCurrent && isset($keys[$char])) { 709 | $regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')'; 710 | } else { 711 | if ('\\' == $char) { 712 | $regex .= $char; 713 | } else { 714 | $regex .= preg_quote($char); 715 | } 716 | } 717 | } 718 | 719 | $dt = array(); 720 | $dt['error_count'] = 0; 721 | // now try to match it 722 | if (preg_match('#^' . $regex . '$#', $date, $dt)) { 723 | foreach ($dt as $k => $v) { 724 | if (is_int($k)) { 725 | unset($dt[$k]); 726 | } 727 | } 728 | if (!CalendarUtils::checkdate($dt['month'], $dt['day'], $dt['year'], false)) { 729 | $dt['error_count'] = 1; 730 | } 731 | } else { 732 | $dt['error_count'] = 1; 733 | } 734 | $dt['errors'] = array(); 735 | $dt['fraction'] = ''; 736 | $dt['warning_count'] = 0; 737 | $dt['warnings'] = array(); 738 | $dt['is_localtime'] = 0; 739 | $dt['zone_type'] = 0; 740 | $dt['zone'] = 0; 741 | $dt['is_dst'] = ''; 742 | 743 | if (strlen($dt['year']) == 2) { 744 | $now = Jalalian::forge('now'); 745 | $x = $now->format('Y') - $now->format('y'); 746 | $dt['year'] += $x; 747 | } 748 | 749 | $dt['year'] = isset($dt['year']) ? (int)$dt['year'] : 0; 750 | $dt['month'] = isset($dt['month']) ? (int)$dt['month'] : 0; 751 | $dt['day'] = isset($dt['day']) ? (int)$dt['day'] : 0; 752 | $dt['hour'] = isset($dt['hour']) ? (int)$dt['hour'] : 0; 753 | $dt['minute'] = isset($dt['minute']) ? (int)$dt['minute'] : 0; 754 | $dt['second'] = isset($dt['second']) ? (int)$dt['second'] : 0; 755 | 756 | return $dt; 757 | } 758 | 759 | /** 760 | * @param $format 761 | * @param $str 762 | * @param null $timezone 763 | * @return \DateTime 764 | */ 765 | public static function createDatetimeFromFormat($format, $str, $timezone = null) 766 | { 767 | $pd = self::parseFromFormat($format, $str); 768 | $gd = self::toGregorian($pd['year'], $pd['month'], $pd['day']); 769 | $date = self::createDateTime('now', $timezone); 770 | $date->setDate($gd[0], $gd[1], $gd[2]); 771 | $date->setTime($pd['hour'], $pd['minute'], $pd['second']); 772 | 773 | return $date; 774 | } 775 | 776 | /** 777 | * @param $format 778 | * @param $str 779 | * @param null $timezone 780 | * @return Carbon 781 | */ 782 | public static function createCarbonFromFormat($format, $str, $timezone = null) 783 | { 784 | $dateTime = self::createDatetimeFromFormat($format, $str, $timezone); 785 | 786 | return Carbon::createFromTimestamp($dateTime->getTimestamp(), $dateTime->getTimezone()); 787 | } 788 | 789 | /** 790 | * Convert Latin numbers to persian numbers and vice versa 791 | * 792 | * @param string $string 793 | * @param boolean $toEnglish, default is false to save compatiblity 794 | * @return string 795 | */ 796 | public static function convertNumbers($string, $toLatin = false) 797 | { 798 | $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"); 799 | $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); 800 | if (!$toLatin) { 801 | return str_replace($english_array, $farsi_array, $string); 802 | } 803 | return str_replace($farsi_array, $english_array, $string); 804 | } 805 | 806 | /** 807 | * @param $timestamp 808 | * @param null $timezone 809 | * @return \DateTime|static 810 | */ 811 | public static function createDateTime($timestamp = null, $timezone = null) 812 | { 813 | $timezone = static::createTimeZone($timezone); 814 | 815 | if ($timestamp === null) { 816 | return Carbon::now($timezone); 817 | } 818 | 819 | 820 | if ($timestamp instanceof \DateTimeInterface) { 821 | return $timestamp; 822 | } 823 | 824 | if (is_string($timestamp)) { 825 | return new \DateTime($timestamp, $timezone); 826 | } 827 | 828 | if (is_numeric($timestamp)) { 829 | return Carbon::createFromTimestamp($timestamp, $timezone); 830 | } 831 | 832 | 833 | throw new \InvalidArgumentException('timestamp is not valid'); 834 | } 835 | 836 | /** 837 | * @param null $timezone 838 | * @return \DateTimeZone|null 839 | */ 840 | public static function createTimeZone($timezone = null) 841 | { 842 | if ($timezone instanceof \DateTimeZone) { 843 | return $timezone; 844 | } 845 | 846 | if ($timezone === null) { 847 | return new \DateTimeZone(date_default_timezone_get()); 848 | } 849 | 850 | if (is_string($timezone)) { 851 | return new \DateTimeZone($timezone); 852 | } 853 | 854 | 855 | throw new \InvalidArgumentException('timezone is not valid'); 856 | } 857 | } 858 | -------------------------------------------------------------------------------- /src/Converter.php: -------------------------------------------------------------------------------- 1 | format("Y/m/d"); 25 | } 26 | 27 | /** 28 | * Format the instance as a readable date 29 | * 30 | * @return string 31 | */ 32 | public function toFormattedDateString() 33 | { 34 | return $this->format('j F Y'); 35 | } 36 | 37 | /** 38 | * Format the instance with the day, and a readable date 39 | * 40 | * @return string 41 | */ 42 | public function toFormattedDayDateString() 43 | { 44 | return $this->format('l j F Y'); 45 | } 46 | 47 | /** 48 | * Format the instance as time 49 | * 50 | * @param string $unitPrecision 51 | * 52 | * @return string 53 | */ 54 | public function toTimeString($unitPrecision = 'second') 55 | { 56 | return $this->format(static::getTimeFormatByPrecision($unitPrecision)); 57 | } 58 | 59 | /** 60 | * Format the instance as date and time 61 | * 62 | * @param string $unitPrecision 63 | * 64 | * @return string 65 | */ 66 | public function toDateTimeString($unitPrecision = 'second') 67 | { 68 | return $this->format('Y/m/d ' . static::getTimeFormatByPrecision($unitPrecision)); 69 | } 70 | 71 | /** 72 | * Format the instance as a readable date and time 73 | * 74 | * @param string $unitPrecision 75 | * 76 | * @return string 77 | */ 78 | public function toFormattedDateTimeString($unitPrecision = 'second') 79 | { 80 | return $this->format('j F Y ' . static::getTimeFormatByPrecision($unitPrecision)); 81 | } 82 | 83 | /** 84 | * Return a format from H:i to H:i:s.u according to given unit precision. 85 | * 86 | * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" 87 | * 88 | * @return string 89 | */ 90 | public static function getTimeFormatByPrecision($unitPrecision) 91 | { 92 | switch (Date::singularUnit($unitPrecision)) { 93 | case 'minute': 94 | return 'H:i'; 95 | case 'second': 96 | return 'H:i:s'; 97 | case 'm': 98 | case 'millisecond': 99 | return 'H:i:s.v'; 100 | case 'µ': 101 | case 'microsecond': 102 | return 'H:i:s.u'; 103 | } 104 | 105 | throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); 106 | } 107 | 108 | /** 109 | * Format the instance as date and time T-separated with no timezone 110 | * echo Jalalian::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond 111 | * ``` 112 | * 113 | * @param string $unitPrecision 114 | * 115 | * @return string 116 | */ 117 | public function toDateTimeLocalString($unitPrecision = 'second') 118 | { 119 | return $this->format('Y-m-d\T' . static::getTimeFormatByPrecision($unitPrecision)); 120 | } 121 | 122 | /** 123 | * Format the instance with day, date and time 124 | * 125 | * @param string $unitPrecision 126 | * 127 | * @return string 128 | */ 129 | public function toDayDateTimeString($unitPrecision = 'second') 130 | { 131 | return $this->format('l j F Y ' . static::getTimeFormatByPrecision($unitPrecision)); 132 | } 133 | 134 | /** 135 | * Format the instance with the year, and a readable month 136 | * 137 | * @return string 138 | */ 139 | public function toFormattedMonthYearString() 140 | { 141 | return $this->format('F Y'); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/Jalalian.php: -------------------------------------------------------------------------------- 1 | 6) { 62 | Assertion::between($day, 1, 30); 63 | } 64 | 65 | if (!CalendarUtils::isLeapJalaliYear($year) && $month === 12) { 66 | Assertion::between($day, 1, 29); 67 | } 68 | Assertion::between($hour, 0, 24); 69 | Assertion::between($minute, 0, 59); 70 | Assertion::between($second, 0, 59); 71 | 72 | $this->year = $year; 73 | $this->month = $month; 74 | $this->day = $day; 75 | $this->hour = $hour; 76 | $this->minute = $minute; 77 | $this->second = $second; 78 | $this->timezone = $timezone; 79 | } 80 | 81 | public static function now(?\DateTimeZone $timeZone = null): Jalalian 82 | { 83 | return static::fromCarbon(Carbon::now($timeZone)); 84 | } 85 | 86 | /** 87 | * @param Carbon $carbon 88 | * @return Jalalian 89 | */ 90 | public static function fromCarbon(Carbon $carbon): Jalalian 91 | { 92 | $jDate = CalendarUtils::toJalali($carbon->year, $carbon->month, $carbon->day); 93 | 94 | return new static( 95 | $jDate[0], 96 | $jDate[1], 97 | $jDate[2], 98 | $carbon->hour, 99 | $carbon->minute, 100 | $carbon->second, 101 | $carbon->getTimezone() 102 | ); 103 | } 104 | 105 | public static function fromFormat(string $format, string $timestamp, ?\DateTimeZone $timeZone = null): Jalalian 106 | { 107 | return static::fromCarbon(CalendarUtils::createCarbonFromFormat($format, $timestamp, $timeZone)); 108 | } 109 | 110 | public static function forge($timestamp, ?\DateTimeZone $timeZone = null): Jalalian 111 | { 112 | return static::fromDateTime($timestamp, $timeZone); 113 | } 114 | 115 | /** 116 | * @param \DateTimeInterface| string $dateTime 117 | * @param \DateTimeZone|null $timeZone 118 | * @return Jalalian 119 | */ 120 | public static function fromDateTime($dateTime, ?\DateTimeZone $timeZone = null): Jalalian 121 | { 122 | if (is_numeric($dateTime)) { 123 | return static::fromCarbon(Carbon::createFromTimestamp($dateTime, $timeZone)); 124 | } 125 | 126 | return static::fromCarbon(new Carbon($dateTime, $timeZone)); 127 | } 128 | 129 | public function getFirstDayOfWeek(): Jalalian 130 | { 131 | return (new static( 132 | $this->getYear(), 133 | $this->getMonth(), 134 | $this->getDay(), 135 | $this->getHour(), 136 | $this->getMinute(), 137 | $this->getSecond(), 138 | $this->getTimezone() 139 | ))->subDays($this->getDayOfWeek()); 140 | } 141 | 142 | public function getFirstDayOfMonth(): Jalalian 143 | { 144 | return new static( 145 | $this->getYear(), 146 | $this->getMonth(), 147 | 1, 148 | $this->getHour(), 149 | $this->getMinute(), 150 | $this->getSecond(), 151 | $this->getTimezone() 152 | ); 153 | } 154 | 155 | public function getFirstDayOfYear(): Jalalian 156 | { 157 | return new static( 158 | $this->getYear(), 159 | 1, 160 | 1, 161 | $this->getHour(), 162 | $this->getMinute(), 163 | $this->getSecond(), 164 | $this->getTimezone() 165 | ); 166 | } 167 | 168 | public function getFirstDayOfQuarter(): Jalalian 169 | { 170 | return new static( 171 | $this->getYear(), 172 | ($this->getQuarter() - 1) * Carbon::MONTHS_PER_QUARTER + 1, 173 | 1, 174 | $this->getHour(), 175 | $this->getMinute(), 176 | $this->getSecond(), 177 | $this->getTimezone() 178 | ); 179 | } 180 | 181 | public function getEndDayOfWeek(): Jalalian 182 | { 183 | $endWeek = $this->addDays(6 - $this->getDayOfWeek()); 184 | 185 | return (new static( 186 | $endWeek->getYear(), 187 | $endWeek->getMonth(), 188 | $endWeek->getDay(), 189 | $endWeek->getHour(), 190 | $endWeek->getMinute(), 191 | $endWeek->getSecond(), 192 | $endWeek->getTimezone() 193 | )); 194 | } 195 | 196 | public function getEndDayOfMonth(): Jalalian 197 | { 198 | return new static( 199 | $this->getYear(), 200 | $this->getMonth(), 201 | $this->getDaysOf($this->getMonth()), 202 | $this->getHour(), 203 | $this->getMinute(), 204 | $this->getSecond(), 205 | $this->getTimezone() 206 | ); 207 | } 208 | 209 | public function getEndDayOfYear(): Jalalian 210 | { 211 | return new static( 212 | $this->getYear(), 213 | 12, 214 | $this->getDaysOf(12), 215 | $this->getHour(), 216 | $this->getMinute(), 217 | $this->getSecond(), 218 | $this->getTimezone() 219 | ); 220 | } 221 | 222 | public function getEndDayOfQuarter(): Jalalian 223 | { 224 | return new static( 225 | $this->getYear(), 226 | $this->getQuarter() * Carbon::MONTHS_PER_QUARTER, 227 | $this->getDaysOf($this->getQuarter() * Carbon::MONTHS_PER_QUARTER), 228 | $this->getHour(), 229 | $this->getMinute(), 230 | $this->getSecond(), 231 | $this->getTimezone() 232 | ); 233 | } 234 | 235 | public function getMonthDays() 236 | { 237 | if ($this->getMonth() <= 6) { 238 | return 31; 239 | } 240 | 241 | if ($this->getMonth() < 12 || $this->isLeapYear()) { 242 | return 30; 243 | } 244 | 245 | return 29; 246 | } 247 | 248 | /** 249 | * @return int 250 | */ 251 | public function getMonth(): int 252 | { 253 | return $this->month; 254 | } 255 | 256 | public function isLeapYear(): bool 257 | { 258 | return CalendarUtils::isLeapJalaliYear($this->getYear()); 259 | } 260 | 261 | /** 262 | * @return int 263 | */ 264 | public function getYear() 265 | { 266 | return $this->year; 267 | } 268 | 269 | public function getQuarter(): int 270 | { 271 | return (int) ceil($this->getMonth() / Carbon::MONTHS_PER_QUARTER); 272 | } 273 | 274 | public function subMonths(int $months = 1): Jalalian 275 | { 276 | Assertion::greaterOrEqualThan($months, 1); 277 | 278 | $diff = ($this->getMonth() - $months); 279 | 280 | if ($diff >= 1) { 281 | $day = $this->getDay(); 282 | $targetMonthDays = $this->getDaysOf($diff); 283 | $targetDay = $day <= $targetMonthDays ? $day : $targetMonthDays; 284 | 285 | return new static( 286 | $this->getYear(), 287 | $diff, 288 | $targetDay, 289 | $this->getHour(), 290 | $this->getMinute(), 291 | $this->getSecond(), 292 | $this->getTimezone() 293 | ); 294 | } 295 | 296 | $years = abs((int)($diff / 12)); 297 | $date = $years > 0 ? $this->subYears($years) : clone $this; 298 | $diff = 12 - abs($diff % 12) - $date->getMonth(); 299 | 300 | return $diff > 0 ? $date->subYears(1)->addMonths($diff) : $date->subYears(1); 301 | } 302 | 303 | /** 304 | * @return int 305 | */ 306 | public function getDay(): int 307 | { 308 | return $this->day; 309 | } 310 | 311 | public function getDaysOf(int $monthNumber = 1): int 312 | { 313 | Assertion::between($monthNumber, 1, 12); 314 | 315 | $months = [ 316 | 1 => 31, 317 | 2 => 31, 318 | 3 => 31, 319 | 4 => 31, 320 | 5 => 31, 321 | 6 => 31, 322 | 7 => 30, 323 | 8 => 30, 324 | 9 => 30, 325 | 10 => 30, 326 | 11 => 30, 327 | 12 => $this->isLeapYear() ? 30 : 29, 328 | ]; 329 | 330 | return $months[$monthNumber]; 331 | } 332 | 333 | /** 334 | * @return int 335 | */ 336 | public function getHour(): int 337 | { 338 | return $this->hour; 339 | } 340 | 341 | /** 342 | * @return int 343 | */ 344 | public function getMinute(): int 345 | { 346 | return $this->minute; 347 | } 348 | 349 | /** 350 | * @return int 351 | */ 352 | public function getSecond(): int 353 | { 354 | return $this->second; 355 | } 356 | 357 | /** 358 | * @return \DateTimeZone|null 359 | */ 360 | public function getTimezone() 361 | { 362 | return $this->timezone; 363 | } 364 | 365 | public function subYears(int $years = 1): Jalalian 366 | { 367 | Assertion::greaterOrEqualThan($years, 1); 368 | 369 | return new static( 370 | $this->getYear() - $years, 371 | $this->getMonth(), 372 | $this->getDay(), 373 | $this->getHour(), 374 | $this->getMinute(), 375 | $this->getSecond(), 376 | $this->getTimezone() 377 | ); 378 | } 379 | 380 | public function addMonths(int $months = 1): Jalalian 381 | { 382 | Assertion::greaterOrEqualThan($months, 1); 383 | 384 | $years = (int)($months / 12); 385 | $months = (int)($months % 12); 386 | $date = $years > 0 ? $this->addYears($years) : clone $this; 387 | 388 | while ($months > 0) { 389 | $nextMonth = ($date->getMonth() + 1) % 12; 390 | $nextMonthDays = $date->getDaysOf($nextMonth === 0 ? 12 : $nextMonth); 391 | $nextMonthDay = $date->getDay() <= $nextMonthDays ? $date->getDay() : $nextMonthDays; 392 | 393 | $days = ($date->getMonthDays() - $date->getDay()) + $nextMonthDay; 394 | 395 | $date = $date->addDays($days); 396 | $months--; 397 | } 398 | 399 | return $date; 400 | } 401 | 402 | public function addYears(int $years = 1): Jalalian 403 | { 404 | Assertion::greaterOrEqualThan($years, 1); 405 | 406 | $year = $this->getYear() + $years; 407 | if (false === CalendarUtils::isLeapJalaliYear($year) && $this->getMonth() === 12 && $this->getDay() === $this->getDaysOf(12)) { 408 | $day = 29; 409 | } else { 410 | $day = $this->getDay(); 411 | } 412 | 413 | return new static( 414 | $year, 415 | $this->getMonth(), 416 | $day, 417 | $this->getHour(), 418 | $this->getMinute(), 419 | $this->getSecond(), 420 | $this->getTimezone() 421 | ); 422 | } 423 | 424 | public function subDays(int $days = 1): Jalalian 425 | { 426 | return static::fromCarbon($this->toCarbon()->subDays($days)); 427 | } 428 | 429 | public function subDay(): Jalalian 430 | { 431 | return $this->subDays(1); 432 | } 433 | 434 | /** 435 | * @return Carbon 436 | */ 437 | public function toCarbon(): Carbon 438 | { 439 | $gDate = CalendarUtils::toGregorian($this->getYear(), $this->getMonth(), $this->getDay()); 440 | $carbon = Carbon::createFromDate($gDate[0], $gDate[1], $gDate[2], $this->getTimezone()); 441 | 442 | $carbon->setTime($this->getHour(), $this->getMinute(), $this->getSecond()); 443 | 444 | return $carbon; 445 | } 446 | 447 | public function addHours(int $hours = 1): Jalalian 448 | { 449 | return static::fromCarbon($this->toCarbon()->addHours($hours)); 450 | } 451 | 452 | public function subHours(int $hours = 1): Jalalian 453 | { 454 | return static::fromCarbon($this->toCarbon()->subHours($hours)); 455 | } 456 | 457 | public function addMinutes(int $minutes = 1): Jalalian 458 | { 459 | return static::fromCarbon($this->toCarbon()->addMinutes($minutes)); 460 | } 461 | 462 | public function subMinutes(int $minutes = 1): Jalalian 463 | { 464 | return static::fromCarbon($this->toCarbon()->subMinutes($minutes)); 465 | } 466 | 467 | public function addSeconds(int $secs = 1): Jalalian 468 | { 469 | return static::fromCarbon($this->toCarbon()->addSeconds($secs)); 470 | } 471 | 472 | public function subSeconds(int $secs = 1): Jalalian 473 | { 474 | return static::fromCarbon($this->toCarbon()->subSeconds($secs)); 475 | } 476 | 477 | public function equalsTo(Jalalian $other): bool 478 | { 479 | return $this->equalsToCarbon($other->toCarbon()); 480 | } 481 | 482 | public function equalsToCarbon(Carbon $carbon): bool 483 | { 484 | return $this->toCarbon()->equalTo($carbon); 485 | } 486 | 487 | public function greaterThan(Jalalian $other): bool 488 | { 489 | return $this->greaterThanCarbon($other->toCarbon()); 490 | } 491 | 492 | public function greaterThanCarbon(Carbon $carbon): bool 493 | { 494 | return $this->toCarbon()->greaterThan($carbon); 495 | } 496 | 497 | public function lessThan(Jalalian $other): bool 498 | { 499 | return $this->lessThanCarbon($other->toCarbon()); 500 | } 501 | 502 | public function lessThanCarbon(Carbon $carbon): bool 503 | { 504 | return $this->toCarbon()->lessThan($carbon); 505 | } 506 | 507 | public function greaterThanOrEqualsTo(Jalalian $other): bool 508 | { 509 | return $this->greaterThanOrEqualsToCarbon($other->toCarbon()); 510 | } 511 | 512 | public function greaterThanOrEqualsToCarbon(Carbon $carbon): bool 513 | { 514 | return $this->toCarbon()->greaterThanOrEqualTo($carbon); 515 | } 516 | 517 | public function lessThanOrEqualsTo(Jalalian $other): bool 518 | { 519 | return $this->lessThanOrEqualsToCarbon($other->toCarbon()); 520 | } 521 | 522 | public function lessThanOrEqualsToCarbon(Carbon $carbon): bool 523 | { 524 | return $this->toCarbon()->lessThanOrEqualTo($carbon); 525 | } 526 | 527 | public function isStartOfWeek(): bool 528 | { 529 | return $this->isSaturday(); 530 | } 531 | 532 | public function isSaturday(): bool 533 | { 534 | return $this->isDayOfWeek(Carbon::SATURDAY); 535 | } 536 | 537 | public function isDayOfWeek(int $day): bool 538 | { 539 | Assertion::between($day, 0, 6); 540 | return $this->toCarbon()->isDayOfWeek($day); 541 | } 542 | 543 | public function isEndOfWeek(): bool 544 | { 545 | return $this->isFriday(); 546 | } 547 | 548 | public function isFriday(): bool 549 | { 550 | return $this->isDayOfWeek(Carbon::FRIDAY); 551 | } 552 | 553 | public function isToday(): bool 554 | { 555 | return $this->toCarbon()->isToday(); 556 | } 557 | 558 | public function isTomorrow(): bool 559 | { 560 | return $this->toCarbon()->isTomorrow(); 561 | } 562 | 563 | public function isYesterday(): bool 564 | { 565 | return $this->toCarbon()->isYesterday(); 566 | } 567 | 568 | public function isFuture(): bool 569 | { 570 | return $this->toCarbon()->isFuture(); 571 | } 572 | 573 | public function isPast(): bool 574 | { 575 | return $this->toCarbon()->isPast(); 576 | } 577 | 578 | public function toArray(): array 579 | { 580 | return [ 581 | 'year' => $this->year, 582 | 'month' => $this->month, 583 | 'day' => $this->day, 584 | 'dayOfWeek' => $this->getDayOfWeek(), 585 | 'dayOfYear' => $this->getDayOfYear(), 586 | 'hour' => $this->hour, 587 | 'minute' => $this->minute, 588 | 'second' => $this->second, 589 | 'micro' => $this->toCarbon()->micro, 590 | 'timestamp' => $this->toCarbon()->timestamp, 591 | 'formatted' => $this->toString(), 592 | 'timezone' => $this->timezone, 593 | ]; 594 | } 595 | 596 | public function getDayOfWeek(): int 597 | { 598 | if ($this->isSaturday()) { 599 | return 0; 600 | } 601 | 602 | if ($this->isSunday()) { 603 | return 1; 604 | } 605 | 606 | if ($this->isMonday()) { 607 | return 2; 608 | } 609 | 610 | if ($this->isTuesday()) { 611 | return 3; 612 | } 613 | 614 | if ($this->isWednesday()) { 615 | return 4; 616 | } 617 | 618 | if ($this->isThursday()) { 619 | return 5; 620 | } 621 | 622 | return 6; 623 | } 624 | 625 | public function isSunday(): bool 626 | { 627 | return $this->isDayOfWeek(Carbon::SUNDAY); 628 | } 629 | 630 | public function isMonday(): bool 631 | { 632 | return $this->isDayOfWeek(Carbon::MONDAY); 633 | } 634 | 635 | public function isTuesday(): bool 636 | { 637 | return $this->isDayOfWeek(Carbon::TUESDAY); 638 | } 639 | 640 | public function isWednesday(): bool 641 | { 642 | return $this->isDayOfWeek(Carbon::WEDNESDAY); 643 | } 644 | 645 | public function isThursday(): bool 646 | { 647 | return $this->isDayOfWeek(Carbon::THURSDAY); 648 | } 649 | 650 | public function getDayOfYear(): int 651 | { 652 | $dayOfYear = 0; 653 | for ($m = 1; $m < $this->getMonth(); $m++) { 654 | if ($m <= 6) { 655 | $dayOfYear += 31; 656 | continue; 657 | } 658 | 659 | if ($m < 12) { 660 | $dayOfYear += 30; 661 | continue; 662 | } 663 | } 664 | 665 | return $dayOfYear + $this->getDay(); 666 | } 667 | 668 | public function toString(): string 669 | { 670 | return $this->format('Y-m-d H:i:s'); 671 | } 672 | 673 | public function format(string $format): string 674 | { 675 | return CalendarUtils::strftime($format, $this->toCarbon()); 676 | } 677 | 678 | public function __toString(): string 679 | { 680 | return $this->toString(); 681 | } 682 | 683 | public function ago(): string 684 | { 685 | $now = time(); 686 | $time = $this->getTimestamp(); 687 | 688 | // catch error 689 | if (!$time) { 690 | return false; 691 | } 692 | 693 | // build period and length arrays 694 | $periods = ['ثانیه', 'دقیقه', 'ساعت', 'روز', 'هفته', 'ماه', 'سال', 'قرن']; 695 | $lengths = [60, 60, 24, 7, 4.35, 12, 10]; 696 | 697 | // get difference 698 | $difference = $now - $time; 699 | 700 | // set descriptor 701 | if ($difference < 0) { 702 | $difference = abs($difference); // absolute value 703 | $negative = true; 704 | } 705 | 706 | // do math 707 | for ($j = 0; $difference >= $lengths[$j] and $j < count($lengths) - 1; $j++) { 708 | $difference /= $lengths[$j]; 709 | } 710 | 711 | // round difference 712 | $difference = intval(round($difference)); 713 | 714 | // return 715 | return number_format($difference) . ' ' . $periods[$j] . ' ' . (isset($negative) ? '' : 'پیش'); 716 | } 717 | 718 | public function getTimestamp(): int 719 | { 720 | return $this->toCarbon()->getTimestamp(); 721 | } 722 | 723 | public function getNextWeek(): Jalalian 724 | { 725 | return $this->addDays(7); 726 | } 727 | 728 | public function getLastWeek(): Jalalian 729 | { 730 | return $this->subDays(7); 731 | } 732 | 733 | public function addDays(int $days = 1): Jalalian 734 | { 735 | return static::fromCarbon($this->toCarbon()->addDays($days)); 736 | } 737 | 738 | public function addDay(): Jalalian 739 | { 740 | return $this->addDays(1); 741 | } 742 | 743 | public function getNextMonth(): Jalalian 744 | { 745 | return $this->addMonths(1); 746 | } 747 | 748 | public function getLastMonth(): Jalalian 749 | { 750 | return $this->subMonths(1); 751 | } 752 | 753 | public function getWeekOfMonth(): int 754 | { 755 | return floor(($this->day + 5 - $this->getDayOfWeek()) / 7) + 1; 756 | } 757 | } 758 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | assertTrue(CalendarUtils::checkDate(1391, 2, 30, true)); 16 | $this->assertFalse(CalendarUtils::checkDate(1395, 13, 10, true)); 17 | $this->assertFalse(CalendarUtils::checkDate(1395, 12, 31, true)); 18 | $this->assertFalse(CalendarUtils::checkDate(2015, 12, 31, true)); 19 | } 20 | 21 | public function testToJalali() 22 | { 23 | $this->assertTrue(CalendarUtils::toJalali(2016, 5, 7) === [1395, 2, 18]); 24 | $this->assertFalse(CalendarUtils::toJalali(2015, 5, 7) === [1394, 2, 18]); 25 | } 26 | 27 | public function testToGregorian() 28 | { 29 | $this->assertTrue(CalendarUtils::toGregorian(1395, 2, 18) === [2016, 5, 7]); 30 | $this->assertFalse(CalendarUtils::toGregorian(1394, 2, 18) === [2015, 5, 7]); 31 | } 32 | 33 | public function testIsLeapJalaliYear() 34 | { 35 | $this->assertTrue(CalendarUtils::isLeapJalaliYear(1395)); 36 | $this->assertFalse(CalendarUtils::isLeapJalaliYear(1394)); 37 | } 38 | 39 | public function testStrftime() 40 | { 41 | $table = [ 42 | [ 43 | '2016-05-08', 44 | 'Y-m-d', 45 | '1395-02-19' 46 | ], 47 | [ 48 | '2022-03-24', 49 | 'y-m-d', 50 | '01-01-04' 51 | ], 52 | [ 53 | '2023-03-24', 54 | 'y-m-D', 55 | '02-01-ج' 56 | ], 57 | ]; 58 | 59 | foreach ($table as $row) { 60 | list($dateTimeString, $format, $expected) = $row; 61 | $timestamp = strtotime($dateTimeString); 62 | $this->assertEquals($expected, CalendarUtils::strftime($format, $timestamp)); 63 | } 64 | } 65 | 66 | public function testFormatMonthName() 67 | { 68 | $months = range(1, 12); 69 | 70 | // Should returns iranian months name as default 71 | foreach ($months as $month) { 72 | $date = sprintf('1401/%d/10', $month); 73 | $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); 74 | $expected = CalendarUtils::IRANIAN_MONTHS_NAME[$month - 1]; 75 | $this->assertEquals($expected, $actual); 76 | } 77 | 78 | // Should returns afghan months name when set 79 | CalendarUtils::useAfghanMonthsName(); 80 | foreach ($months as $month) { 81 | $date = sprintf('1401/%d/10', $month); 82 | $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); 83 | $expected = CalendarUtils::AFGHAN_MONTHS_NAME[$month - 1]; 84 | $this->assertEquals($expected, $actual); 85 | } 86 | 87 | // Should returns afghan months name when set 88 | CalendarUtils::useIranianMonthsName(); 89 | foreach ($months as $month) { 90 | $date = sprintf('1401/%d/10', $month); 91 | $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); 92 | $expected = CalendarUtils::IRANIAN_MONTHS_NAME[$month - 1]; 93 | $this->assertEquals($expected, $actual); 94 | } 95 | } 96 | 97 | public function test_parseFromPersian() 98 | { 99 | $jalaliDate = '1393/03/27'; 100 | $date = CalendarUtils::parseFromFormat('Y/m/d', $jalaliDate); 101 | 102 | $this->assertEquals(1393, $date['year']); 103 | $this->assertEquals(03, $date['month']); 104 | $this->assertEquals(27, $date['day']); 105 | 106 | $date = CalendarUtils::parseFromFormat('Y-m-d H:i:s', '1395-03-15 21:00:00'); 107 | $this->assertEquals(21, $date['hour']); 108 | $this->assertEquals(0, $date['minute']); 109 | $this->assertEquals(0, $date['second']); 110 | } 111 | 112 | public function testCreateDateTimeFormFormat() 113 | { 114 | $jdate = '1394/11/25 15:00:00'; 115 | $gDateTime = CalendarUtils::createDatetimeFromFormat('Y/m/d H:i:s', $jdate); 116 | 117 | $this->assertTrue($gDateTime instanceof \DateTime); 118 | 119 | $this->assertTrue('2016-02-14 15:00:00' === $gDateTime->format('Y-m-d H:i:s')); 120 | } 121 | 122 | public function testCreateCarbonFormFormat() 123 | { 124 | $jdate = '1394/11/25 15:00:00'; 125 | $carbon = CalendarUtils::createCarbonFromFormat('Y/m/d H:i:s', $jdate); 126 | 127 | $this->assertTrue($carbon instanceof \Carbon\Carbon); 128 | $this->assertTrue($carbon->day === 14); 129 | $this->assertTrue('2016-02-14 15:00:00' === $carbon->format('Y-m-d H:i:s')); 130 | 131 | $jalaiDateFormatted = Jalalian::fromDateTime($carbon->toDateString())->format('Y-m-d H:i:s'); 132 | $jalaiDateTimeFormatted = Jalalian::fromDateTime($carbon->toDateTimeString())->format('Y-m-d H:i:s'); 133 | $this->assertFalse($jalaiDateFormatted === '1394-11-25 15:00:00'); 134 | $this->assertTrue($jalaiDateTimeFormatted === '1394-11-25 15:00:00'); 135 | 136 | // Test support years after 1416 137 | $carbon = CalendarUtils::createCarbonFromFormat('Y/m/d', '1417/10/11'); 138 | $this->assertEquals('2039-01-01', $carbon->format('Y-m-d')); 139 | } 140 | 141 | public function testTimezone() 142 | { 143 | date_default_timezone_set('Asia/Tehran'); 144 | $tehranDate = Jalalian::now(); 145 | $tehranHour = $tehranDate->format('H'); 146 | $tehranMin = $tehranDate->format('i'); 147 | 148 | date_default_timezone_set('UTC'); 149 | $utcDate = Jalalian::now(); 150 | $utcHour = $utcDate->format('H'); 151 | $utcMin = $utcDate->format('i'); 152 | 153 | $tzOffset = $this->getTimeZoneOffset('Asia/Tehran', 'UTC'); 154 | 155 | $this->assertTrue((((($utcHour * 60) + $utcMin) * 60) - ((($tehranHour * 60) + $tehranMin) * 60)) === $tzOffset); 156 | } 157 | 158 | 159 | private function getTimeZoneOffset($remote_tz, $origin_tz = null) 160 | { 161 | if ($origin_tz === null) { 162 | if (!is_string($origin_tz = date_default_timezone_get())) { 163 | return false; // A UTC timestamp was returned -- bail out! 164 | } 165 | } 166 | $origin_dtz = new DateTimeZone($origin_tz); 167 | $remote_dtz = new DateTimeZone($remote_tz); 168 | $origin_dt = new DateTime("now", $origin_dtz); 169 | $remote_dt = new DateTime("now", $remote_dtz); 170 | $offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt); 171 | 172 | return $offset; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/HelperTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(function_exists('jdate')); 13 | 14 | $jdate = jdate('now'); 15 | $this->assertTrue($jdate instanceof Jalalian); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/JalalianTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($jDate instanceof Jalalian); 15 | $this->assertEquals($jDate->getDay(), 25); 16 | $this->assertEquals($jDate->getYear(), 1397); 17 | $this->assertEquals($jDate->getMonth(), 1); 18 | 19 | $this->assertEquals($jDate->format('Y-m-d H:i:s'), '1397-01-25 00:00:00'); 20 | } 21 | 22 | public function testGetDayOfYear() 23 | { 24 | $jDate = new Jalalian(1397, 1, 25); 25 | $this->assertEquals($jDate->getDayOfYear(), 25); 26 | 27 | $jDate = new Jalalian(1397, 5, 20); 28 | $this->assertEquals($jDate->getDayOfYear(), 144); 29 | 30 | $jDate = new Jalalian(1397, 7, 3); 31 | $this->assertEquals($jDate->getDayOfYear(), 189); 32 | 33 | $jDate = new Jalalian(1397, 12, 29); 34 | $this->assertEquals($jDate->getDayOfYear(), 365); 35 | 36 | $jDate = new Jalalian(1395, 12, 30); 37 | $this->assertTrue($jDate->isLeapYear()); 38 | $this->assertEquals($jDate->getDayOfYear(), 366); 39 | } 40 | 41 | public function testModifiers() 42 | { 43 | $jDate = new Jalalian(1397, 1, 18); 44 | 45 | $this->assertEquals($jDate->addYears()->getYear(), 1398); 46 | $this->assertEquals($jDate->addMonths(11)->getMonth(), 12); 47 | $this->assertEquals($jDate->addMonths(11)->addDays(20)->getMonth(), 1); 48 | $this->assertEquals($jDate->subDays(8)->getNextMonth()->getMonth(), 2); 49 | 50 | $jDate = Jalalian::fromCarbon(Carbon::createFromDate(2019, 1, 1)); 51 | $this->assertEquals($jDate->addMonths(4)->getYear(), 1398); 52 | 53 | $jDate = new Jalalian(1397, 1, 31); 54 | $this->assertEquals($jDate->addMonths(1)->getDay(), 31); 55 | $this->assertEquals($jDate->addYears(3)->getDay(), 31); 56 | $this->assertEquals($jDate->addMonths(36)->toString(), $jDate->addYears(3)->toString()); 57 | $this->assertEquals($jDate->subYears(10)->toString(), (new Jalalian(1387, 1, 31))->toString()); 58 | $this->assertTrue($jDate->subYears(2)->subMonths(34)->equalsTo(new Jalalian(1392, 03, 31))); 59 | 60 | $jDate = (new Jalalian(1397, 6, 11))->subMonths(1); 61 | $this->assertEquals($jDate->getMonth(), 5); 62 | 63 | $this->assertEquals((new Jalalian(1397, 7, 1))->subMonths(1)->getMonth(), 6); 64 | 65 | $jDate = Jalalian::now(); 66 | $month = $jDate->getMonth(); 67 | if ($month > 1) { 68 | $this->assertEquals($month - 1, $jDate->subMonths()->getMonth()); 69 | } 70 | 71 | 72 | $jDate = Jalalian::fromFormat('Y-m-d', '1397-12-12'); 73 | $this->assertEquals('1398-01-12', $jDate->addMonths(1)->format('Y-m-d')); 74 | 75 | $jDate = Jalalian::fromFormat('Y-m-d', '1397-11-30'); 76 | $this->assertEquals('1397-12-29', $jDate->addMonths(1)->format('Y-m-d')); 77 | 78 | $jDate = Jalalian::fromFormat('Y-m-d', '1397-06-30'); 79 | $this->assertEquals('1397-07-30', $jDate->addMonths(1)->format('Y-m-d')); 80 | 81 | $jDate = Jalalian::fromFormat('Y-m-d', '1397-06-31'); 82 | $this->assertEquals('1397-07-30', $jDate->addMonths(1)->format('Y-m-d')); 83 | 84 | $jDate = Jalalian::fromFormat('Y-m-d', '1395-12-30'); 85 | $this->assertEquals('1399-12-30', $jDate->addMonths(48)->format('Y-m-d')); 86 | 87 | $jDate = Jalalian::fromFormat('Y-m-d', '1395-12-30'); 88 | $this->assertEquals('1398-12-29', $jDate->addMonths(36)->format('Y-m-d')); 89 | } 90 | 91 | public function testForge() 92 | { 93 | $jDate = Jalalian::forge(strtotime('now')); 94 | $this->assertTrue($jDate instanceof Jalalian); 95 | $this->assertTrue($jDate->getTimestamp() === strtotime('now')); 96 | 97 | $jDate = Jalalian::forge(1333857600); 98 | $this->assertEquals($jDate->toString(), '1391-01-20 04:00:00'); 99 | 100 | $jDate = Jalalian::forge('last monday'); 101 | $this->assertTrue($jDate instanceof Jalalian); 102 | 103 | $jDate = Jalalian::forge(1552608000); 104 | $this->assertEquals('1397-12-24', $jDate->format('Y-m-d')); 105 | } 106 | 107 | public function testMaximumYearFormatting() 108 | { 109 | $jDate = Jalalian::fromFormat('Y-m-d', '1800-12-01'); 110 | $this->assertEquals(1800, $jDate->getYear()); 111 | $this->assertEquals($jDate->format('Y-m-d'), '1800-12-01'); 112 | 113 | // issue-110 114 | $jDate = Jalalian::fromFormat('Y-m-d', '1416-12-01'); 115 | $this->assertEquals(1416, $jDate->format('Y')); 116 | } 117 | 118 | public function testGetWeekOfMonth() 119 | { 120 | $jDate = new Jalalian(1400, 1, 8); 121 | $this->assertEquals($jDate->getWeekOfMonth(), 2); 122 | 123 | $jDate = new Jalalian(1400, 5, 13); 124 | $this->assertEquals($jDate->getWeekOfMonth(), 3); 125 | 126 | $jDate = new Jalalian(1390, 11, 11); 127 | $this->assertEquals($jDate->getWeekOfMonth(), 2); 128 | 129 | $jDate = new Jalalian(1395, 7, 20); 130 | $this->assertEquals($jDate->getWeekOfMonth(), 4); 131 | 132 | $jDate = new Jalalian(1401, 1, 5); 133 | $this->assertEquals($jDate->getWeekOfMonth(), 1); 134 | 135 | $jDate = new Jalalian(1390, 8, 7); 136 | $this->assertEquals($jDate->getWeekOfMonth(), 2); 137 | 138 | 139 | $jDate = new Jalalian(1390, 8, 27); 140 | $this->assertEquals($jDate->getWeekOfMonth(), 4); 141 | 142 | $jDate = new Jalalian(1390, 7, 1); 143 | $this->assertEquals($jDate->getWeekOfMonth(), 1); 144 | 145 | $jDate = new Jalalian(1390, 7, 2); 146 | $this->assertEquals($jDate->getWeekOfMonth(), 2); 147 | 148 | $jDate = new Jalalian(1390, 7, 30); 149 | $this->assertEquals($jDate->getWeekOfMonth(), 6); 150 | 151 | $jDate = new Jalalian(1390, 6, 15); 152 | $this->assertEquals($jDate->getWeekOfMonth(), 3); 153 | 154 | $jDate = new Jalalian(1390, 6, 25); 155 | $this->assertEquals($jDate->getWeekOfMonth(), 4); 156 | 157 | $jDate = new Jalalian(1390, 6, 26); 158 | $this->assertEquals($jDate->getWeekOfMonth(), 5); 159 | 160 | $jDate = new Jalalian(1401, 3, 7); 161 | $this->assertEquals($jDate->getWeekOfMonth(), 2); 162 | } 163 | 164 | public function testGetFirstDayOfWeek() 165 | { 166 | $jDate = new Jalalian(1401, 1, 23); 167 | $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1401-01-20'); 168 | 169 | $jDate = new Jalalian(1395, 4, 24); 170 | $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1395-04-19'); 171 | 172 | $jDate = new Jalalian(1398, 11, 7); 173 | $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1398-11-05'); 174 | 175 | $jDate = new Jalalian(1400, 8, 19); 176 | $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1400-08-15'); 177 | } 178 | 179 | public function testGetFirstDayOfMonth() 180 | { 181 | $jDate = new Jalalian(1401, 1, 23); 182 | $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1401-01-01'); 183 | 184 | $jDate = new Jalalian(1390, 5, 14); 185 | $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1390-05-01'); 186 | 187 | $jDate = new Jalalian(1399, 2, 29); 188 | $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1399-02-01'); 189 | 190 | $jDate = new Jalalian(1398, 10, 10); 191 | $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1398-10-01'); 192 | } 193 | 194 | public function testGetFirstDayOfYear() 195 | { 196 | $jDate = new Jalalian(1401, 6, 11); 197 | $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1401-01-01'); 198 | 199 | $jDate = new Jalalian(1399, 11, 28); 200 | $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1399-01-01'); 201 | 202 | $jDate = new Jalalian(1394, 1, 12); 203 | $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1394-01-01'); 204 | 205 | $jDate = new Jalalian(1393, 9, 5); 206 | $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1393-01-01'); 207 | } 208 | 209 | public function testAddDay() 210 | { 211 | $jDate = new Jalalian(1401, 6, 31); 212 | $this->assertEquals($jDate->addDay()->format('Y-m-d'), '1401-07-01'); 213 | } 214 | 215 | public function testSubDay() 216 | { 217 | $jDate = new Jalalian(1401, 6, 1); 218 | $this->assertEquals($jDate->subDay()->format('Y-m-d'), '1401-05-31'); 219 | } 220 | 221 | public function testGetLastWeek() 222 | { 223 | $jDate = new Jalalian(1401, 6, 8); 224 | $this->assertEquals($jDate->getLastWeek()->format('Y-m-d'), '1401-06-01'); 225 | } 226 | 227 | public function testGetLastMonth() 228 | { 229 | $jDate = new Jalalian(1401, 6, 8); 230 | $this->assertEquals($jDate->getLastMonth()->format('Y-m-d'), '1401-05-08'); 231 | } 232 | 233 | public function testGetFirstDayOfQuarter() 234 | { 235 | $jDate = new Jalalian(1402, 1, 25); 236 | $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 237 | 238 | $jDate = new Jalalian(1402, 2, 25); 239 | $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 240 | 241 | $jDate = new Jalalian(1402, 3, 25); 242 | $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 243 | 244 | $jDate = new Jalalian(1402, 4, 25); 245 | $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 246 | 247 | $jDate = new Jalalian(1402, 5, 25); 248 | $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 249 | 250 | $jDate = new Jalalian(1402, 6, 25); 251 | $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 252 | 253 | $jDate = new Jalalian(1402, 7, 25); 254 | $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 255 | 256 | $jDate = new Jalalian(1402, 8, 25); 257 | $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 258 | 259 | $jDate = new Jalalian(1402, 9, 25); 260 | $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 261 | 262 | $jDate = new Jalalian(1402, 10, 25); 263 | $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 264 | 265 | $jDate = new Jalalian(1402, 11, 19); 266 | $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 267 | 268 | $jDate = new Jalalian(1402, 12, 25); 269 | $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); 270 | 271 | } 272 | 273 | public function testGetEndDayOfWeek() 274 | { 275 | /* 276 | * -------------------------------- 277 | * Day 1402 (March 2024) 278 | * -------------------------------- 279 | * Sat Sun Mon Tue Wed Thu Fri 280 | * -------------------------------- 281 | * 1 282 | * 2 3 4 5 6 7 8 283 | * 9 10 11 12 13 14 15 284 | * 16 17 18 19 20 21 22 285 | * 23 24 25 26 27 28 29 286 | * 30 287 | * ------------------------------- 288 | */ 289 | 290 | $jDate = new Jalalian(1402, 10, 25); 291 | $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); 292 | 293 | $jDate = new Jalalian(1402, 10, 29); 294 | $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); 295 | 296 | $jDate = new Jalalian(1402, 10, 23); 297 | $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); 298 | 299 | $jDate = new Jalalian(1402, 12, 29); 300 | $this->assertEquals('1403-01-03', $jDate->getEndDayOfWeek()->format('Y-m-d')); 301 | 302 | } 303 | 304 | public function testGetEndDayOfMonth() 305 | { 306 | /* 307 | * -------------------------------- 308 | * Day 1402 (March 2024) 309 | * -------------------------------- 310 | * Sat Sun Mon Tue Wed Thu Fri 311 | * -------------------------------- 312 | * 1 313 | * 2 3 4 5 6 7 8 314 | * 9 10 11 12 13 14 15 315 | * 16 17 18 19 20 21 22 316 | * 23 24 25 26 27 28 29 317 | * 30 318 | * ------------------------------- 319 | */ 320 | 321 | $jDate = new Jalalian(1402, 10, 25); 322 | $this->assertEquals('1402-10-30', $jDate->getEndDayOfMonth()->format('Y-m-d')); 323 | 324 | $jDate = new Jalalian(1402, 04, 12); 325 | $this->assertEquals('1402-04-31', $jDate->getEndDayOfMonth()->format('Y-m-d')); 326 | 327 | $jDate = new Jalalian(1402, 12, 25); 328 | $this->assertEquals('1402-12-29', $jDate->getEndDayOfMonth()->format('Y-m-d')); 329 | } 330 | 331 | public function testGetEndDayOfYear() 332 | { 333 | $jDate = new Jalalian(1402, 10, 25); 334 | $this->assertEquals('1402-12-29', $jDate->getEndDayOfYear()->format('Y-m-d')); 335 | 336 | // LeapYear 337 | $jDate = new Jalalian(1403, 10, 25); 338 | $this->assertEquals('1403-12-30', $jDate->getEndDayOfYear()->format('Y-m-d')); 339 | } 340 | 341 | public function testGetEndDayOfQuarter() 342 | { 343 | $jDate = new Jalalian(1402, 10, 25); 344 | $this->assertEquals('1402-12-29', $jDate->getEndDayOfQuarter()->format('Y-m-d')); 345 | 346 | $jDate = new Jalalian(1402, 2, 25); 347 | $this->assertEquals('1402-03-31', $jDate->getEndDayOfQuarter()->format('Y-m-d')); 348 | 349 | 350 | $jDate = new Jalalian(1402, 6, 01); 351 | $this->assertEquals('1402-06-31', $jDate->getEndDayOfQuarter()->format('Y-m-d')); 352 | 353 | 354 | $jDate = new Jalalian(1402, 9, 01); 355 | $this->assertEquals('1402-09-30', $jDate->getEndDayOfQuarter()->format('Y-m-d')); 356 | } 357 | 358 | public function testGetQuarter() 359 | { 360 | $jDate = new Jalalian(1402, 10, 25); 361 | $this->assertEquals(4, $jDate->getQuarter()); 362 | 363 | $jDate = new Jalalian(1402, 1, 01); 364 | $this->assertEquals(1, $jDate->getQuarter()); 365 | 366 | 367 | $jDate = new Jalalian(1402, 05, 24); 368 | $this->assertEquals(2, $jDate->getQuarter()); 369 | 370 | $jDate = new Jalalian(1402, 7, 23); 371 | $this->assertEquals(3, $jDate->getQuarter()); 372 | } 373 | } 374 | --------------------------------------------------------------------------------