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'));
+ }
+}