diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..11ed588 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ + +issuehunt: morilog + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..fb8a116 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: tests + +on: + push: + pull_request: + +jobs: + tests: + runs-on: ubuntu-22.04 + + strategy: + fail-fast: true + matrix: + php: [ 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3 ] + + name: PHP ${{ matrix.php }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: error_reporting=E_ALL + tools: composer:v2 + coverage: none + + - name: Install dependencies + run: | + composer update --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2c1fc0c..28c68e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /vendor composer.phar composer.lock -.DS_Store \ No newline at end of file +.DS_Store +.idea +.phpunit.* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 0a1c1cb..c75476e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ language: php -php: - - 5.3 - - 5.4 - - 5.5 - +php: + - 7.0 + - 7.1 + - 7.2 + - 7.3 + - 7.4 before_script: - curl -s http://getcomposer.org/installer | php - php composer.phar install --dev -script: phpunit \ No newline at end of file +script: ./vendor/bin/phpunit diff --git a/Facades/JDateTime.php b/Facades/JDateTime.php deleted file mode 100644 index 12616a2..0000000 --- a/Facades/JDateTime.php +++ /dev/null @@ -1,14 +0,0 @@ - -## Installation +- CalendarUtils class was ported from [jalaali/jalaali-js](https://github.com/jalaali/jalaali-js) -In the `require` key of `composer.json` file add the following +## Version 3 features +- High human readable API +- DateTime manipulating API +- DateTime comparing API +- Immutable -```yml - "miladr/jalali": "dev-master" +## Installation Version 3.* +> If you are using version <= 2.*, please read [old docs](https://github.com/morilog/jalali/blob/v2.3.0/README.md) +#### Requirements: +- `php >= 7.0` + +Run the Composer update command + + $ composer require morilog/jalali:3.* + + +## Basic Usage +In the current version, I introduced `Jalalian` class for manipulating Jalali date time +### Jalalian +In version >= 1.1, you can use `jdate()` instead of `Jalalian::forge()`; +#### `now([$timestamp = null])` +``` php +// the default timestamp is Now +$date = \Morilog\Jalali\Jalalian::now() +// OR +$date = jdate(); + +// pass timestamps +$date = Jalalian::forge(1333857600); +// OR +$date = jdate(1333857600); + +// pass human readable strings to make timestamps +$date = Jalalian::forge('last sunday'); + +// get the timestamp +$date = Jalalian::forge('last sunday')->getTimestamp(); // 1333857600 + +// format the timestamp +$date = Jalalian::forge('last sunday')->format('%B %d، %Y'); // دی 02، 1391 +$date = Jalalian::forge('today')->format('%A, %d %B %y'); // جمعه، 23 اسفند 97 + +// get a predefined format +$date = Jalalian::forge('last sunday')->format('datetime'); // 1391-10-02 00:00:00 +$date = Jalalian::forge('last sunday')->format('date'); // 1391-10-02 +$date = Jalalian::forge('last sunday')->format('time'); // 00:00:00 + +// get relative 'ago' format +$date = Jalalian::forge('now - 10 minutes')->ago() // 10 دقیقه پیش ``` -Run the Composer update comand +#### Methods api +--- - $ composer update -In your `config/app.php` add `'Miladr\Jalali\JalaliServiceProvider'` to the end of the `$providers` array +```php +public static function now(\DateTimeZone $timeZone = null): Jalalian +$jDate = Jalalian::now(); +``` + +--- ```php - 'providers' => array( +public static function fromCarbon(Carbon $carbon): Jalalian - 'Illuminate\Foundation\Providers\ArtisanServiceProvider', - 'Illuminate\Auth\AuthServiceProvider', - ... - 'Miladr\Jalali\JalaliServiceProvider', +$jDate = Jalalian::fromCarbon(Carbon::now()); +``` - ), +--- +```php +public static function fromFormat(string $format, string $timestamp, \DateTimeZone$timeZone = null): Jalalian + +$jDate = Jalalian::fromFormat('Y-m-d H:i:s', '1397-01-18 12:00:40'); ``` - -## Basic Usage -## Examples ## -Some Examples (based on examples provided by Sallar) +--- +```php +public static function forge($timestamp, \DateTimeZone $timeZone = null): Jalalian + +// Alias fo fromDatetime +``` +--- ```php -// default timestamp is now -$date = jDate::forge(); +public static function fromDateTime($dateTime, \DateTimeZone $timeZone = null): Jalalian -// pass timestamps -$date = jDate::forge(1333857600); +$jDate = Jalalian::fromDateTime(Carbon::now()) +// OR +$jDate = Jalalian::fromDateTime(new \DateTime()); +// OR +$jDate = Jalalian::fromDateTime('yesterday'); -// pass strings to make timestamps -$date = jDate::forge('last sunday'); +``` -// get the timestamp -$date = jDate::forge('last sunday')->time(); // 1333857600 -// format the timestamp -$date = jDate::forge('last sunday')->format('%B %d، %Y'); // دی 02، 1391 +--- +```php +public function getMonthDays(): int -// get a predefined format -$date = jDate::forge('last sunday')->format('datetime'); // 1391-10-02 00:00:00 -$date = jDate::forge('last sunday')->format('date'); // 1391-10-02 -$date = jDate::forge('last sunday')->format('time'); // 00:00:00 +$date = (new Jalalian(1397, 1, 18))->getMonthDays() +// output: 31 +``` -// amend the timestamp value, relative to existing value -$date = jDate::forge('2012-10-12')->reforge('+ 3 days')->format('date'); // 1391-07-24 +--- +```php +public function getMonth(): int -// get relative 'ago' format -$date = jDate::forge('now - 10 minutes')->ago() // ۱۰ دقیقه پیش +$date = (new Jalalian(1397, 1, 18))->getMonth() +// output: 1 +``` + +--- +```php +public function isLeapYear(): bool + +$date = (new Jalalian(1397, 1, 18))->isLeapYear() +// output: false + +``` + +--- +```php +public function getYear(): int + +$date = (new Jalalian(1397, 1, 18))->getYear() +// output: 1397 ``` +--- +```php +public function subMonths(int $months = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18))->subMonths(1)->toString() +// output: 1396-12-18 00:00:00 + +``` + +--- +```php +public function subYears(int $years = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18))->subYears(1)->toString() +// output: 1396-01-18 00:00:00 +``` + +--- +```php +public function getDay(): int + +$date = (new Jalalian(1397, 1, 18))->getDay() +// output: 18 + +``` + +--- +```php +public function getHour(): int + +$date = (new Jalalian(1397, 1, 18, 12, 0, 0))->getHour() +// output: 12 + + +``` + +--- +```php +public function getMinute(): int + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->getMinute() +// output: 10 + +``` + +--- +```php +public function getSecond(): int + +$date = (new Jalalian(1397, 1, 18, 12, 10, 45))->getSecond() +// output: 45 +``` + +--- +```php +public function getTimezone(): \DateTimeZone + +// Get current timezone +``` + +--- +```php +public function addMonths(int $months = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addMonths(1)->format('m') +// output: 02 + +``` + +--- +```php +public function addYears(int $years = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addYears(1)->format('Y') +// output: 1398 + +``` + +--- +```php +public function getDaysOf(int $monthNumber = 1): int + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->getDaysOf(1) +// output: 31 +``` + +--- +```php +public function addDays(int $days = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addDays(1)->format('d') +// output: 18 + +``` + +--- +```php +public function toCarbon(): Carbon + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->toCarbon()->toDateTimeString() +// output: 2018-04-07 12:10:00 +``` + +--- +```php +public function subDays(int $days = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subDays(10)->format('d') +// output: 08 +``` + +--- +```php +public function addHours(int $hours = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addHours(1)->format('H') +// output: 13 + +``` + +--- +```php +public function subHours(int $hours = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subHours(1)->format('H') +// output: 11 + +``` + +--- +```php +public function addMinutes(int $minutes = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addMinutes(10)->format('i') +// output: 22 + +``` + +--- +```php +public function subMinutes(int $minutes = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subMinutes(10)->format('i') +// output: 02 + +``` + +--- +```php +public function addSeconds(int $secs = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->addSeconds(10)->format('s') +// output: 10 + +``` + +--- +```php +public function subSeconds(int $secs = 1): Jalalian + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->subSeconds(10)->format('i:s') +// output: 11:40 + + +``` + +--- +```php +public function equalsTo(Jalalian $other): bool + +$date = (new Jalalian(1397, 1, 18, 12, 10, 0))->equalsTo(Jalalian::now()) +// output: false + +$date = Jalalian::now()->equalsTo(Jalalian::now()) +// output: true + +``` + +--- +```php +public function equalsToCarbon(Carbon $carbon): bool + +$date = Jalalian::now()->equalsToCarbon(Carbon::now()) +// output: true +``` + +--- +```php +public function greaterThan(Jalalian $other): bool + +$date = Jalalian::now()->greaterThan(Jalalian::now()->subDays(1))) +// output: true +``` + +--- +```php +public function greaterThanCarbon(Carbon $carbon): bool + +$date = Jalalian::now()->greaterThanCarbon(Carbon::now()->subDays(1))) +// output: true + +``` + +--- +```php +public function lessThan(Jalalian $other): bool + +$date = Jalalian::now()->lessThan(Jalalian::now()->addDays(1))) +// output: true + +``` + +--- +```php +public function lessThanCarbon(Carbon $carbon): bool + +$date = Jalalian::now()->lessThanCarbon(Carbon::now()->addDays(1))) +// output: true + +``` + +--- +```php +public function greaterThanOrEqualsTo(Jalalian $other): bool + +$date = Jalalian::now()->greaterThan(Jalalian::now()->subDays(1))) +// output: true + +``` + +--- +```php +public function greaterThanOrEqualsToCarbon(Carbon $carbon): bool + +$date = Jalalian::now()->greaterThanOrEqualsToCarbon(Carbon::now())) +// output: true + +``` + +--- +```php +public function lessThanOrEqualsTo(Jalalian $other): bool + +$date = Jalalian::now()->lessThanOrEqualsTo(Jalalian::now())) +// output: true + +``` + +--- +```php +public function lessThanOrEqualsToCarbon(Carbon $carbon): bool + +$date = Jalalian::now()->lessThanOrEqualsToCarbon(Carbon::now())) +// output: true + +``` + +--- +```php +public function isStartOfWeek(): bool + +$date = (new Jalalian(1397, 6, 24))->isStartOfWeek() +// output: true + +``` +--- +```php +public function getEndDayOfYear(): bool + +$date = (new Jalalian(1397, 6, 24))->getEndDayOfYear() +// output: 1397, 12, 29 + +``` +--- +```php +public function getFirstDayOfMonth(): bool + +$date = (new Jalalian(1397, 6, 24))->getEndDayOfYear() +// output: 1397, 6, 1 + +``` +--- +```php +public function getEndDayOfMonth(): bool + +$date = (new Jalalian(1397, 6, 24))->getEndDayOfMonth() +// output: 1397, 6, 31 + +``` +--- +```php +public function isSaturday(): bool + +$date = (new Jalalian(1397, 6, 24))->isSaturday() +// output: true + +``` + +--- +```php +public function isDayOfWeek(int $day): bool + +$date = (new Jalalian(1397, 6, 24))->isDayOfWeek(0) +// output: true + +``` + +--- +```php +public function isEndOfWeek(): bool + +$date = (new Jalalian(1397, 6, 24))->isEndOfWeek() +// output: false + +``` + +--- +```php +public function isFriday(): bool + +$date = (new Jalalian(1397, 6, 24))->isFriday() +// output: false + +``` + +--- +```php +public function isToday(): bool + +$date = (new Jalalian(1397, 6, 24))->isToday() +// output: (!maybe) true + +``` + +--- +```php +public function isTomorrow(): bool + +$date = (new Jalalian(1397, 6, 25))->isTomorrow() +// output: true + +``` + +--- +```php +public function isYesterday(): bool + +$date = (new Jalalian(1397, 6, 23))->isYesterday() +// output: true + +``` + +--- +```php +public function isFuture(): bool + +$date = (new Jalalian(1397, 6, 26))->isFuture() +// output: true + +``` + +--- +```php +public function isPast(): bool + +$date = (new Jalalian(1397, 5, 24))->isPast() +// output: true + +``` + +--- +```php +public function toArray(): array +$date = (new Jalalian(1397, 6, 24))->toArray() +// output: ( +// [year] => 1397 +// [month] => 6 +// [day] => 24 +// [dayOfWeek] => 0 +// [dayOfYear] => 179 +// [hour] => 0 +// [minute] => 0 +// [second] => 0 +// [micro] => 0 +// [timestamp] => 1536969600 +// [formatted] => 1397-06-24 00:00:00 +// [timezone] => +// ) +``` + +--- +```php +public function getDayOfWeek(): int + +$date = (new Jalalian(1397, 5, 24))->getDayOfWeek() +// output: 0 + +``` + +--- +```php +public function isSunday(): bool + +$date = (new Jalalian(1397, 6, 24))->isSunday() +// output: false + +``` + +--- +```php +public function isMonday(): bool + +$date = (new Jalalian(1397, 6, 26))->isMonday() +// output: true + +``` + +--- +```php +public function isTuesday(): bool + +$date = (new Jalalian(1397, 6, 24))->isTuesday() +// output: false + +``` + +--- +```php +public function isWednesday(): bool + +$date = (new Jalalian(1397, 6, 24))->isWednesday() +// output: false + +``` + +--- +```php +public function isThursday(): bool + +$date = (new Jalalian(1397, 6, 22))->isThursday() +// output: true + +``` + +--- +```php +public function getDayOfYear(): int + +$date = (new Jalalian(1397, 5, 24))->getDayOfYear() +// output: 179 + +``` + +--- +```php +public function toString(): string +$date = (new Jalalian(1397, 5, 24))->toString() +// output: 1397-05-24 00:00:00 + +``` + +--- +```php +public function format(string $format): string + +$date = (new Jalalian(1397, 5, 24))->format('y') +// output: 1397 +// see php date formats + +``` + +--- +```php +public function __toString(): string + +// Alias of toString() +``` + +--- +```php +public function ago(): string + +``` + +--- +```php +public function getTimestamp(): int + +``` + +--- +```php +public function getNextWeek(): Jalalian + +``` + +--- +```php +public function getNextMonth(): Jalalian + +``` + +--- +```php +public function diff(Jalalian $ref): array +$diff = (new Jalalian(1397, 5, 24))->diff(new Jalalian(1398, 6, 30)); +// output: [1, 1, 6] + +``` +--- +```php +public function next(string $dayName):Jalalian +$next = (new Jalalian(1403, 5, 22))->next('شنبه')->format('Y-m-d'); +// output: 1403-05-27 + +``` +--- +```php +public function previous(string $dayName):Jalalian +$previous = (new Jalalian(1403, 5, 22))->previous('شنبه')->format('Y-m-d'); +// output: 1403-05-20 + +``` +### CalendarUtils +--- + + +#### `checkDate($year, $month, $day, [$isJalali = true])` +```php +// Check jalali date +\Morilog\Jalali\CalendarUtils::checkDate(1391, 2, 30, true); // true + +// Check jalali date +\Morilog\Jalali\CalendarUtils::checkDate(2016, 5, 7); // false + +// Check gregorian date +\Morilog\Jalali\CalendarUtils::checkDate(2016, 5, 7, false); // true +``` +--- +#### `toJalali($gYear, $gMonth, $gDay)` +```php +\Morilog\Jalali\CalendarUtils::toJalali(2016, 5, 7); // [1395, 2, 18] +``` +--- +#### `toGregorian($jYear, $jMonth, $jDay)` +```php +\Morilog\Jalali\CalendarUtils::toGregorian(1395, 2, 18); // [2016, 5, 7] +``` +--- +#### `strftime($format, [$timestamp = false, $timezone = null])` +```php +CalendarUtils::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19 +``` +--- +#### `createDateTimeFromFormat($format, $jalaiTimeString)` +```php +$Jalalian = '1394/11/25 15:00:00'; + +// get instance of \DateTime +$dateTime = \Morilog\Jalali\CalendarUtils::createDatetimeFromFormat('Y/m/d H:i:s', $Jalalian); + +``` +--- +#### `createCarbonFromFormat($format, $jalaiTimeString)` +```php +$Jalalian = '1394/11/25 15:00:00'; + +// get instance of \Carbon\Carbon +$carbon = \Morilog\Jalali\CalendarUtils::createCarbonFromFormat('Y/m/d H:i:s', $Jalalian); + +``` +--- +#### `convertNumbers($string)` +```php +// convert latin to persian +$date = \Morilog\Jalali\CalendarUtils::strftime('Y-m-d', strtotime('2016-05-8')); // 1395-02-19 +\Morilog\Jalali\CalendarUtils::convertNumbers($date); // ۱۳۹۵-۰۲-۱۹ + +// convert persian to latin +$dateString = \Morilog\Jalali\CalendarUtils::convertNumbers('۱۳۹۵-۰۲-۱۹', true); // 1395-02-19 +\Morilog\Jalali\CalendarUtils::createCarbonFromFormat('Y-m-d', $dateString)->format('Y-m-d'); //2016-05-8 +``` + +--- +#### `Carbon api-difference` + +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. + +##### [Difference](https://carbon.nesbot.com/docs/#api-difference) in months: + +```php +// convert persian to Carbon +$date = \Morilog\Jalali\Jalalian::fromFormat('Y-m-d', "1395-02-19")->toCarbon(); +// ->toString() => Sun May 08 2016 00:00:00 GMT+0000 + +// Add 4 months to Carbon +$dateAdd4Months = $date->addMonths(4); + +// Difference in months +$dateAdd4Months->DiffInMonths($date); //4 +$dateAdd4Months->floatDiffInMonths($date); //4.0 +``` +--- ## Formatting ## For help in building your formats, checkout the [PHP strftime() docs](http://php.net/manual/en/function.strftime.php). ## Notes ## -The class relies on ``strtotime()`` to make sense of your strings, and ``strftime()`` to make the format changes. Just always check the ``time()`` output to see if you get false timestamps... which means the class couldn't understand what you were telling it. +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. ## License ## -- This bundle is created based on [Laravel-Date](https://github.com/swt83/laravel-date) by [Scott Travis](https://github.com/swt83) (MIT Licensed). -- [Jalali (Shamsi) DateTime](https://github.com/sallar/jDateTime) class included in the package is created by [Sallar Kaboli](http://sallar.me) and is released under the MIT License. -- This package was created by [Milad Rey](http://milad.io) and is released under the MIT License. +- This bundle is created based on [Laravel-Date](https://github.com/swt83/laravel-date) by [Scott Travis](https://github.com/swt83) (MIT Licensed). +- [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. +- This package is created and modified by [Morteza Parvini](http://morilog.ir) for Laravel >= 5 and is released under the MIT License. diff --git a/composer.json b/composer.json index 3c9f6a7..036e98e 100644 --- a/composer.json +++ b/composer.json @@ -1,21 +1,38 @@ { - "name": "miladr/jalali", - "description": "This Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in Laravel 4 applications, based on Jalali (Shamsi) DateTime class. This Package is based on a Laravel 3 bundle sallar/laravel-jdate by Sallar Kaboli.", + "name": "moree-dev/jalali", + "description": "This Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in PHP applications, based on Jalali (Shamsi) DateTime class.", "license": "MIT", + "type": "library", "authors": [ { "name": "Milad Rey", "email": "miladr@gmail.com" - } + }, { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } ], - "keywords": ["Laravel","Date","Datetime","Jalali"], + "keywords": ["Laravel","Date","Datetime","Jalali", "Morilog"], "require": { - "php": ">=5.3.0" + "php": "^7.0 | ^8.0", + "nesbot/carbon": "^1.21 || ^2.0 || ^3.0", + "beberlei/assert": "^3.0" + }, + "require-dev" : { + "phpunit/phpunit": ">4.0" }, "autoload": { - "psr-0": { - "Miladr\\Jalali": "src/" + "psr-4": { + "Morilog\\Jalali\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Morilog\\Jalali\\Tests\\": "tests/" } }, - "minimum-stability": "dev" + "minimum-stablity": "dev" } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d2d2c99 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + ./tests/ + + + \ No newline at end of file diff --git a/src/CalendarUtils.php b/src/CalendarUtils.php new file mode 100644 index 0000000..aa288a8 --- /dev/null +++ b/src/CalendarUtils.php @@ -0,0 +1,857 @@ +setDate($year, $month, $day); + + + return $georgianDate; + } + + /** + * Checks whether a Jalaali date is valid or not. + * + * @param int $jy + * @param int $jm + * @param int $jd + * @return bool + */ + public static function isValidateJalaliDate($jy, $jm, $jd) + { + return $jy >= -61 && $jy <= 3177 + && $jm >= 1 && $jm <= 12 + && $jd >= 1 && $jd <= self::jalaliMonthLength($jy, $jm); + } + + /** + * Checks whether a date is valid or not. + * + * @param $year + * @param $month + * @param $day + * @param bool $isJalali + * @return bool + */ + public static function checkDate($year, $month, $day, $isJalali = true) + { + return $isJalali === true ? self::isValidateJalaliDate($year, $month, $day) : checkdate($month, $day, $year); + } + + /** + * Is this a leap year or not? + * + * @param $jy + * @return bool + */ + public static function isLeapJalaliYear($jy) + { + return self::jalaliCal($jy)['leap'] === 0; + } + + /** + * Number of days in a given month in a Jalaali year. + * + * @param int $jy + * @param int $jm + * @return int + */ + public static function jalaliMonthLength($jy, $jm) + { + if ($jm <= 6) { + return 31; + } + + if ($jm <= 11) { + return 30; + } + + return self::isLeapJalaliYear($jy) ? 30 : 29; + } + + + /** + * This function determines if the Jalaali (Persian) year is + * leap (366-day long) or is the common year (365 days), and + * finds the day in March (Gregorian calendar) of the first + * day of the Jalaali year (jy). + * + * @param int $jy Jalaali calendar year (-61 to 3177) + * @return array + * leap: number of years since the last leap year (0 to 4) + * gy: Gregorian year of the beginning of Jalaali year + * march: the March day of Farvardin the 1st (1st day of jy) + * @see: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm + * @see: http://www.fourmilab.ch/documents/calendar/ + */ + public static function jalaliCal($jy) + { + $breaks = [ + -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178 + ]; + + $breaksCount = count($breaks); + + $gy = $jy + 621; + $leapJ = -14; + $jp = $breaks[0]; + + if ($jy < $jp || $jy >= $breaks[$breaksCount - 1]) { + throw new \InvalidArgumentException('Invalid Jalali year : ' . $jy); + } + + $jump = 0; + + for ($i = 1; $i < $breaksCount; $i += 1) { + $jm = $breaks[$i]; + $jump = $jm - $jp; + + if ($jy < $jm) { + break; + } + + $leapJ = $leapJ + self::div($jump, 33) * 8 + self::div(self::mod($jump, 33), 4); + + $jp = $jm; + } + + $n = $jy - $jp; + + $leapJ = $leapJ + self::div($n, 33) * 8 + self::div(self::mod($n, 33) + 3, 4); + + if (self::mod($jump, 33) === 4 && $jump - $n === 4) { + $leapJ += 1; + } + + $leapG = self::div($gy, 4) - self::div((self::div($gy, 100) + 1) * 3, 4) - 150; + + $march = 20 + $leapJ - $leapG; + + if ($jump - $n < 6) { + $n = $n - $jump + self::div($jump + 4, 33) * 33; + } + + $leap = self::mod(self::mod($n + 1, 33) - 1, 4); + + if ($leap === -1) { + $leap = 4; + } + + return [ + 'leap' => $leap, + 'gy' => $gy, + 'march' => $march + ]; + } + + /** + * @param $a + * @param $b + */ + public static function div($a, $b): int + { + return intdiv($a, $b); + } + + /** + * @param $a + * @param $b + * @return mixed + */ + public static function mod($a, $b): int + { + return $a - intdiv($a, $b) * $b; + } + + /** + * @param $jdn + * @return array + */ + public static function d2g($jdn) + { + $j = 4 * $jdn + 139361631; + $j += self::div(self::div(4 * $jdn + 183187720, 146097) * 3, 4) * 4 - 3908; + $i = self::div(self::mod($j, 1461), 4) * 5 + 308; + + $gd = self::div(self::mod($i, 153), 5) + 1; + $gm = self::mod(self::div($i, 153), 12) + 1; + $gy = self::div($j, 1461) - 100100 + self::div(8 - $gm, 6); + + return [$gy, $gm, $gd]; + } + + /** + * Calculates the Julian Day number from Gregorian or Julian + * calendar dates. This integer number corresponds to the noon of + * the date (i.e. 12 hours of Universal Time). + * The procedure was tested to be good since 1 March, -100100 (of both + * calendars) up to a few million years into the future. + * + * @param int $gy Calendar year (years BC numbered 0, -1, -2, ...) + * @param int $gm Calendar month (1 to 12) + * @param int $gd Calendar day of the month (1 to 28/29/30/31) + * @return int Julian Day number + */ + public static function g2d($gy, $gm, $gd) + { + return (self::div(($gy + self::div($gm - 8, 6) + 100100) * 1461, 4) + + self::div(153 * self::mod($gm + 9, 12) + 2, 5) + + $gd - 34840408 + ) - self::div(self::div($gy + 100100 + self::div($gm - 8, 6), 100) * 3, 4) + 752; + } + + /** + * Converts a date of the Jalaali calendar to the Julian Day number. + * + * @param int $jy Jalaali year (1 to 3100) + * @param int $jm Jalaali month (1 to 12) + * @param int $jd Jalaali day (1 to 29/31) + * @return int Julian Day number + */ + public static function j2d($jy, $jm, $jd) + { + $jCal = self::jalaliCal($jy); + + return self::g2d($jCal['gy'], 3, $jCal['march']) + ($jm - 1) * 31 - self::div($jm, 7) * ($jm - 7) + $jd - 1; + } + + + /** + * Converts the Julian Day number to a date in the Jalaali calendar. + * + * @param int $jdn Julian Day number + * @return array + * 0: Jalaali year (1 to 3100) + * 1: Jalaali month (1 to 12) + * 2: Jalaali day (1 to 29/31) + */ + public static function d2j($jdn) + { + $gy = self::d2g($jdn)[0]; + $jy = $gy - 621; + $jCal = self::jalaliCal($jy); + $jdn1f = self::g2d($gy, 3, $jCal['march']); + + $k = $jdn - $jdn1f; + + if ($k >= 0) { + if ($k <= 185) { + $jm = 1 + self::div($k, 31); + $jd = self::mod($k, 31) + 1; + + return [$jy, $jm, $jd]; + } else { + $k -= 186; + } + } else { + $jy -= 1; + $k += 179; + + if ($jCal['leap'] === 1) { + $k += 1; + } + } + + $jm = 7 + self::div($k, 30); + $jd = self::mod($k, 30) + 1; + + return [$jy, $jm, $jd]; + } + + /** + * @param $format + * @param bool $stamp + * @param bool $timezone + * @return mixed + */ + public static function date($format, $stamp = false, $timezone = null) + { + $stamp = ($stamp !== false) ? $stamp : time(); + $dateTime = static::createDateTime($stamp, $timezone); + + + //Find what to replace + $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array(); + + //Intact Keys + $intact = array('B', 'h', 'H', 'g', 'G', 'i', 's', 'I', 'U', 'u', 'Z', 'O', 'P'); + $intact = self::filterArray($chars, $intact); + $intactValues = array(); + + foreach ($intact as $k => $v) { + $intactValues[$k] = $dateTime->format($v); + } + //End Intact Keys + + //Changed Keys + list($year, $month, $day) = array($dateTime->format('Y'), $dateTime->format('n'), $dateTime->format('j')); + list($jYear, $jMonth, $jDay) = self::toJalali($year, $month, $day); + + $keys = array( + 'd', + 'D', + 'j', + 'l', + 'N', + 'S', + 'w', + 'z', + 'W', + 'F', + 'm', + 'M', + 'n', + 't', + 'L', + 'o', + 'Y', + 'y', + 'a', + 'A', + 'c', + 'r', + 'e', + 'T' + ); + $keys = self::filterArray($chars, $keys, array('z')); + $values = array(); + + foreach ($keys as $k => $key) { + $v = ''; + switch ($key) { + //Day + case 'd': + $v = sprintf("%02d", $jDay); + break; + case 'D': + $v = self::getDayNames($dateTime->format('D'), true); + break; + case 'j': + $v = $jDay; + break; + case 'l': + $v = self::getDayNames($dateTime->format('l')); + break; + case 'N': + $v = self::getDayNames($dateTime->format('l'), false, 1, true); + break; + case 'S': + $v = 'ام'; + break; + case 'w': + $v = self::getDayNames($dateTime->format('l'), false, 1, true) - 1; + break; + case 'z': + if ($jMonth > 6) { + $v = 186 + (($jMonth - 6 - 1) * 30) + $jDay; + } else { + $v = (($jMonth - 1) * 31) + $jDay; + } + self::$temp['z'] = $v; + break; + //Week + case 'W': + $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1); + break; + //Month + case 'F': + $v = self::getMonthName($jMonth); + break; + case 'm': + $v = sprintf("%02d", $jMonth); + break; + case 'M': + $v = self::getMonthName($jMonth, true); + break; + case 'n': + $v = $jMonth; + break; + case 't': + $v = ($jMonth == 12) ? (self::isLeapJalaliYear($jYear) ? 30 : 29) : ($jMonth > 6 ? 30 : 31); + break; + //Year + case 'L': + $tmpObj = static::createDateTime(time() - 31536000, $timezone); + $v = $tmpObj->format('L'); + break; + case 'o': + case 'Y': + $v = $jYear; + break; + case 'y': + $v = $jYear % 100; + if ($v < 10) { + $v = '0' . $v; + } + break; + //Time + case 'a': + $v = ($dateTime->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ'; + break; + case 'A': + $v = ($dateTime->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر'; + break; + //Full Dates + case 'c': + $v = $jYear . '-' . sprintf("%02d", $jMonth) . '-' . sprintf("%02d", $jDay) . 'T'; + $v .= $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . $dateTime->format('P'); + break; + case 'r': + $v = self::getDayNames($dateTime->format('D'), true) . ', ' . sprintf( + "%02d", + $jDay + ) . ' ' . self::getMonthName($jMonth, true); + $v .= ' ' . $jYear . ' ' . $dateTime->format('H') . ':' . $dateTime->format('i') . ':' . $dateTime->format('s') . ' ' . $dateTime->format('P'); + break; + //Timezone + case 'e': + $v = $dateTime->format('e'); + break; + case 'T': + $v = $dateTime->format('T'); + break; + } + $values[$k] = $v; + } + //End Changed Keys + + //Merge + $keys = array_merge($intact, $keys); + $values = array_merge($intactValues, $values); + + return strtr($format, array_combine($keys, $values)); + } + + /** + * @param $format + * @param bool $stamp + * @param null $timezone + * @return mixed + */ + public static function strftime($format, $stamp = false, $timezone = null) + { + $str_format_code = array( + "%a", + "%A", + "%d", + "%e", + "%j", + "%u", + "%w", + "%U", + "%V", + "%W", + "%b", + "%B", + "%h", + "%m", + "%C", + "%g", + "%G", + "%y", + "%Y", + "%H", + "%I", + "%l", + "%M", + "%p", + "%P", + "%r", + "%R", + "%S", + "%T", + "%X", + "%z", + "%Z", + "%c", + "%D", + "%F", + "%s", + "%x", + "%n", + "%t", + "%%", + ); + + $date_format_code = array( + "D", + "l", + "d", + "j", + "z", + "N", + "w", + "W", + "W", + "W", + "M", + "F", + "M", + "m", + "y", + "y", + "y", + "y", + "Y", + "H", + "h", + "g", + "i", + "A", + "a", + "h:i:s A", + "H:i", + "s", + "H:i:s", + "h:i:s", + "H", + "H", + "D j M H:i:s", + "d/m/y", + "Y-m-d", + "U", + "d/m/y", + "\n", + "\t", + "%", + ); + + //Change Strftime format to Date format + $format = str_replace($str_format_code, $date_format_code, $format); + + //Convert to date + return self::date($format, $stamp, $timezone); + } + + private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false) + { + switch (strtolower($day)) { + case 'sat': + case 'saturday': + $ret = 'شنبه'; + $n = 1; + break; + case 'sun': + case 'sunday': + $ret = 'یکشنبه'; + $n = 2; + break; + case 'mon': + case 'monday': + $ret = 'دوشنبه'; + $n = 3; + break; + case 'tue': + case 'tuesday': + $ret = 'سه‌شنبه'; + $n = 4; + break; + case 'wed': + case 'wednesday': + $ret = 'چهارشنبه'; + $n = 5; + break; + case 'thu': + case 'thursday': + $ret = 'پنج‌شنبه'; + $n = 6; + break; + case 'fri': + case 'friday': + $ret = 'جمعه'; + $n = 7; + break; + default: + $ret = ''; + $n = -1; + } + + return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret); + } + + private static function getMonthName($month, $shorten = false, $len = 3) + { + $monthIndex = ((int)$month) -1 ; + $monthName = static::$monthNames[$monthIndex]; + return ($shorten) ? mb_substr($monthName, 0, $len, 'UTF-8') : $monthName; + } + + private static function filterArray($needle, $haystack, $always = array()) + { + foreach ($haystack as $k => $v) { + if (!in_array($v, $needle) && !in_array($v, $always)) { + unset($haystack[$k]); + } + } + + + return $haystack; + } + + + /** + * @param $format + * @param $date + * @return array + */ + public static function parseFromFormat($format, $date) + { + // reverse engineer date formats + $keys = array( + 'Y' => array('year', '\d{4}'), + 'y' => array('year', '\d{2}'), + 'm' => array('month', '\d{2}'), + 'n' => array('month', '\d{1,2}'), + 'M' => array('month', '[A-Z][a-z]{3}'), + 'F' => array('month', '[A-Z][a-z]{2,8}'), + 'd' => array('day', '\d{2}'), + 'j' => array('day', '\d{1,2}'), + 'D' => array('day', '[A-Z][a-z]{2}'), + 'l' => array('day', '[A-Z][a-z]{6,9}'), + 'u' => array('hour', '\d{1,6}'), + 'h' => array('hour', '\d{2}'), + 'H' => array('hour', '\d{2}'), + 'g' => array('hour', '\d{1,2}'), + 'G' => array('hour', '\d{1,2}'), + 'i' => array('minute', '\d{2}'), + 's' => array('second', '\d{2}'), + ); + + // convert format string to regex + $regex = ''; + $chars = str_split($format); + foreach ($chars as $n => $char) { + $lastChar = isset($chars[$n - 1]) ? $chars[$n - 1] : ''; + $skipCurrent = '\\' == $lastChar; + if (!$skipCurrent && isset($keys[$char])) { + $regex .= '(?P<' . $keys[$char][0] . '>' . $keys[$char][1] . ')'; + } else { + if ('\\' == $char) { + $regex .= $char; + } else { + $regex .= preg_quote($char); + } + } + } + + $dt = array(); + $dt['error_count'] = 0; + // now try to match it + if (preg_match('#^' . $regex . '$#', $date, $dt)) { + foreach ($dt as $k => $v) { + if (is_int($k)) { + unset($dt[$k]); + } + } + if (!CalendarUtils::checkdate($dt['month'], $dt['day'], $dt['year'], false)) { + $dt['error_count'] = 1; + } + } else { + $dt['error_count'] = 1; + } + $dt['errors'] = array(); + $dt['fraction'] = ''; + $dt['warning_count'] = 0; + $dt['warnings'] = array(); + $dt['is_localtime'] = 0; + $dt['zone_type'] = 0; + $dt['zone'] = 0; + $dt['is_dst'] = ''; + + if (strlen($dt['year']) == 2) { + $now = Jalalian::forge('now'); + $x = $now->format('Y') - $now->format('y'); + $dt['year'] += $x; + } + + $dt['year'] = isset($dt['year']) ? (int)$dt['year'] : 0; + $dt['month'] = isset($dt['month']) ? (int)$dt['month'] : 0; + $dt['day'] = isset($dt['day']) ? (int)$dt['day'] : 0; + $dt['hour'] = isset($dt['hour']) ? (int)$dt['hour'] : 0; + $dt['minute'] = isset($dt['minute']) ? (int)$dt['minute'] : 0; + $dt['second'] = isset($dt['second']) ? (int)$dt['second'] : 0; + + return $dt; + } + + /** + * @param $format + * @param $str + * @param null $timezone + * @return \DateTime + */ + public static function createDatetimeFromFormat($format, $str, $timezone = null) + { + $pd = self::parseFromFormat($format, $str); + $gd = self::toGregorian($pd['year'], $pd['month'], $pd['day']); + $date = self::createDateTime('now', $timezone); + $date->setDate($gd[0], $gd[1], $gd[2]); + $date->setTime($pd['hour'], $pd['minute'], $pd['second']); + + return $date; + } + + /** + * @param $format + * @param $str + * @param null $timezone + * @return Carbon + */ + public static function createCarbonFromFormat($format, $str, $timezone = null) + { + $dateTime = self::createDatetimeFromFormat($format, $str, $timezone); + + return Carbon::createFromTimestamp($dateTime->getTimestamp(), $dateTime->getTimezone()); + } + + /** + * Convert Latin numbers to persian numbers and vice versa + * + * @param string $string + * @param boolean $toEnglish, default is false to save compatiblity + * @return string + */ + public static function convertNumbers($string, $toLatin = false) + { + $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"); + $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + if (!$toLatin) { + return str_replace($english_array, $farsi_array, $string); + } + return str_replace($farsi_array, $english_array, $string); + } + + /** + * @param $timestamp + * @param null $timezone + * @return \DateTime|static + */ + public static function createDateTime($timestamp = null, $timezone = null) + { + $timezone = static::createTimeZone($timezone); + + if ($timestamp === null) { + return Carbon::now($timezone); + } + + + if ($timestamp instanceof \DateTimeInterface) { + return $timestamp; + } + + if (is_string($timestamp)) { + return new \DateTime($timestamp, $timezone); + } + + if (is_numeric($timestamp)) { + return Carbon::createFromTimestamp($timestamp, $timezone); + } + + + throw new \InvalidArgumentException('timestamp is not valid'); + } + + /** + * @param null $timezone + * @return \DateTimeZone|null + */ + public static function createTimeZone($timezone = null) + { + if ($timezone instanceof \DateTimeZone) { + return $timezone; + } + + if ($timezone === null) { + return new \DateTimeZone(date_default_timezone_get()); + } + + if (is_string($timezone)) { + return new \DateTimeZone($timezone); + } + + + throw new \InvalidArgumentException('timezone is not valid'); + } +} diff --git a/src/Converter.php b/src/Converter.php new file mode 100644 index 0000000..78ca211 --- /dev/null +++ b/src/Converter.php @@ -0,0 +1,170 @@ +format("Y/m/d"); + } + + /** + * Format the instance as a readable date + * + * @return string + */ + public function toFormattedDateString() + { + return $this->format('j F Y'); + } + + /** + * Format the instance with the day, and a readable date + * + * @return string + */ + public function toFormattedDayDateString() + { + return $this->format('l j F Y'); + } + + /** + * Format the instance as time + * + * @param string $unitPrecision + * + * @return string + */ + public function toTimeString($unitPrecision = 'second') + { + return $this->format(static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as date and time + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeString($unitPrecision = 'second') + { + return $this->format('Y/m/d ' . static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance as a readable date and time + * + * @param string $unitPrecision + * + * @return string + */ + public function toFormattedDateTimeString($unitPrecision = 'second') + { + return $this->format('j F Y ' . static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Return a format from H:i to H:i:s.u according to given unit precision. + * + * @param string $unitPrecision "minute", "second", "millisecond" or "microsecond" + * + * @return string + */ + public static function getTimeFormatByPrecision($unitPrecision) + { + switch (Date::singularUnit($unitPrecision)) { + case 'minute': + return 'H:i'; + case 'second': + return 'H:i:s'; + case 'm': + case 'millisecond': + return 'H:i:s.v'; + case 'µ': + case 'microsecond': + return 'H:i:s.u'; + } + + throw new UnitException('Precision unit expected among: minute, second, millisecond and microsecond.'); + } + + /** + * Format the instance as date and time T-separated with no timezone + * echo Jalalian::now()->toDateTimeLocalString('minute'); // You can specify precision among: minute, second, millisecond and microsecond + * ``` + * + * @param string $unitPrecision + * + * @return string + */ + public function toDateTimeLocalString($unitPrecision = 'second') + { + return $this->format('Y-m-d\T' . static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with day, date and time + * + * @param string $unitPrecision + * + * @return string + */ + public function toDayDateTimeString($unitPrecision = 'second') + { + return $this->format('l j F Y ' . static::getTimeFormatByPrecision($unitPrecision)); + } + + /** + * Format the instance with the year, and a readable month + * + * @return string + */ + public function toFormattedMonthYearString() + { + return $this->format('F Y'); + } + + /** + * Change persian day name to CarbonInterface + * + * @param string $modifier + * + * @return int + */ + public function toCarbonDayName(string $modifier): int + { + $carbonDayModifiers = [ + 'شنبه' => CarbonInterface::SATURDAY, + 'یکشنبه' => CarbonInterface::SUNDAY, + 'دوشنبه' => CarbonInterface::MONDAY, + 'سه‌شنبه' => CarbonInterface::TUESDAY, + 'چهارشنبه' => CarbonInterface::WEDNESDAY, + 'پنج‌شنبه' => CarbonInterface::THURSDAY, + 'جمعه' => CarbonInterface::FRIDAY, + ]; + + if (!isset($carbonDayModifiers[$modifier])) { + throw new \InvalidArgumentException('Modifier expected among: شنبه, یکشنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه و جمعه.'); + } + + return $carbonDayModifiers[$modifier]; + } +} diff --git a/src/Jalalian.php b/src/Jalalian.php new file mode 100644 index 0000000..75738ab --- /dev/null +++ b/src/Jalalian.php @@ -0,0 +1,806 @@ + 6) { + Assertion::between($day, 1, 30); + } + + if (!CalendarUtils::isLeapJalaliYear($year) && $month === 12) { + Assertion::between($day, 1, 29); + } + Assertion::between($hour, 0, 24); + Assertion::between($minute, 0, 59); + Assertion::between($second, 0, 59); + + $this->year = $year; + $this->month = $month; + $this->day = $day; + $this->hour = $hour; + $this->minute = $minute; + $this->second = $second; + $this->timezone = $timezone; + } + + public static function now(\DateTimeZone $timeZone = null): Jalalian + { + return static::fromCarbon(Carbon::now($timeZone)); + } + + /** + * @param Carbon $carbon + * @return Jalalian + */ + public static function fromCarbon(Carbon $carbon): Jalalian + { + $jDate = CalendarUtils::toJalali($carbon->year, $carbon->month, $carbon->day); + + return new static( + $jDate[0], + $jDate[1], + $jDate[2], + $carbon->hour, + $carbon->minute, + $carbon->second, + $carbon->getTimezone() + ); + } + + public static function fromFormat(string $format, string $timestamp, \DateTimeZone $timeZone = null): Jalalian + { + return static::fromCarbon(CalendarUtils::createCarbonFromFormat($format, $timestamp, $timeZone)); + } + + public static function forge($timestamp, \DateTimeZone $timeZone = null): Jalalian + { + return static::fromDateTime($timestamp, $timeZone); + } + + /** + * @param \DateTimeInterface| string $dateTime + * @param \DateTimeZone|null $timeZone + * @return Jalalian + */ + public static function fromDateTime($dateTime, \DateTimeZone $timeZone = null): Jalalian + { + if (is_numeric($dateTime)) { + return static::fromCarbon(Carbon::createFromTimestamp($dateTime, $timeZone)); + } + + return static::fromCarbon(new Carbon($dateTime, $timeZone)); + } + + public function getFirstDayOfWeek(): Jalalian + { + return (new static( + $this->getYear(), + $this->getMonth(), + $this->getDay(), + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ))->subDays($this->getDayOfWeek()); + } + + public function getFirstDayOfMonth(): Jalalian + { + return new static( + $this->getYear(), + $this->getMonth(), + 1, + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getFirstDayOfYear(): Jalalian + { + return new static( + $this->getYear(), + 1, + 1, + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getFirstDayOfQuarter(): Jalalian + { + return new static( + $this->getYear(), + ($this->getQuarter() - 1) * Carbon::MONTHS_PER_QUARTER + 1, + 1, + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getEndDayOfWeek(): Jalalian + { + $endWeek = $this->addDays(6 - $this->getDayOfWeek()); + + return (new static( + $endWeek->getYear(), + $endWeek->getMonth(), + $endWeek->getDay(), + $endWeek->getHour(), + $endWeek->getMinute(), + $endWeek->getSecond(), + $endWeek->getTimezone() + )); + } + + public function getEndDayOfMonth(): Jalalian + { + return new static( + $this->getYear(), + $this->getMonth(), + $this->getDaysOf($this->getMonth()), + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getEndDayOfYear(): Jalalian + { + return new static( + $this->getYear(), + 12, + $this->getDaysOf(12), + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getEndDayOfQuarter(): Jalalian + { + return new static( + $this->getYear(), + $this->getQuarter() * Carbon::MONTHS_PER_QUARTER, + $this->getDaysOf($this->getQuarter() * Carbon::MONTHS_PER_QUARTER), + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function getMonthDays() + { + if ($this->getMonth() <= 6) { + return 31; + } + + if ($this->getMonth() < 12 || $this->isLeapYear()) { + return 30; + } + + return 29; + } + + /** + * @return int + */ + public function getMonth(): int + { + return $this->month; + } + + public function isLeapYear(): bool + { + return CalendarUtils::isLeapJalaliYear($this->getYear()); + } + + /** + * @return int + */ + public function getYear() + { + return $this->year; + } + + public function getQuarter(): int + { + return (int) ceil($this->getMonth() / Carbon::MONTHS_PER_QUARTER); + } + + public function subMonths(int $months = 1): Jalalian + { + Assertion::greaterOrEqualThan($months, 1); + + $diff = ($this->getMonth() - $months); + + if ($diff >= 1) { + $day = $this->getDay(); + $targetMonthDays = $this->getDaysOf($diff); + $targetDay = $day <= $targetMonthDays ? $day : $targetMonthDays; + + return new static( + $this->getYear(), + $diff, + $targetDay, + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + $years = abs((int)($diff / 12)); + $date = $years > 0 ? $this->subYears($years) : clone $this; + $diff = 12 - abs($diff % 12) - $date->getMonth(); + + return $diff > 0 ? $date->subYears(1)->addMonths($diff) : $date->subYears(1); + } + + /** + * @return int + */ + public function getDay(): int + { + return $this->day; + } + + public function getDaysOf(int $monthNumber = 1): int + { + Assertion::between($monthNumber, 1, 12); + + $months = [ + 1 => 31, + 2 => 31, + 3 => 31, + 4 => 31, + 5 => 31, + 6 => 31, + 7 => 30, + 8 => 30, + 9 => 30, + 10 => 30, + 11 => 30, + 12 => $this->isLeapYear() ? 30 : 29, + ]; + + return $months[$monthNumber]; + } + + /** + * @return int + */ + public function getHour(): int + { + return $this->hour; + } + + /** + * @return int + */ + public function getMinute(): int + { + return $this->minute; + } + + /** + * @return int + */ + public function getSecond(): int + { + return $this->second; + } + + /** + * @return \DateTimeZone|null + */ + public function getTimezone() + { + return $this->timezone; + } + + public function subYears(int $years = 1): Jalalian + { + Assertion::greaterOrEqualThan($years, 1); + + return new static( + $this->getYear() - $years, + $this->getMonth(), + $this->getDay(), + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function addMonths(int $months = 1): Jalalian + { + Assertion::greaterOrEqualThan($months, 1); + + $years = (int)($months / 12); + $months = (int)($months % 12); + $date = $years > 0 ? $this->addYears($years) : clone $this; + + while ($months > 0) { + $nextMonth = ($date->getMonth() + 1) % 12; + $nextMonthDays = $date->getDaysOf($nextMonth === 0 ? 12 : $nextMonth); + $nextMonthDay = $date->getDay() <= $nextMonthDays ? $date->getDay() : $nextMonthDays; + + $days = ($date->getMonthDays() - $date->getDay()) + $nextMonthDay; + + $date = $date->addDays($days); + $months--; + } + + return $date; + } + + public function addYears(int $years = 1): Jalalian + { + Assertion::greaterOrEqualThan($years, 1); + + $year = $this->getYear() + $years; + if (false === CalendarUtils::isLeapJalaliYear($year) && $this->getMonth() === 12 && $this->getDay() === $this->getDaysOf(12)) { + $day = 29; + } else { + $day = $this->getDay(); + } + + return new static( + $year, + $this->getMonth(), + $day, + $this->getHour(), + $this->getMinute(), + $this->getSecond(), + $this->getTimezone() + ); + } + + public function subDays(int $days = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->subDays($days)); + } + + public function subDay(): Jalalian + { + return $this->subDays(1); + } + + /** + * @return Carbon + */ + public function toCarbon(): Carbon + { + $gDate = CalendarUtils::toGregorian($this->getYear(), $this->getMonth(), $this->getDay()); + $carbon = Carbon::createFromDate($gDate[0], $gDate[1], $gDate[2], $this->getTimezone()); + + $carbon->setTime($this->getHour(), $this->getMinute(), $this->getSecond()); + + return $carbon; + } + + public function addHours(int $hours = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->addHours($hours)); + } + + public function subHours(int $hours = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->subHours($hours)); + } + + public function addMinutes(int $minutes = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->addMinutes($minutes)); + } + + public function subMinutes(int $minutes = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->subMinutes($minutes)); + } + + public function addSeconds(int $secs = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->addSeconds($secs)); + } + + public function subSeconds(int $secs = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->subSeconds($secs)); + } + + public function equalsTo(Jalalian $other): bool + { + return $this->equalsToCarbon($other->toCarbon()); + } + + public function equalsToCarbon(Carbon $carbon): bool + { + return $this->toCarbon()->equalTo($carbon); + } + + public function greaterThan(Jalalian $other): bool + { + return $this->greaterThanCarbon($other->toCarbon()); + } + + public function greaterThanCarbon(Carbon $carbon): bool + { + return $this->toCarbon()->greaterThan($carbon); + } + + public function lessThan(Jalalian $other): bool + { + return $this->lessThanCarbon($other->toCarbon()); + } + + public function lessThanCarbon(Carbon $carbon): bool + { + return $this->toCarbon()->lessThan($carbon); + } + + public function greaterThanOrEqualsTo(Jalalian $other): bool + { + return $this->greaterThanOrEqualsToCarbon($other->toCarbon()); + } + + public function greaterThanOrEqualsToCarbon(Carbon $carbon): bool + { + return $this->toCarbon()->greaterThanOrEqualTo($carbon); + } + + public function lessThanOrEqualsTo(Jalalian $other): bool + { + return $this->lessThanOrEqualsToCarbon($other->toCarbon()); + } + + public function lessThanOrEqualsToCarbon(Carbon $carbon): bool + { + return $this->toCarbon()->lessThanOrEqualTo($carbon); + } + + public function isStartOfWeek(): bool + { + return $this->isSaturday(); + } + + public function isSaturday(): bool + { + return $this->isDayOfWeek(Carbon::SATURDAY); + } + + public function isDayOfWeek(int $day): bool + { + Assertion::between($day, 0, 6); + return $this->toCarbon()->isDayOfWeek($day); + } + + public function isEndOfWeek(): bool + { + return $this->isFriday(); + } + + public function isFriday(): bool + { + return $this->isDayOfWeek(Carbon::FRIDAY); + } + + public function isToday(): bool + { + return $this->toCarbon()->isToday(); + } + + public function isTomorrow(): bool + { + return $this->toCarbon()->isTomorrow(); + } + + public function isYesterday(): bool + { + return $this->toCarbon()->isYesterday(); + } + + public function isFuture(): bool + { + return $this->toCarbon()->isFuture(); + } + + public function isPast(): bool + { + return $this->toCarbon()->isPast(); + } + + public function toArray(): array + { + return [ + 'year' => $this->year, + 'month' => $this->month, + 'day' => $this->day, + 'dayOfWeek' => $this->getDayOfWeek(), + 'dayOfYear' => $this->getDayOfYear(), + 'hour' => $this->hour, + 'minute' => $this->minute, + 'second' => $this->second, + 'micro' => $this->toCarbon()->micro, + 'timestamp' => $this->toCarbon()->timestamp, + 'formatted' => $this->toString(), + 'timezone' => $this->timezone, + ]; + } + + public function getDayOfWeek(): int + { + if ($this->isSaturday()) { + return 0; + } + + if ($this->isSunday()) { + return 1; + } + + if ($this->isMonday()) { + return 2; + } + + if ($this->isTuesday()) { + return 3; + } + + if ($this->isWednesday()) { + return 4; + } + + if ($this->isThursday()) { + return 5; + } + + return 6; + } + + public function isSunday(): bool + { + return $this->isDayOfWeek(Carbon::SUNDAY); + } + + public function isMonday(): bool + { + return $this->isDayOfWeek(Carbon::MONDAY); + } + + public function isTuesday(): bool + { + return $this->isDayOfWeek(Carbon::TUESDAY); + } + + public function isWednesday(): bool + { + return $this->isDayOfWeek(Carbon::WEDNESDAY); + } + + public function isThursday(): bool + { + return $this->isDayOfWeek(Carbon::THURSDAY); + } + + public function getDayOfYear(): int + { + $dayOfYear = 0; + for ($m = 1; $m < $this->getMonth(); $m++) { + if ($m <= 6) { + $dayOfYear += 31; + continue; + } + + if ($m < 12) { + $dayOfYear += 30; + continue; + } + } + + return $dayOfYear + $this->getDay(); + } + + public function toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + public function format(string $format): string + { + return CalendarUtils::strftime($format, $this->toCarbon()); + } + + public function __toString(): string + { + return $this->toString(); + } + + public function ago(): string + { + $now = time(); + $time = $this->getTimestamp(); + + // catch error + if (!$time) { + return false; + } + + // build period and length arrays + $periods = ['ثانیه', 'دقیقه', 'ساعت', 'روز', 'هفته', 'ماه', 'سال', 'قرن']; + $lengths = [60, 60, 24, 7, 4.35, 12, 10]; + + // get difference + $difference = $now - $time; + + // set descriptor + if ($difference < 0) { + $difference = abs($difference); // absolute value + $negative = true; + } + + // do math + for ($j = 0; $difference >= $lengths[$j] and $j < count($lengths) - 1; $j++) { + $difference /= $lengths[$j]; + } + + // round difference + $difference = intval(round($difference)); + + // return + return number_format($difference) . ' ' . $periods[$j] . ' ' . (isset($negative) ? '' : 'پیش'); + } + + public function getTimestamp(): int + { + return $this->toCarbon()->getTimestamp(); + } + + public function getNextWeek(): Jalalian + { + return $this->addDays(7); + } + + public function getLastWeek(): Jalalian + { + return $this->subDays(7); + } + + public function addDays(int $days = 1): Jalalian + { + return static::fromCarbon($this->toCarbon()->addDays($days)); + } + + public function addDay(): Jalalian + { + return $this->addDays(1); + } + + public function getNextMonth(): Jalalian + { + return $this->addMonths(1); + } + + public function getLastMonth(): Jalalian + { + return $this->subMonths(1); + } + + public function getWeekOfMonth(): int + { + return floor(($this->day + 5 - $this->getDayOfWeek()) / 7) + 1; + } + + public function diff(Jalalian $ref): array + { + if ($this->equalsTo($ref)) { + return [0, 0, 0]; + } + + $biggerDate = $this->greaterThan($ref) ? $this : $ref; + $biggerYear = $biggerDate->getYear(); + $biggerMonth = $biggerDate->getMonth(); + $biggerDay = $biggerDate->getDay(); + $smallerDate = $this->greaterThan($ref) ? $ref : $this; + $smallerYear = $smallerDate->getYear(); + $smallerMonth = $smallerDate->getMonth(); + $smallerDay = $smallerDate->getDay(); + + $yearDiff = $biggerYear - $smallerYear; + $monthDiff = $biggerMonth - $smallerMonth; + $dayDiff = $biggerDay - $smallerDay; + if ($dayDiff < 0) { + $dayDiff = $biggerDay + + $smallerDate->getEndDayOfMonth()->getDay() - $smallerDate->getDay(); + $monthDiff--; + } + if ($monthDiff < 0) { + $monthDiff += 12; + $yearDiff--; + } + + return [$yearDiff, $monthDiff, $dayDiff]; + } + + /** + * @param string $dayName One of: شنبه, یکشنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه, جمعه + * @return Jalalian + */ + public function next(string $dayName): Jalalian + { + return $this->fromCarbon($this->toCarbon()->next($this->toCarbonDayName($dayName))); + } + + /** + * @param string $dayName One of: شنبه, یکشنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه, جمعه + * @return Jalalian + */ + public function previous(string $dayName): Jalalian + { + return $this->fromCarbon($this->toCarbon()->previous($this->toCarbonDayName($dayName))); + } +} diff --git a/src/Miladr/Jalali/JalaliServiceProvider.php b/src/Miladr/Jalali/JalaliServiceProvider.php deleted file mode 100644 index d56eafc..0000000 --- a/src/Miladr/Jalali/JalaliServiceProvider.php +++ /dev/null @@ -1,64 +0,0 @@ -package('miladr/jalali'); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - $this->app['jalali'] = $this->app->share(function($app) - { - return new jDate; - }); - $this->app->booting(function() - { - $loader = \Illuminate\Foundation\AliasLoader::getInstance(); - $loader->alias('jDate', 'Miladr\Jalali\jDate'); - }); - - $this->app['jDateTime'] = $this->app->share(function($app) - { - return new jDateTime; - }); - $this->app->booting(function() - { - $loader = \Illuminate\Foundation\AliasLoader::getInstance(); - $loader->alias('jDateTime', 'Miladr\Jalali\jDateTime'); - }); - - - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array('jalali','jDateTime'); - } - -} diff --git a/src/Miladr/Jalali/jDate.php b/src/Miladr/Jalali/jDate.php deleted file mode 100644 index 6fc081f..0000000 --- a/src/Miladr/Jalali/jDate.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * - * Based on Laravel-Date bundle - * by Scott Travis - * http://github.com/swt83/laravel-date - * - * - * @package jDate - * @author Sallar Kaboli - * @link http:// - * @basedon http://github.com/swt83/laravel-date - * @license MIT License - */ - -class jDate -{ - protected $time; - - protected $formats = array( - 'datetime' => '%Y-%m-%d %H:%M:%S', - 'date' => '%Y-%m-%d', - 'time' => '%H:%M:%S', - ); - - public static function forge($str = null) - { - $class = __CLASS__; - return new $class($str); - } - - public function __construct($str = null) - { - if ($str === null){ - $this->time = time(); - } - else - { - if (is_numeric($str)){ - $this->time = $str; - } - else - { - $time = strtotime($str); - - if (!$time){ - $this->time = false; - } - else{ - $this->time = $time; - } - } - } - } - - public function time() - { - return $this->time; - } - - public function format($str) - { - // convert alias string - if (in_array($str, array_keys($this->formats))){ - $str = $this->formats[$str]; - } - - // if valid unix timestamp... - if ($this->time !== false){ - return jDateTime::strftime($str, $this->time); - } - else{ - return false; - } - } - - public function reforge($str) - { - if ($this->time !== false) - { - // amend the time - $time = strtotime($str, $this->time); - - // if conversion fails... - if (!$time){ - // set time as false - $this->time = false; - } - else{ - // accept time value - $this->time = $time; - } - } - - return $this; - } - - public function ago() - { - $now = time(); - $time = $this->time(); - - // catch error - if (!$time) return false; - - // build period and length arrays - $periods = array('ثانیه', 'دقیقه', 'ساعت', 'روز', 'هفته', 'ماه', 'سال', 'قرن'); - $lengths = array(60, 60, 24, 7, 4.35, 12, 10); - - // get difference - $difference = $now - $time; - - // set descriptor - if ($difference < 0) - { - $difference = abs($difference); // absolute value - $negative = true; - } - - // do math - for($j = 0; $difference >= $lengths[$j] and $j < count($lengths)-1; $j++){ - $difference /= $lengths[$j]; - } - - // round difference - $difference = intval(round($difference)); - - // return - return number_format($difference).' '.$periods[$j].' '.(isset($negative) ? '' : 'پیش'); - } - - public function until() - { - return $this->ago(); - } -} \ No newline at end of file diff --git a/src/Miladr/Jalali/jDateTime.php b/src/Miladr/Jalali/jDateTime.php deleted file mode 100644 index 640c596..0000000 --- a/src/Miladr/Jalali/jDateTime.php +++ /dev/null @@ -1,574 +0,0 @@ -= 5.2 - * - * PHP's default 'date' function does not support years higher than - * 2038. Intorduced in PHP5, DateTime class supports higher years - * and also invalid date entries. - * Also, Persian users are using classic 'jdate' function for years now - * and beside the fact that it's amazing and also helped me to write this - * one, it's really out of date, and can't be used in modern real world - * web applications as it is completely written in functions. - * - * Copyright (C) 2012 Sallar Kaboli (http://sallar.me) - * Part of Phoenix Framework (p5x.org) by Phoenix Alternatvie - * - * Original Jalali to Gregorian (and vice versa) convertor: - * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi - * - * List of supported timezones can be found here: - * http://www.php.net/manual/en/timezones.php - * - * PHP version 5 - * - * LICENSE: This source file is subject to version 3.01 of the PHP license - * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_01.txt. If you did not receive a copy of - * the PHP License and are unable to obtain it through the web, please - * send a note to license@php.net so we can mail you a copy immediately. - * - * @package jDateTime - * @author Sallar Kaboli - * @author Omid Pilevar - * @copyright 2003-2012 Sallar Kaboli - * @license http://www.php.net/license/3_01.txt PHP License 3.01 - * @link http://sallar.me/projects/jdatetime/ - * @see DateTime - * @version 2.0.0 - */ -class jDateTime -{ - - /** - * Defaults - */ - private static $jalali = true; //Use Jalali Date, If set to false, falls back to gregorian - private static $convert = true; //Convert numbers to Farsi characters in utf-8 - private static $timezone = null; //Timezone String e.g Asia/Tehran, Defaults to Server Timezone Settings - private static $temp = array(); - - /** - * jDateTime::Constructor - * - * Pass these parameteres when creating a new instance - * of this Class, and they will be used as defaults. - * e.g $obj = new jDateTime(false, true, 'Asia/Tehran'); - * To use system defaults pass null for each one or just - * create the object without any parameters. - * - * @author Sallar Kaboli - * @param $convert bool Converts numbers to Farsi - * @param $jalali bool Converts date to Jalali - * @param $timezone string Timezone string - */ - public function __construct($convert = null, $jalali = null, $timezone = null) - { - if ( $jalali !== null ) self::$jalali = ($jalali === false) ? false : true; - if ( $convert !== null ) self::$convert = ($convert === false) ? false : true; - if ( $timezone !== null ) self::$timezone = ($timezone != null) ? $timezone : null; - } - - /** - * jDateTime::Date - * - * Formats and returns given timestamp just like php's - * built in date() function. - * e.g: - * $obj->date("Y-m-d H:i", time()); - * $obj->date("Y-m-d", time(), false, false, 'America/New_York'); - * - * @author Sallar Kaboli - * @param $format string Acceps format string based on: php.net/date - * @param $stamp int Unix Timestamp (Epoch Time) - * @param $convert bool (Optional) forces convert action. pass null to use system default - * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default - * @param $timezone string (Optional) forces a different timezone. pass null to use system default - * @return string Formatted input - */ - public static function date($format, $stamp = false, $convert = null, $jalali = null, $timezone = null) - { - //Timestamp + Timezone - $stamp = ($stamp != false) ? $stamp : time(); - $timezone = ($timezone != null) ? $timezone : ((self::$timezone != null) ? self::$timezone : date_default_timezone_get()); - $obj = new \DateTime('@' . $stamp); - $obj->setTimezone(new \DateTimeZone($timezone)); - - if ( (self::$jalali === false && $jalali === null) || $jalali === false ) { - return $obj->format($format); - } - else { - - //Find what to replace - $chars = (preg_match_all('/([a-zA-Z]{1})/', $format, $chars)) ? $chars[0] : array(); - - //Intact Keys - $intact = array('B','h','H','g','G','i','s','I','U','u','Z','O','P'); - $intact = self::filterArray($chars, $intact); - $intactValues = array(); - - foreach ($intact as $k => $v) { - $intactValues[$k] = $obj->format($v); - } - //End Intact Keys - - - //Changed Keys - list($year, $month, $day) = array($obj->format('Y'), $obj->format('n'), $obj->format('j')); - list($jyear, $jmonth, $jday) = self::toJalali($year, $month, $day); - - $keys = array('d','D','j','l','N','S','w','z','W','F','m','M','n','t','L','o','Y','y','a','A','c','r','e','T'); - $keys = self::filterArray($chars, $keys, array('z')); - $values = array(); - - foreach ($keys as $k => $key) { - - $v = ''; - switch ($key) { - //Day - case 'd': - $v = sprintf("%02d", $jday); - break; - case 'D': - $v = self::getDayNames($obj->format('D'), true); - break; - case 'j': - $v = $jday; - break; - case 'l': - $v = self::getDayNames($obj->format('l')); - break; - case 'N': - $v = self::getDayNames($obj->format('l'), false, 1, true); - break; - case 'S': - $v = 'ام'; - break; - case 'w': - $v = self::getDayNames($obj->format('l'), false, 1, true) - 1; - break; - case 'z': - if ($jmonth > 6) { - $v = 186 + (($jmonth - 6 - 1) * 30) + $jday; - } - else { - $v = (($jmonth - 1) * 31) + $jday; - } - self::$temp['z'] = $v; - break; - //Week - case 'W': - $v = is_int(self::$temp['z'] / 7) ? (self::$temp['z'] / 7) : intval(self::$temp['z'] / 7 + 1); - break; - //Month - case 'F': - $v = self::getMonthNames($jmonth); - break; - case 'm': - $v = sprintf("%02d", $jmonth); - break; - case 'M': - $v = self::getMonthNames($jmonth, true); - break; - case 'n': - $v = $jmonth; - break; - case 't': - $v = ( $jmonth == 12 ) ? 29 : ( ($jmonth > 6 && $jmonth != 12) ? 30 : 31 ); - break; - //Year - case 'L': - $tmpObj = new \DateTime('@'.(time()-31536000)); - $v = $tmpObj->format('L'); - break; - case 'o': - case 'Y': - $v = $jyear; - break; - case 'y': - $v = $jyear % 100; - break; - //Time - case 'a': - $v = ($obj->format('a') == 'am') ? 'ق.ظ' : 'ب.ظ'; - break; - case 'A': - $v = ($obj->format('A') == 'AM') ? 'قبل از ظهر' : 'بعد از ظهر'; - break; - //Full Dates - case 'c': - $v = $jyear.'-'.sprintf("%02d", $jmonth).'-'.sprintf("%02d", $jday).'T'; - $v .= $obj->format('H').':'.$obj->format('i').':'.$obj->format('s').$obj->format('P'); - break; - case 'r': - $v = self::getDayNames($obj->format('D'), true).', '.sprintf("%02d", $jday).' '.self::getMonthNames($jmonth, true); - $v .= ' '.$jyear.' '.$obj->format('H').':'.$obj->format('i').':'.$obj->format('s').' '.$obj->format('P'); - break; - //Timezone - case 'e': - $v = $obj->format('e'); - break; - case 'T': - $v = $obj->format('T'); - break; - - } - $values[$k] = $v; - - } - //End Changed Keys - - //Merge - $keys = array_merge($intact, $keys); - $values = array_merge($intactValues, $values); - - //Return - $ret = strtr($format, array_combine($keys, $values)); - return - ($convert === false || - ($convert === null && self::$convert === false) || - ( $jalali === false || $jalali === null && self::$jalali === false )) - ? $ret : self::convertNumbers($ret); - - } - - } - - /** - * jDateTime::gDate - * - * Same as jDateTime::Date method - * but this one works as a helper and returns Gregorian Date - * in case someone doesn't like to pass all those false arguments - * to Date method. - * - * e.g. $obj->gDate("Y-m-d") //Outputs: 2011-05-05 - * $obj->date("Y-m-d", false, false, false); //Outputs: 2011-05-05 - * Both return the exact same result. - * - * @author Sallar Kaboli - * @param $format string Acceps format string based on: php.net/date - * @param $stamp int Unix Timestamp (Epoch Time) - * @param $timezone string (Optional) forces a different timezone. pass null to use system default - * @return string Formatted input - */ - public static function gDate($format, $stamp = false, $timezone = null) - { - return self::date($format, $stamp, false, false, $timezone); - } - - /** - * jDateTime::Strftime - * - * Format a local time/date according to locale settings - * built in strftime() function. - * e.g: - * $obj->strftime("%x %H", time()); - * $obj->strftime("%H", time(), false, false, 'America/New_York'); - * - * @author Omid Pilevar - * @param $format string Acceps format string based on: php.net/date - * @param $stamp int Unix Timestamp (Epoch Time) - * @param $jalali bool (Optional) forces jalali conversion. pass null to use system default - * @param $timezone string (Optional) forces a different timezone. pass null to use system default - * @return string Formatted input - */ - public static function strftime($format, $stamp = false, $jalali = null, $timezone = null) - { - $str_format_code = array( - "%a", "%A", "%d", "%e", "%j", "%u", "%w", - "%U", "%V", "%W", - "%b", "%B", "%h", "%m", - "%C", "%g", "%G", "%y", "%Y", - "%H", "%I", "%l", "%M", "%p", "%P", "%r", "%R", "%S", "%T", "%X", "%z", "%Z", - "%c", "%D", "%F", "%s", "%x", - "%n", "%t", "%%" - ); - - $date_format_code = array( - "D", "l", "d", "j", "z", "N", "w", - "W", "W", "W", - "M", "F", "M", "m", - "y", "y", "y", "y", "Y", - "H", "h", "g", "i", "A", "a", "h:i:s A", "H:i", "s", "H:i:s", "h:i:s", "H", "H", - "D j M H:i:s", "d/m/y", "Y-m-d", "U", "d/m/y", - "\n", "\t", "%" - ); - - //Change Strftime format to Date format - $format = str_replace($str_format_code, $date_format_code, $format); - - //Convert to date - return self::date($format, $stamp, $jalali, $timezone); - } - - /** - * jDateTime::Mktime - * - * Creates a Unix Timestamp (Epoch Time) based on given parameters - * works like php's built in mktime() function. - * e.g: - * $time = $obj->mktime(0,0,0,2,10,1368); - * $obj->date("Y-m-d", $time); //Format and Display - * $obj->date("Y-m-d", $time, false, false); //Display in Gregorian ! - * - * You can force gregorian mktime if system default is jalali and you - * need to create a timestamp based on gregorian date - * $time2 = $obj->mktime(0,0,0,12,23,1989, false); - * - * @author Sallar Kaboli - * @param $hour int Hour based on 24 hour system - * @param $minute int Minutes - * @param $second int Seconds - * @param $month int Month Number - * @param $day int Day Number - * @param $year int Four-digit Year number eg. 1390 - * @param $jalali bool (Optional) pass false if you want to input gregorian time - * @param $timezone string (Optional) acceps an optional timezone if you want one - * @return int Unix Timestamp (Epoch Time) - */ - public static function mktime($hour, $minute, $second, $month, $day, $year, $jalali = null, $timezone = null) - { - //Defaults - $month = (intval($month) == 0) ? self::date('m') : $month; - $day = (intval($day) == 0) ? self::date('d') : $day; - $year = (intval($year) == 0) ? self::date('Y') : $year; - - //Convert to Gregorian if necessary - if ( $jalali === true || ($jalali === null && self::$jalali === true) ) { - list($year, $month, $day) = self::toGregorian($year, $month, $day); - } - - //Create a new object and set the timezone if available - $date = $year.'-'.sprintf("%02d", $month).'-'.sprintf("%02d", $day).' '.$hour.':'.$minute.':'.$second; - - if ( self::$timezone != null || $timezone != null ) { - $obj = new \DateTime($date, new \DateTimeZone(($timezone != null) ? $timezone : self::$timezone)); - } - else { - $obj = new \DateTime($date); - } - - //Return - return $obj->format("U"); - } - - /** - * jDateTime::Checkdate - * - * Checks the validity of the date formed by the arguments. - * A date is considered valid if each parameter is properly defined. - * works like php's built in checkdate() function. - * Leap years are taken into consideration. - * e.g: - * $obj->checkdate(10, 21, 1390); // Return true - * $obj->checkdate(9, 31, 1390); // Return false - * - * You can force gregorian checkdate if system default is jalali and you - * need to check based on gregorian date - * $check = $obj->checkdate(12, 31, 2011, false); - * - * @author Omid Pilevar - * @param $month int The month is between 1 and 12 inclusive. - * @param $day int The day is within the allowed number of days for the given month. - * @param $year int The year is between 1 and 32767 inclusive. - * @param $jalali bool (Optional) pass false if you want to input gregorian time - * @return bool - */ - public static function checkdate($month, $day, $year, $jalali = null) - { - //Defaults - $month = (intval($month) == 0) ? self::date('n') : intval($month); - $day = (intval($day) == 0) ? self::date('j') : intval($day); - $year = (intval($year) == 0) ? self::date('Y') : intval($year); - - //Check if its jalali date - if ( $jalali === true || ($jalali === null && self::$jalali === true) ) - { - $epoch = self::mktime(0, 0, 0, $month, $day, $year); - - if( self::date("Y-n-j", $epoch,false) == "$year-$month-$day" ) { - $ret = true; - } - else{ - $ret = false; - } - } - else //Gregorian Date - { - $ret = checkdate($month, $day, $year); - } - - //Return - return $ret; - } - - /** - * System Helpers below - * - */ - private static function filterArray($needle, $heystack, $always = array()) - { - foreach($heystack as $k => $v) - { - if( !in_array($v, $needle) && !in_array($v, $always) ) - unset($heystack[$k]); - } - - return $heystack; - } - - private static function getDayNames($day, $shorten = false, $len = 1, $numeric = false) - { - $ret = ''; - switch ( strtolower($day) ) { - case 'sat': case 'saturday': $ret = 'شنبه'; $n = 1; break; - case 'sun': case 'sunday': $ret = 'یکشنبه'; $n = 2; break; - case 'mon': case 'monday': $ret = 'دوشنبه'; $n = 3; break; - case 'tue': case 'tuesday': $ret = 'سه شنبه'; $n = 4; break; - case 'wed': case 'wednesday': $ret = 'چهارشنبه'; $n = 5; break; - case 'thu': case 'thursday': $ret = 'پنجشنبه'; $n = 6; break; - case 'fri': case 'friday': $ret = 'جمعه'; $n = 7; break; - } - return ($numeric) ? $n : (($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret); - } - - private static function getMonthNames($month, $shorten = false, $len = 3) - { - $ret = ''; - switch ( $month ) { - case '1': $ret = 'فروردین'; break; - case '2': $ret = 'اردیبهشت'; break; - case '3': $ret = 'خرداد'; break; - case '4': $ret = 'تیر'; break; - case '5': $ret = 'امرداد'; break; - case '6': $ret = 'شهریور'; break; - case '7': $ret = 'مهر'; break; - case '8': $ret = 'آبان'; break; - case '9': $ret = 'آذر'; break; - case '10': $ret = 'دی'; break; - case '11': $ret = 'بهمن'; break; - case '12': $ret = 'اسفند'; break; - } - return ($shorten) ? mb_substr($ret, 0, $len, 'UTF-8') : $ret; - } - - private static function convertNumbers($matches) - { - $farsi_array = array("۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"); - $english_array = array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); - return str_replace($english_array, $farsi_array, $matches); - } - - private static function div($a, $b) - { - return (int) ($a / $b); - } - - /** - * Gregorian to Jalali Conversion - * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi - * - */ - public static function toJalali($g_y, $g_m, $g_d) - { - - $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); - $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); - - $gy = $g_y-1600; - $gm = $g_m-1; - $gd = $g_d-1; - - $g_day_no = 365*$gy+self::div($gy+3, 4)-self::div($gy+99, 100)+self::div($gy+399, 400); - - for ($i=0; $i < $gm; ++$i) - $g_day_no += $g_days_in_month[$i]; - if ($gm>1 && (($gy%4==0 && $gy%100!=0) || ($gy%400==0))) - $g_day_no++; - $g_day_no += $gd; - - $j_day_no = $g_day_no-79; - - $j_np = self::div($j_day_no, 12053); - $j_day_no = $j_day_no % 12053; - - $jy = 979+33*$j_np+4*self::div($j_day_no, 1461); - - $j_day_no %= 1461; - - if ($j_day_no >= 366) { - $jy += self::div($j_day_no-1, 365); - $j_day_no = ($j_day_no-1)%365; - } - - for ($i = 0; $i < 11 && $j_day_no >= $j_days_in_month[$i]; ++$i) - $j_day_no -= $j_days_in_month[$i]; - $jm = $i+1; - $jd = $j_day_no+1; - - return array($jy, $jm, $jd); - - } - - /** - * Jalali to Gregorian Conversion - * Copyright (C) 2000 Roozbeh Pournader and Mohammad Toossi - * - */ - public static function toGregorian($j_y, $j_m, $j_d) - { - - $g_days_in_month = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); - $j_days_in_month = array(31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29); - - $jy = $j_y-979; - $jm = $j_m-1; - $jd = $j_d-1; - - $j_day_no = 365*$jy + self::div($jy, 33)*8 + self::div($jy%33+3, 4); - for ($i=0; $i < $jm; ++$i) - $j_day_no += $j_days_in_month[$i]; - - $j_day_no += $jd; - - $g_day_no = $j_day_no+79; - - $gy = 1600 + 400*self::div($g_day_no, 146097); - $g_day_no = $g_day_no % 146097; - - $leap = true; - if ($g_day_no >= 36525) { - $g_day_no--; - $gy += 100*self::div($g_day_no, 36524); - $g_day_no = $g_day_no % 36524; - - if ($g_day_no >= 365) - $g_day_no++; - else - $leap = false; - } - - $gy += 4*self::div($g_day_no, 1461); - $g_day_no %= 1461; - - if ($g_day_no >= 366) { - $leap = false; - - $g_day_no--; - $gy += self::div($g_day_no, 365); - $g_day_no = $g_day_no % 365; - } - - for ($i = 0; $g_day_no >= $g_days_in_month[$i] + ($i == 1 && $leap); $i++) - $g_day_no -= $g_days_in_month[$i] + ($i == 1 && $leap); - $gm = $i+1; - $gd = $g_day_no+1; - - return array($gy, $gm, $gd); - - } - -} diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..7cfb212 --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,13 @@ +assertTrue(CalendarUtils::checkDate(1391, 2, 30, true)); + $this->assertFalse(CalendarUtils::checkDate(1395, 13, 10, true)); + $this->assertFalse(CalendarUtils::checkDate(1395, 12, 31, true)); + $this->assertFalse(CalendarUtils::checkDate(2015, 12, 31, true)); + } + + public function testToJalali() + { + $this->assertTrue(CalendarUtils::toJalali(2016, 5, 7) === [1395, 2, 18]); + $this->assertFalse(CalendarUtils::toJalali(2015, 5, 7) === [1394, 2, 18]); + } + + public function testToGregorian() + { + $this->assertTrue(CalendarUtils::toGregorian(1395, 2, 18) === [2016, 5, 7]); + $this->assertFalse(CalendarUtils::toGregorian(1394, 2, 18) === [2015, 5, 7]); + } + + public function testIsLeapJalaliYear() + { + $this->assertTrue(CalendarUtils::isLeapJalaliYear(1395)); + $this->assertFalse(CalendarUtils::isLeapJalaliYear(1394)); + } + + public function testStrftime() + { + $table = [ + [ + '2016-05-08', + 'Y-m-d', + '1395-02-19' + ], + [ + '2022-03-24', + 'y-m-d', + '01-01-04' + ], + [ + '2023-03-24', + 'y-m-D', + '02-01-ج' + ], + ]; + + foreach ($table as $row) { + list($dateTimeString, $format, $expected) = $row; + $timestamp = strtotime($dateTimeString); + $this->assertEquals($expected, CalendarUtils::strftime($format, $timestamp)); + } + } + + public function testFormatMonthName() + { + $months = range(1, 12); + + // Should returns iranian months name as default + foreach ($months as $month) { + $date = sprintf('1401/%d/10', $month); + $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); + $expected = CalendarUtils::IRANIAN_MONTHS_NAME[$month - 1]; + $this->assertEquals($expected, $actual); + } + + // Should returns afghan months name when set + CalendarUtils::useAfghanMonthsName(); + foreach ($months as $month) { + $date = sprintf('1401/%d/10', $month); + $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); + $expected = CalendarUtils::AFGHAN_MONTHS_NAME[$month - 1]; + $this->assertEquals($expected, $actual); + } + + // Should returns afghan months name when set + CalendarUtils::useIranianMonthsName(); + foreach ($months as $month) { + $date = sprintf('1401/%d/10', $month); + $actual = Jalalian::fromFormat('Y/n/d', $date)->format('F'); + $expected = CalendarUtils::IRANIAN_MONTHS_NAME[$month - 1]; + $this->assertEquals($expected, $actual); + } + } + + public function test_parseFromPersian() + { + $jalaliDate = '1393/03/27'; + $date = CalendarUtils::parseFromFormat('Y/m/d', $jalaliDate); + + $this->assertEquals(1393, $date['year']); + $this->assertEquals(03, $date['month']); + $this->assertEquals(27, $date['day']); + + $date = CalendarUtils::parseFromFormat('Y-m-d H:i:s', '1395-03-15 21:00:00'); + $this->assertEquals(21, $date['hour']); + $this->assertEquals(0, $date['minute']); + $this->assertEquals(0, $date['second']); + } + + public function testCreateDateTimeFormFormat() + { + $jdate = '1394/11/25 15:00:00'; + $gDateTime = CalendarUtils::createDatetimeFromFormat('Y/m/d H:i:s', $jdate); + + $this->assertTrue($gDateTime instanceof \DateTime); + + $this->assertTrue('2016-02-14 15:00:00' === $gDateTime->format('Y-m-d H:i:s')); + } + + public function testCreateCarbonFormFormat() + { + $jdate = '1394/11/25 15:00:00'; + $carbon = CalendarUtils::createCarbonFromFormat('Y/m/d H:i:s', $jdate); + + $this->assertTrue($carbon instanceof \Carbon\Carbon); + $this->assertTrue($carbon->day === 14); + $this->assertTrue('2016-02-14 15:00:00' === $carbon->format('Y-m-d H:i:s')); + + $jalaiDateFormatted = Jalalian::fromDateTime($carbon->toDateString())->format('Y-m-d H:i:s'); + $jalaiDateTimeFormatted = Jalalian::fromDateTime($carbon->toDateTimeString())->format('Y-m-d H:i:s'); + $this->assertFalse($jalaiDateFormatted === '1394-11-25 15:00:00'); + $this->assertTrue($jalaiDateTimeFormatted === '1394-11-25 15:00:00'); + + // Test support years after 1416 + $carbon = CalendarUtils::createCarbonFromFormat('Y/m/d', '1417/10/11'); + $this->assertEquals('2039-01-01', $carbon->format('Y-m-d')); + } + + public function testTimezone() + { + date_default_timezone_set('Asia/Tehran'); + $tehranDate = Jalalian::now(); + $tehranHour = $tehranDate->format('H'); + $tehranMin = $tehranDate->format('i'); + + date_default_timezone_set('UTC'); + $utcDate = Jalalian::now(); + $utcHour = $utcDate->format('H'); + $utcMin = $utcDate->format('i'); + + $tzOffset = $this->getTimeZoneOffset('Asia/Tehran', 'UTC'); + + $this->assertTrue((((($utcHour * 60) + $utcMin) * 60) - ((($tehranHour * 60) + $tehranMin) * 60)) === $tzOffset); + } + + + private function getTimeZoneOffset($remote_tz, $origin_tz = null) + { + if ($origin_tz === null) { + if (!is_string($origin_tz = date_default_timezone_get())) { + return false; // A UTC timestamp was returned -- bail out! + } + } + $origin_dtz = new DateTimeZone($origin_tz); + $remote_dtz = new DateTimeZone($remote_tz); + $origin_dt = new DateTime("now", $origin_dtz); + $remote_dt = new DateTime("now", $remote_dtz); + $offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt); + + return $offset; + } +} diff --git a/tests/HelperTest.php b/tests/HelperTest.php new file mode 100644 index 0000000..7570373 --- /dev/null +++ b/tests/HelperTest.php @@ -0,0 +1,17 @@ +assertTrue(function_exists('jdate')); + + $jdate = jdate('now'); + $this->assertTrue($jdate instanceof Jalalian); + } +} diff --git a/tests/JalalianTest.php b/tests/JalalianTest.php new file mode 100644 index 0000000..3c22047 --- /dev/null +++ b/tests/JalalianTest.php @@ -0,0 +1,441 @@ +assertTrue($jDate instanceof Jalalian); + $this->assertEquals($jDate->getDay(), 25); + $this->assertEquals($jDate->getYear(), 1397); + $this->assertEquals($jDate->getMonth(), 1); + + $this->assertEquals($jDate->format('Y-m-d H:i:s'), '1397-01-25 00:00:00'); + } + + public function testGetDayOfYear() + { + $jDate = new Jalalian(1397, 1, 25); + $this->assertEquals($jDate->getDayOfYear(), 25); + + $jDate = new Jalalian(1397, 5, 20); + $this->assertEquals($jDate->getDayOfYear(), 144); + + $jDate = new Jalalian(1397, 7, 3); + $this->assertEquals($jDate->getDayOfYear(), 189); + + $jDate = new Jalalian(1397, 12, 29); + $this->assertEquals($jDate->getDayOfYear(), 365); + + $jDate = new Jalalian(1395, 12, 30); + $this->assertTrue($jDate->isLeapYear()); + $this->assertEquals($jDate->getDayOfYear(), 366); + } + + public function testModifiers() + { + $jDate = new Jalalian(1397, 1, 18); + + $this->assertEquals($jDate->addYears()->getYear(), 1398); + $this->assertEquals($jDate->addMonths(11)->getMonth(), 12); + $this->assertEquals($jDate->addMonths(11)->addDays(20)->getMonth(), 1); + $this->assertEquals($jDate->subDays(8)->getNextMonth()->getMonth(), 2); + + $jDate = Jalalian::fromCarbon(Carbon::createFromDate(2019, 1, 1)); + $this->assertEquals($jDate->addMonths(4)->getYear(), 1398); + + $jDate = new Jalalian(1397, 1, 31); + $this->assertEquals($jDate->addMonths(1)->getDay(), 31); + $this->assertEquals($jDate->addYears(3)->getDay(), 31); + $this->assertEquals($jDate->addMonths(36)->toString(), $jDate->addYears(3)->toString()); + $this->assertEquals($jDate->subYears(10)->toString(), (new Jalalian(1387, 1, 31))->toString()); + $this->assertTrue($jDate->subYears(2)->subMonths(34)->equalsTo(new Jalalian(1392, 03, 31))); + + $jDate = (new Jalalian(1397, 6, 11))->subMonths(1); + $this->assertEquals($jDate->getMonth(), 5); + + $this->assertEquals((new Jalalian(1397, 7, 1))->subMonths(1)->getMonth(), 6); + + $jDate = Jalalian::now(); + $month = $jDate->getMonth(); + if ($month > 1) { + $this->assertEquals($month - 1, $jDate->subMonths()->getMonth()); + } + + + $jDate = Jalalian::fromFormat('Y-m-d', '1397-12-12'); + $this->assertEquals('1398-01-12', $jDate->addMonths(1)->format('Y-m-d')); + + $jDate = Jalalian::fromFormat('Y-m-d', '1397-11-30'); + $this->assertEquals('1397-12-29', $jDate->addMonths(1)->format('Y-m-d')); + + $jDate = Jalalian::fromFormat('Y-m-d', '1397-06-30'); + $this->assertEquals('1397-07-30', $jDate->addMonths(1)->format('Y-m-d')); + + $jDate = Jalalian::fromFormat('Y-m-d', '1397-06-31'); + $this->assertEquals('1397-07-30', $jDate->addMonths(1)->format('Y-m-d')); + + $jDate = Jalalian::fromFormat('Y-m-d', '1395-12-30'); + $this->assertEquals('1399-12-30', $jDate->addMonths(48)->format('Y-m-d')); + + $jDate = Jalalian::fromFormat('Y-m-d', '1395-12-30'); + $this->assertEquals('1398-12-29', $jDate->addMonths(36)->format('Y-m-d')); + } + + public function testForge() + { + $jDate = Jalalian::forge(strtotime('now')); + $this->assertTrue($jDate instanceof Jalalian); + $this->assertTrue($jDate->getTimestamp() === strtotime('now')); + + $jDate = Jalalian::forge(1333857600); + $this->assertEquals($jDate->toString(), '1391-01-20 04:00:00'); + + $jDate = Jalalian::forge('last monday'); + $this->assertTrue($jDate instanceof Jalalian); + + $jDate = Jalalian::forge(1552608000); + $this->assertEquals('1397-12-24', $jDate->format('Y-m-d')); + } + + public function testMaximumYearFormatting() + { + $jDate = Jalalian::fromFormat('Y-m-d', '1800-12-01'); + $this->assertEquals(1800, $jDate->getYear()); + $this->assertEquals($jDate->format('Y-m-d'), '1800-12-01'); + + // issue-110 + $jDate = Jalalian::fromFormat('Y-m-d', '1416-12-01'); + $this->assertEquals(1416, $jDate->format('Y')); + } + + public function testGetWeekOfMonth() + { + $jDate = new Jalalian(1400, 1, 8); + $this->assertEquals($jDate->getWeekOfMonth(), 2); + + $jDate = new Jalalian(1400, 5, 13); + $this->assertEquals($jDate->getWeekOfMonth(), 3); + + $jDate = new Jalalian(1390, 11, 11); + $this->assertEquals($jDate->getWeekOfMonth(), 2); + + $jDate = new Jalalian(1395, 7, 20); + $this->assertEquals($jDate->getWeekOfMonth(), 4); + + $jDate = new Jalalian(1401, 1, 5); + $this->assertEquals($jDate->getWeekOfMonth(), 1); + + $jDate = new Jalalian(1390, 8, 7); + $this->assertEquals($jDate->getWeekOfMonth(), 2); + + + $jDate = new Jalalian(1390, 8, 27); + $this->assertEquals($jDate->getWeekOfMonth(), 4); + + $jDate = new Jalalian(1390, 7, 1); + $this->assertEquals($jDate->getWeekOfMonth(), 1); + + $jDate = new Jalalian(1390, 7, 2); + $this->assertEquals($jDate->getWeekOfMonth(), 2); + + $jDate = new Jalalian(1390, 7, 30); + $this->assertEquals($jDate->getWeekOfMonth(), 6); + + $jDate = new Jalalian(1390, 6, 15); + $this->assertEquals($jDate->getWeekOfMonth(), 3); + + $jDate = new Jalalian(1390, 6, 25); + $this->assertEquals($jDate->getWeekOfMonth(), 4); + + $jDate = new Jalalian(1390, 6, 26); + $this->assertEquals($jDate->getWeekOfMonth(), 5); + + $jDate = new Jalalian(1401, 3, 7); + $this->assertEquals($jDate->getWeekOfMonth(), 2); + } + + public function testGetFirstDayOfWeek() + { + $jDate = new Jalalian(1401, 1, 23); + $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1401-01-20'); + + $jDate = new Jalalian(1395, 4, 24); + $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1395-04-19'); + + $jDate = new Jalalian(1398, 11, 7); + $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1398-11-05'); + + $jDate = new Jalalian(1400, 8, 19); + $this->assertEquals($jDate->getFirstDayOfWeek()->format('Y-m-d'), '1400-08-15'); + } + + public function testGetFirstDayOfMonth() + { + $jDate = new Jalalian(1401, 1, 23); + $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1401-01-01'); + + $jDate = new Jalalian(1390, 5, 14); + $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1390-05-01'); + + $jDate = new Jalalian(1399, 2, 29); + $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1399-02-01'); + + $jDate = new Jalalian(1398, 10, 10); + $this->assertEquals($jDate->getFirstDayOfMonth()->format('Y-m-d'), '1398-10-01'); + } + + public function testGetFirstDayOfYear() + { + $jDate = new Jalalian(1401, 6, 11); + $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1401-01-01'); + + $jDate = new Jalalian(1399, 11, 28); + $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1399-01-01'); + + $jDate = new Jalalian(1394, 1, 12); + $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1394-01-01'); + + $jDate = new Jalalian(1393, 9, 5); + $this->assertEquals($jDate->getFirstDayOfYear()->format('Y-m-d'), '1393-01-01'); + } + + public function testAddDay() + { + $jDate = new Jalalian(1401, 6, 31); + $this->assertEquals($jDate->addDay()->format('Y-m-d'), '1401-07-01'); + } + + public function testSubDay() + { + $jDate = new Jalalian(1401, 6, 1); + $this->assertEquals($jDate->subDay()->format('Y-m-d'), '1401-05-31'); + } + + public function testGetLastWeek() + { + $jDate = new Jalalian(1401, 6, 8); + $this->assertEquals($jDate->getLastWeek()->format('Y-m-d'), '1401-06-01'); + } + + public function testGetLastMonth() + { + $jDate = new Jalalian(1401, 6, 8); + $this->assertEquals($jDate->getLastMonth()->format('Y-m-d'), '1401-05-08'); + } + + public function testGetFirstDayOfQuarter() + { + $jDate = new Jalalian(1402, 1, 25); + $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 2, 25); + $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 3, 25); + $this->assertEquals('1402-01-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 4, 25); + $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 5, 25); + $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 6, 25); + $this->assertEquals('1402-04-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 7, 25); + $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 8, 25); + $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 9, 25); + $this->assertEquals('1402-07-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 11, 19); + $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 12, 25); + $this->assertEquals('1402-10-01', $jDate->getFirstDayOfQuarter()->format('Y-m-d')); + + } + + public function testGetEndDayOfWeek() + { + /* + * -------------------------------- + * Day 1402 (March 2024) + * -------------------------------- + * Sat Sun Mon Tue Wed Thu Fri + * -------------------------------- + * 1 + * 2 3 4 5 6 7 8 + * 9 10 11 12 13 14 15 + * 16 17 18 19 20 21 22 + * 23 24 25 26 27 28 29 + * 30 + * ------------------------------- + */ + + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 10, 29); + $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 10, 23); + $this->assertEquals('1402-10-29', $jDate->getEndDayOfWeek()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 12, 29); + $this->assertEquals('1403-01-03', $jDate->getEndDayOfWeek()->format('Y-m-d')); + + } + + public function testGetEndDayOfMonth() + { + /* + * -------------------------------- + * Day 1402 (March 2024) + * -------------------------------- + * Sat Sun Mon Tue Wed Thu Fri + * -------------------------------- + * 1 + * 2 3 4 5 6 7 8 + * 9 10 11 12 13 14 15 + * 16 17 18 19 20 21 22 + * 23 24 25 26 27 28 29 + * 30 + * ------------------------------- + */ + + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals('1402-10-30', $jDate->getEndDayOfMonth()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 04, 12); + $this->assertEquals('1402-04-31', $jDate->getEndDayOfMonth()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 12, 25); + $this->assertEquals('1402-12-29', $jDate->getEndDayOfMonth()->format('Y-m-d')); + } + + public function testGetEndDayOfYear() + { + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals('1402-12-29', $jDate->getEndDayOfYear()->format('Y-m-d')); + + // LeapYear + $jDate = new Jalalian(1403, 10, 25); + $this->assertEquals('1403-12-30', $jDate->getEndDayOfYear()->format('Y-m-d')); + } + + public function testGetEndDayOfQuarter() + { + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals('1402-12-29', $jDate->getEndDayOfQuarter()->format('Y-m-d')); + + $jDate = new Jalalian(1402, 2, 25); + $this->assertEquals('1402-03-31', $jDate->getEndDayOfQuarter()->format('Y-m-d')); + + + $jDate = new Jalalian(1402, 6, 01); + $this->assertEquals('1402-06-31', $jDate->getEndDayOfQuarter()->format('Y-m-d')); + + + $jDate = new Jalalian(1402, 9, 01); + $this->assertEquals('1402-09-30', $jDate->getEndDayOfQuarter()->format('Y-m-d')); + } + + public function testGetQuarter() + { + $jDate = new Jalalian(1402, 10, 25); + $this->assertEquals(4, $jDate->getQuarter()); + + $jDate = new Jalalian(1402, 1, 01); + $this->assertEquals(1, $jDate->getQuarter()); + + + $jDate = new Jalalian(1402, 05, 24); + $this->assertEquals(2, $jDate->getQuarter()); + + $jDate = new Jalalian(1402, 7, 23); + $this->assertEquals(3, $jDate->getQuarter()); + } + + public function testdiff() + { + $jDate = new Jalalian(1401, 6, 26); + + //same day + $this->assertEquals($jDate->diff(new Jalalian(1401, 6, 26)), [0, 0, 0]); + + //year before + $this->assertEquals($jDate->diff(new Jalalian(1401, 6, 25)), [0, 0, 1]); + + //day after + $this->assertEquals($jDate->diff(new Jalalian(1401, 6, 27)), [0, 0, 1]); + + //same montt, same year, before + $this->assertEquals($jDate->diff(new Jalalian(1401, 6, 24)), [0, 0, 2]); + + //same month, same year, after + $this->assertEquals($jDate->diff(new Jalalian(1401, 6, 28)), [0, 0, 2]); + + //same year, before, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1401, 4, 12)), [0, 2, 14]); + + //same year, before, greater day + $this->assertEquals($jDate->diff(new Jalalian(1401, 4, 27)), [0, 1, 30]); + + //same year, after, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1401, 9, 10)), [0, 2, 15]); + + //same year, after, greater day + $this->assertEquals($jDate->diff(new Jalalian(1401, 10, 28)), [0, 4, 2]); + + //previous year, smaller month, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1389, 4, 12)), [12, 2, 14]); + + //previous year, smaller month, greater day + $this->assertEquals($jDate->diff(new Jalalian(1395, 4, 29)), [6, 1, 28]); + + //previous year, greater month, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1395, 9, 10)), [5, 9, 16]); + + //previous year, greater month, greater day + $this->assertEquals($jDate->diff(new Jalalian(1395, 9, 30)), [5, 8, 26]); + + //greater year, smaller month, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1402, 4, 12)), [0, 9, 17]); + + //greater year, smaller month, greater day + $this->assertEquals($jDate->diff(new Jalalian(1403, 4, 29)), [1, 10, 3]); + + //greater year, greater month, smaller day + $this->assertEquals($jDate->diff(new Jalalian(1405, 9, 10)), [4, 2, 15]); + + //greater year, greater month, greater day + $this->assertEquals($jDate->diff(new Jalalian(1405, 9, 30)), [4, 3, 4]); + } + + public function testNext() + { + $jDate = new Jalalian(1403, 05, 28); + $this->assertEquals('1403-05-30', $jDate->next('سه‌شنبه')->format('Y-m-d')); + } + + public function testPrevious() + { + $jDate = new Jalalian(1403, 05, 28); + $this->assertEquals('1403-05-22', $jDate->previous('دوشنبه')->format('Y-m-d')); + } +}