remls / hijri-date
Laravel helper package for Hijri dates.
Requires
- php: >=8.1
- ext-intl: *
- guzzlehttp/guzzle: ^7.5
- laravel/framework: ^10.0 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0 || ^11.0
README
HijriDate
Laravel helper package for Hijri dates. Supports displaying dates in Arabic, Bengali, Dhivehi, English, Indonesian, Malay and Urdu out of the box, with support for further customizations or adding a language of your choice.
A demo of some of the package's capabilities is available at https://hijri.remls.io.
Warning
This package was built primarily for use in Maldives: the default language is Dhivehi, and the default calendar is a custom mapping of dates specifically for Maldives. (Why?)
However, this can all be customised for your specific needs; read further for how to do so.
- Installation
- Creating dates
- Available methods
- Casting
- Validation
- Localization
- Migrating from v2 to v3
- Migrating from v1 to v2
Installation
composer require remls/hijri-date
To publish configuration files:
php artisan vendor:publish --provider="Remls\HijriDate\HijriDateServiceProvider" --tag="config"
Creating dates
All of the following methods return an instance of Remls\HijriDate\HijriDate.
use Carbon\Carbon; new HijriDate(); // 1st Muharram 1000 new HijriDate(1443, 9, 1); // 1st Ramadan 1443 HijriDate::parse('1443-09-01'); // 1st Ramadan 1443 // From Gregorian HijriDate::createFromGregorian(); // Today's date today_hijri(); // Today's date HijriDate::createFromGregorian('1991-12-01'); // 20th Jumada al-Ula 1412 $input = Carbon::parse('2002-03-04'); HijriDate::createFromGregorian($input); // 12th Dhul-Hijja 1422
Customizing how dates are converted between Hijri and Gregorian
By default, the package uses an external map between Hijri and Gregorian dates in Maldives to convert between the two. This map is cached and reused for subsequent conversions.
You may customize for how long the map is cached by changing config/hijri.php > conversion.cache_period.
You may manually re-fetch data from the external source by running php artisan hijri:fetch.
The package also comes with an alternative class for converting dates using calculations instead of a map. You may enable it by changing config/hijri.php > conversion.converter to \Remls\HijriDate\Converters\MaldivesEstimateG2HConverter::class.
You may customize how dates are converted by:
- providing your own map in
config/hijri.php>conversion.data_url - providing your own custom converter class in
config/hijri.php>conversion.converter- The class must implement
\Remls\HijriDate\Converters\Contracts\GregorianToHijriConverter.
- The class must implement
- binding your own implementation of
GregorianToHijriConverterin the service container- This takes precedence over
config/hijri.php>conversion.converter - Example:
- This takes precedence over
// App/Providers/AppServiceProvider.php use Remls\HijriDate\Converters\Contracts\GregorianToHijriConverter; public function register(): void { $this->app->singleton(GregorianToHijriConverter::class, fn () => new YourCustomConverter()); }
Available methods
Conversions
// Gregorian to Hijri HijriDate::createFromGregorian('1991-12-01'); // returns HijriDate // Hijri to Gregorian $date = new HijriDate(1443, 9, 1); $date->getGregorianDate(); // returns Carbon
By default, conversions are date-only: any time component on the input is ignored, and input Carbon instances are never modified. getGregorianDate() returns a copy of the original input (for dates created with createFromGregorian()), or a date at midnight in Maldives time (UTC+5), derived on first call.
You may customise how the conversion works, as detailed here.
Calculations
$date = new HijriDate(1443, 9, 1); // 1st Ramadan 1443 $date->addDays(1); // 2nd Ramadan 1443 $date->subDays(3); // 28th Sha'ban 1443 $date2 = new HijriDate(1443, 8, 20); // 20th Sha'ban 1443 $date->diffInDays($date2); // 8
Comparisons
You may compare two HijriDate objects $a and $b using the following methods:
| Method | Description |
|---|---|
$a->compareWith($b) |
Returns -1 if $a < $b. Returns 0 if $a == $b. Returns 1 if $a > $b. |
$a->equalTo($b) |
Returns true if $a == $b. |
$a->greaterThan($b) |
Returns true if $a > $b (a is after b). |
$a->lessThan($b) |
Returns true if $a < $b (a is before b). |
$a->greaterThanOrEqualTo($b) |
Returns true if $a >= $b (a is after or equal to b). |
$a->lessThanOrEqualTo($b) |
Returns true if $a <= $b (a is before or equal to b). |
Formatting
Each HijriDate object will have a set locale when it is created. This locale will be used for formatting.
The locale is 'dv' by default, but you may customize it by:
- passing locale in constructor (eg:
new HijriDate(1443, 9, 1, 'en')) - changing locale after creation (eg:
$date->setLocale('en')) - changing
default_localein configuration, so all HijriDate objects are created using that default locale
The following options are supported with $date->format():
| Option | Description | Example |
|---|---|---|
| d | Day of month (with leading zero) | 01 ... 30 |
| D | Weekday (short) | Sun ... Sat |
| j | Day of month (without leading zero) | 1 ... 30 |
| l (lowercase L) |
Weekday | Sunday ... Saturday |
| F | Month | Muharram ... Dhul-Hijja |
| m | Month (number, with leading zero) | 01 ... 12 |
| M | Month (short) | Mhr ... DhH |
| n | Month (number, without leading zero) | 1 ... 12 |
| Y | Year | 1000 ... 1999 |
| y | Year (final two digits) | 00 ... 99 |
$date = new HijriDate(1443, 9, 1); // 1st Ramadan 1443 $date->format("F"); // "ރަމަޟާން" (using default locale 'dv') $date->setLocale('ar'); $date->format("F Y"); // "رمضان 1443" // Use numerals from locale $date->format("F Y", true); // "رمضان ١٤٤٣"
Casting
The field to be cast must be a string field on database.
// App/Models/YourModel.php class YourModel { ... protected $casts = [ ... 'your_hijri_date_field' => HijriDate::class, ]; }
This will automatically store data as Y-m-d string in database, and cast to Remls\HijriDate\HijriDate when accessing.
Validation
Any string that passes the following conditions is considered a valid Hijri date:
- in the format
Y-m-d - year between 1000 and 1999 (This can be changed in config.)
- month between 1 and 12
- day between 1 and 30
use Remls\HijriDate\Rules\ValidHijriDate; ... request()->validate([ 'your_hijri_date_field' => ['required', new ValidHijriDate], ]);
Note that validation error messages will use app's locale (unlike formatting).
Localization
Publish translation files by using:
php artisan vendor:publish --provider="Remls\HijriDate\HijriDateServiceProvider" --tag="lang"
You may then customize strings as needed.
Adding a language
To add support for another language:
- Publish the configuration file. The file will be copied to
config/hijri.php. - Publish the translation files. The files will be copied to
lang/vendor/hijri. - Copy one of the existing translation folders, and rename it with the language code of your choice. Eg:
lang/vendor/hijri/es - Change strings to their respective translations.
- Add the language code to
supported_localesinconfig/hijri.php. - (Optional) Change
default_localeinconfig/hijri.phpto the new language code.
Migrating from v2 to v3
This version mainly addresses stale code and bug fixes, with little to no change in functionality for the vast majority of cases.
Minimum supported Laravel version has been bumped from v8 to v10.
⚠️ denotes new behaviour that could potentially be a breaking change.
Improvements
ValidHijriDatenow implementsIlluminate\Contracts\Validation\ValidationRuleinstead of the deprecatedRule.- Normal usage (
new ValidHijriDatein a rules array) is unaffected. - ⚠️ If you called
passes()ormessage()on the rule directly, or extended the class, update to the newvalidate()method.
- Normal usage (
- The map is now fetched with Laravel's HTTP client, with timeouts and a retry.
- ⚠️ Network failures throw
Illuminate\Http\Client\ConnectionExceptionorRequestExceptioninstead ofRuntimeException. Invalid map data still throwsRuntimeException. allow_url_fopenis no longer required.- Fetched data is validated before use, and the last successfully fetched map is kept indefinitely and served as a fallback if a refetch fails.
- ⚠️ Network failures throw
- The converter is resolved from the service container as a singleton, instead of being constructed for every conversion.
- You can now register your conversion class in
AppServiceProviderinstead of changingconversion.converterin config. - ⚠️ Changing
conversion.converterin config at runtime after the first conversion now has no effect.
- You can now register your conversion class in
Bug fixes
conversion.cache_periodis now set to 6 hours by default. If you have publishedconfig/hijri.php, updateconversion.cache_periodto60 * 60 * 6(optional, leaving as the previous default just means the cache expires more quickly).- ⚠️
Carboninstances passed into the package are never modified anymore.- Previously, an instance passed to
createFromGregorian()was silently mutated by the default converter class. It now stays exactly as you created it, andgetGregorianDate()returns a copy of it as provided.
- Previously, an instance passed to
- Negative amounts passed to
addDays()/subDays()now stay on the calculation path selected by$useGregorian. Previously they always used the Gregorian path. - ⚠️ Out-of-range results from
addDays()/subDays()with$useGregorian = falsenow throwInvalidArgumentException, consistent with the rest of the package, instead ofOutOfRangeException. isParsable()now checks the year against the configured range, so it no longer returnstruefor strings thatparse()would then reject.
Migrating from v1 to v2
The package no longer uses estimates when converting from Hijri to Gregorian by default.
- New keys have been added to
config/hijri.php. You may need to update your configuration file. - The function
getEstimateFromGregorianhas been REMOVED in favour ofcreateFromGregorian.- To maintain the same behaviour as before:
- Change
config/hijri.php>conversion.converterto\Remls\HijriDate\Converters\MaldivesEstimateG2HConverter::class. - Change all calls from
getEstimateFromGregoriantocreateFromGregorian.
- Change
- To maintain the same behaviour as before:
- The function
isEstimatehas been REMOVED. There is no more need to check if the date was made from an estimate, as you can now always just get the corresponding Gregorian date with a call togetGregorianDate, regardless of how it was created. - The function
getEstimatedFromhas been REMOVED in favour ofgetGregorianDate. - The function
resetEstimationhas been REMOVED in favour ofresetGregorianDate. - The functions
addDaysandsubDayswill now use the underlying Gregorian date by default for calculations.- To maintain the same behaviour as before, pass
falseto$useGregorianparameter of these functions.
- To maintain the same behaviour as before, pass