Laravel Hijri
Laravel Hijri adds Hijri (Islamic) calendar support to your Laravel application. It registers the PHP Hijri Carbon mixin, so every Carbon date (including now() and Eloquent date attributes) can convert to and from Hijri. It also ships an @hijri Blade directive, a Hijri date validation rule, English and Arabic translations, and a global day adjustment you set once in config.
Carbon Integration
Call , and on any Carbon date, with no setup.
Blade Directive
Print a Hijri date in any view with , in the app locale and your default format.
Validation Rule
Validate Hijri input such as with , with English and Arabic messages.
Day Adjustment
Shift every conversion by a number of days to match local moon sighting, globally or per call.
Arabic & English Names
Month names switch between Arabic () and transliterated English () by locale.
Publishable Config
One small config file, with the day adjustment readable from in .
Store dates in Gregorian in your database and convert to Hijri only when you display them. Your queries, sorting and date math keep working as usual.
Installation
Install the package with Composer. Laravel discovers the service provider automatically.
Requirements
- PHP 8.0
- Laravel 7.30 or newer within 7.x
pharaonic/php-hijri^8.0.1 (installed automatically)
Composer Installation
composer require pharaonic/laravel-hijriPublish Configuration
Publishing the config is optional. Without it, the package uses its defaults (adjustment -1, format D MMMM YYYY).
php artisan vendor:publish --tag=hijri-configThis creates config/pharaonic/hijri.php.
Publish Translations
Publish the validation messages only if you want to change them or add a language.
php artisan vendor:publish --tag=hijri-translationsThe files are copied to resources/lang/vendor/hijri.
--tag=laravel-hijri (or --tag=pharaonic) publishes the config and the translations together. pharaonic-config and pharaonic-translations are also available.
You're all set! Try now()->toHijri()->format('Y-m-d') in php artisan tinker to see today's Hijri date.
Configuration
The config file lives at config/pharaonic/hijri.php after publishing and is read under the pharaonic.hijri key.
1return [2 'adjustment' => (int) env('HIJRI_ADJUSTMENT', -1),3 4 'format' => 'D MMMM YYYY',5];Options
| Key | Default | Description |
|---|---|---|
adjustment | -1 (from HIJRI_ADJUSTMENT) | Days added to every Hijri conversion when no per-call adjustment is given. |
format | D MMMM YYYY | The Carbon isoFormat tokens the @hijri directive uses when you don't pass a format. |
Day Adjustment
The underlying calculator uses the tabular Hijri calendar. Actual month starts depend on local moon sighting and can differ by a day or two, so the adjustment lets you align the output with your region. The historic Pharaonic default is -1.
Set it from your environment:
HIJRI_ADJUSTMENT=01Carbon::parse('2024-03-11')->toHijri()->format('Y-m-d'); // "1445-09-01" (adjustment -1)2Carbon::parse('2024-03-11')->toHijri(0)->format('Y-m-d'); // "1445-09-02"The service provider reads pharaonic.hijri.adjustment once while booting and stores it as the global default. Changing the config at runtime with config([...]) doesn't change it. Use Carbon::setHijriAdjustment() or pass an adjustment per call instead (see Basic Usage).
Default Format
format is only used by the @hijri directive and HijriFormatter::format(). It accepts any Carbon isoFormat pattern:
| Format | Output (en) |
|---|---|
D MMMM YYYY | 1 Ramadan 1445 |
dddd D MMMM YYYY | Monday 1 Ramadan 1445 |
YYYY/MM/DD | 1445/09/01 |
ddd, D MMM YYYY | Mon, 1 Ramadan 1445 |
Basic Usage
The service provider registers the Pharaonic\Hijri\HijriCarbon mixin on Carbon\Carbon when your app boots. Every Carbon instance, including Illuminate\Support\Carbon from now() and Eloquent date attributes, gets the Hijri methods.
Gregorian to Hijri
toHijri() returns a Pharaonic\Hijri\Hijri object. It extends Carbon, so its year, month and day are the Hijri values and all the usual formatting methods work.
1use Carbon\Carbon;2 3$hijri = Carbon::parse('2024-03-11 10:30:00')->toHijri();4 5$hijri->year; // 14456$hijri->month; // 97$hijri->day; // 18$hijri->format('Y-m-d H:i'); // "1445-09-01 10:30"It works on any date your app already has:
1now()->toHijri()->format('Y-m-d');2$post->created_at->toHijri()->format('Y-m-d');Hijri to Gregorian
Build a Gregorian Carbon date from Hijri year, month and day:
1Carbon::fromHijri(1445, 9, 1)->toDateString(); // "2024-03-11"2Carbon::fromHijri(1446, 10, 1)->toDateString(); // "2025-03-31"Or parse a Hijri string in YYYY-MM-DD format, with an optional HH:MM[:SS] time part:
1Carbon::parseHijri('1445-09-01')->toDateString(); // "2024-03-11"2Carbon::parseHijri('1445-09-01 14:30')->toDateTimeString(); // "2024-03-11 14:30:00"Both accept a timezone as the next argument, and both throw Pharaonic\Hijri\Exception\InvalidHijriDateException for a date that doesn't exist (such as 1445-02-30) or a string in another format.
Formatting
Format a Hijri object with format() or isoFormat(). Set the locale to get Arabic or English month and day names:
1$hijri = Carbon::parse('2024-03-11')->toHijri();2 3$hijri->locale('en')->isoFormat('dddd D MMMM YYYY'); // "Monday 1 Ramadan 1445"4$hijri->locale('ar')->isoFormat('dddd D MMMM YYYY'); // "الاثنين 1 رَمضان 1445"In Blade views, the @hijri directive does this in one step.
Per-call Adjustment
Pass an adjustment in days to override the global one for a single conversion. It doesn't change the global value.
1Carbon::parse('2024-03-11')->toHijri(0)->format('Y-m-d'); // "1445-09-02"2Carbon::parse('2024-03-11')->toHijri(1)->format('Y-m-d'); // "1445-09-03"3 4Carbon::fromHijri(1445, 9, 1, null, 0);5Carbon::parseHijri('1445-09-01', null, 0);Changing the Global Adjustment
Read or change the default adjustment at runtime, for example per tenant or per user region:
1Carbon::getHijriAdjustment(); // -12 3Carbon::setHijriAdjustment(0);The adjustment is a static value shared by the whole PHP process. On Laravel Octane or a long-running queue worker, a value you set during one request or job stays for the next. Prefer a per-call adjustment when it depends on the user.
Blade Directive
@hijri prints a Gregorian date as an escaped Hijri string.
1@hijri($date, $format = null, $locale = null, $adjustment = null)| Argument | Type | Default |
|---|---|---|
$date | DateTimeInterface, string or null | Required |
$format | string (Carbon isoFormat tokens) | config('pharaonic.hijri.format'), D MMMM YYYY |
$locale | string | app()->getLocale() |
$adjustment | int | The global adjustment |
Default Format
With only a date, the directive uses the configured format and the current app locale:
1<time>@hijri($post->published_at)</time>2{{-- 1 Ramadan 1445 --}}Custom Format and Locale
1@hijri('2024-03-11', 'dddd D MMMM YYYY', 'ar')2{{-- الاثنين 1 رَمضان 1445 --}}3 4@hijri($order->created_at, 'YYYY/MM/DD')5{{-- 1445/09/01 --}}Custom Adjustment
1@hijri($event->starts_at, null, null, 0)Empty Values
A null or empty string date renders nothing, so you don't need an @if around nullable columns:
1@hijri($user->verified_at)Outside Blade
The directive calls Pharaonic\Laravel\Hijri\Support\HijriFormatter::format(). Call it directly in controllers, notifications or API resources to get the same output:
1use Pharaonic\Laravel\Hijri\Support\HijriFormatter;2 3HijriFormatter::format($post->published_at); // "1 Ramadan 1445"4HijriFormatter::format('2024-03-11', 'dddd D MMMM YYYY', 'ar'); // "الاثنين 1 رَمضان 1445"Validation
Pharaonic\Laravel\Hijri\Rules\HijriDateRule checks that the input is a real Hijri date in YYYY-MM-DD format, with an optional HH:MM[:SS] time part.
1use Pharaonic\Laravel\Hijri\Rules\HijriDateRule;2 3$request->validate([4 'date' => ['required', new HijriDateRule()],5]);What Passes
| Value | Result |
|---|---|
1445-09-01 | Passes |
1445-9-1 | Passes |
1445-09-01 14:30 | Passes |
1445-02-30 | Fails (Safar has 29 days in the tabular calendar) |
01/09/1445 | Fails (wrong format) |
null, 123, blank string | Fails (not a non-empty string) |
The rule parses the value with Carbon::parseHijri(), so anything that passes can be converted the same way.
Error Message
The message comes from the hijri::validation.hijri_date translation key:
| Locale | Message |
|---|---|
en | The :attribute must be a valid Hijri date. |
ar | يجب أن يكون حقل :attribute تاريخًا هجريًا صالحًا. |
To change the wording, publish the translations (--tag=hijri-translations) and edit resources/lang/vendor/hijri/{locale}/validation.php. See Localization.
The rule fails on an empty value. For optional fields, add nullable so Laravel skips the rule when the field is empty: ['nullable', new HijriDateRule()].
Localization
Month and Day Names
A Hijri object picks month names from its locale. Any locale starting with ar gets the Arabic names, and every other locale gets transliterated English names. Day names come from Carbon's translations for that locale.
1$hijri = Carbon::parse('2025-03-01')->toHijri();2 3$hijri->locale('en')->isoFormat('D MMMM YYYY'); // "1 Ramadan 1446"4$hijri->locale('ar')->isoFormat('D MMMM YYYY'); // "1 رَمضان 1446"| # | Arabic (ar) | Other locales |
|---|---|---|
| 1 | مُحرَّم | Muharram |
| 2 | صفَر | Safar |
| 3 | ربيع الأول | Rabi' Al-Awwal |
| 4 | ربيع الآخر | Rabi' Al-Akher |
| 5 | جمادى الأول | Jumada Al-Awwal |
| 6 | جمادى الآخرة | Jumada Al-Akherah |
| 7 | رَجب | Rajab |
| 8 | شَعبان | Sha'aban |
| 9 | رَمضان | Ramadan |
| 10 | شوّال | Shawwal |
| 11 | ذو القِعدة | Dhu Al-Qi'dah |
| 12 | ذو الحِجّة | Dhu Al-Hijjah |
The @hijri directive and HijriFormatter::format() use app()->getLocale() unless you pass a locale, so switching the app locale switches the output:
1app()->setLocale('ar');Validation Messages
The package registers its translations under the hijri namespace and ships en and ar. To edit them or add a language, publish them:
php artisan vendor:publish --tag=hijri-translationsThen add a folder for your locale with the same key:
1return [2 'hijri_date' => 'Le champ :attribute doit être une date hégirienne valide.',3];API Reference
Carbon Methods
Added to Carbon\Carbon (and so to Illuminate\Support\Carbon) by the Pharaonic\Hijri\HijriCarbon mixin.
| Method | Description | Returns |
|---|---|---|
$date->toHijri(?int $adjustment = null) | Converts the Gregorian date to Hijri. The adjustment applies to this call only. | Pharaonic\Hijri\Hijri |
Carbon::fromHijri(int $year, int $month, int $day, $tz = null, ?int $adjustment = null) | Creates a Gregorian date (midnight) from Hijri parts. Throws InvalidHijriDateException for a date that doesn't exist. | Carbon\Carbon |
Carbon::parseHijri(string $date, $tz = null, ?int $adjustment = null) | Parses YYYY-MM-DD[ HH:MM[:SS]] as Hijri and returns the Gregorian date. Throws InvalidHijriDateException on bad input. | Carbon\Carbon |
Carbon::setHijriAdjustment(int $days) | Sets the global day adjustment. | void |
Carbon::getHijriAdjustment() | Gets the global day adjustment. | int |
Hijri Class
Pharaonic\Hijri\Hijri extends Carbon\Carbon. Its date parts hold the Hijri values.
| Method | Description | Returns |
|---|---|---|
Hijri::fromGregorian($time = null, $tz = null, ?int $adjustment = null) | Converts any Carbon-supported input (string, DateTimeInterface, null for now) to Hijri. | Hijri |
Hijri::parse($time = null, $tz = null) | Same as fromGregorian() with the global adjustment. | Hijri |
$hijri->locale(string $locale) | Sets the locale and the matching Arabic or English month names. With no argument, returns the current locale. | Hijri or string |
$hijri->format(string $format) | PHP date() format with translated day and month names. | string |
$hijri->isoFormat(string $format) | Carbon ISO format with translated names. | string |
$hijri->year, ->month, ->day | The Hijri year, month and day. | int |
$hijri->monthName | The Hijri month name in the current locale. | string |
$hijri->dayName | The weekday name in the current locale. | string |
Package Classes
| Class / Member | Description | Returns |
|---|---|---|
HijriFormatter::format($date, ?string $format = null, ?string $locale = null, ?int $adjustment = null) | Formats a Gregorian DateTimeInterface or string as Hijri. Returns '' for null or ''. Used by @hijri. | string |
new HijriDateRule() | Validation rule for Hijri YYYY-MM-DD[ HH:MM[:SS]] strings. | Illuminate\Contracts\Validation\Rule |
@hijri($date, $format, $locale, $adjustment) | Blade directive that echoes HijriFormatter::format(), escaped. | Output |
Namespaces: Pharaonic\Laravel\Hijri\Support\HijriFormatter, Pharaonic\Laravel\Hijri\Rules\HijriDateRule, Pharaonic\Hijri\Hijri, Pharaonic\Hijri\Exception\InvalidHijriDateException.
Config and Publish Tags
| Item | Value |
|---|---|
| Config key | pharaonic.hijri (adjustment, format) |
| Config file | config/pharaonic/hijri.php |
| Translation namespace | hijri (key hijri::validation.hijri_date) |
| Config tags | hijri-config, pharaonic-config |
| Translation tags | hijri-translations, pharaonic-translations |
| All assets | laravel-hijri, pharaonic |
Examples
1. Show Hijri Dates on a Model
Add an accessor that returns the Hijri date, and print it next to the Gregorian one.
1namespace App; 2 3use Illuminate\Database\Eloquent\Model; 4use Pharaonic\Laravel\Hijri\Support\HijriFormatter; 5 6class Post extends Model 7{ 8 protected $dates = ['published_at']; 9 10 public function getPublishedAtHijriAttribute(): string11 {12 return HijriFormatter::format($this->published_at);13 }14}1<article>2 <h1>{{ $post->title }}</h1>3 4 <p class="meta">5 {{ $post->published_at->format('j F Y') }}6 · @hijri($post->published_at, 'dddd D MMMM YYYY')7 </p>8</article> 1namespace App\Http\Resources; 2 3use Illuminate\Http\Resources\Json\JsonResource; 4 5class PostResource extends JsonResource 6{ 7 public function toArray($request) 8 { 9 return [10 'title' => $this->title,11 'published_at' => $this->published_at->toDateString(),12 'published_at_hijri' => $this->published_at->toHijri()->format('Y-m-d'),13 'published_at_hijri_label' => $this->published_at_hijri,14 ];15 }16}2. Accept a Hijri Date in a Form
Validate the Hijri input, then store it as a Gregorian date so your queries keep working.
1namespace App\Http\Requests; 2 3use Illuminate\Foundation\Http\FormRequest; 4use Pharaonic\Laravel\Hijri\Rules\HijriDateRule; 5 6class StoreBookingRequest extends FormRequest 7{ 8 public function authorize() 9 {10 return true;11 }12 13 public function rules()14 {15 return [16 'name' => ['required', 'string', 'max:255'],17 'date' => ['required', new HijriDateRule()],18 ];19 }20} 1namespace App\Http\Controllers; 2 3use App\Booking; 4use App\Http\Requests\StoreBookingRequest; 5use Carbon\Carbon; 6 7class BookingController extends Controller 8{ 9 public function store(StoreBookingRequest $request)10 {11 Booking::create([12 'name' => $request->name,13 'date' => Carbon::parseHijri($request->date), // "1445-09-01" → 2024-03-1114 ]);15 16 return back()->with('status', 'Booking saved.');17 }18} 1<form method="POST" action="{{ route('bookings.store') }}"> 2 @csrf 3 4 <input name="name" value="{{ old('name') }}"> 5 6 <input name="date" placeholder="1445-09-01" value="{{ old('date') }}"> 7 @error('date') <span>{{ $message }}</span> @enderror 8 9 <button type="submit">Book</button>10</form>3. Ramadan Banner
Show a banner during Ramadan (month 9) and hide it the rest of the year.
1namespace App\Http\View\Composers; 2 3use Illuminate\View\View; 4 5class RamadanComposer 6{ 7 public function compose(View $view) 8 { 9 $today = now()->toHijri();10 11 $view->with('isRamadan', $today->month === 9);12 $view->with('ramadanDay', $today->day);13 }14}1@if ($isRamadan)2 <div class="banner">Ramadan Kareem! Day {{ $ramadanDay }} of Ramadan.</div>3@endif4. Find the Gregorian Date of an Occasion
Get the Gregorian start of a Hijri month, for example to schedule a campaign or a reminder.
1use Carbon\Carbon; 2 3$hijriYear = now()->toHijri()->year; 4 5$ramadan = Carbon::fromHijri($hijriYear, 9, 1); // 1446 → 2025-03-01 6$eidFitr = Carbon::fromHijri($hijriYear, 10, 1); // 1446 → 2025-03-31 7$eidAdha = Carbon::fromHijri($hijriYear, 12, 10); 8 9if ($eidFitr->isPast()) {10 $eidFitr = Carbon::fromHijri($hijriYear + 1, 10, 1);11}12 13$daysLeft = now()->diffInDays($eidFitr);5. Per-user Region Adjustment
Let users pick an adjustment that matches their country's moon sighting, and pass it per call so other requests aren't affected.
1use Pharaonic\Laravel\Hijri\Support\HijriFormatter;2 3// Assumes `locale` and `hijri_adjustment` columns on your users table.4public function hijri($date): string5{6 return HijriFormatter::format($date, null, $this->locale, $this->hijri_adjustment);7}1@hijri(now(), 'dddd D MMMM YYYY', auth()->user()->locale, auth()->user()->hijri_adjustment)Troubleshooting
Call to undefined method toHijri()
The mixin is registered when HijriServiceProvider boots. Check that:
- Package discovery isn't disabled for it (
dont-discoverin your app'scomposer.json). If it is, addPharaonic\Laravel\Hijri\HijriServiceProvider::classtoprovidersinconfig/app.php. - You're not calling it before the app has booted, for example in a config file.
- Run
php artisan package:discoverafter installing.
The Hijri date is off by one day
The tabular calendar can differ from your local moon sighting. Set HIJRI_ADJUSTMENT in .env (the default is -1), or pass an adjustment per call: $date->toHijri(0). If you cache your config, run php artisan config:cache again.
Changing the adjustment in config at runtime does nothing
pharaonic.hijri.adjustment is read only once, at boot. Use Carbon::setHijriAdjustment($days) to change the global value, or pass $adjustment to toHijri(), fromHijri(), parseHijri() or @hijri.
Month names appear in English in an Arabic page
Arabic names are used only when the Hijri object's locale starts with ar. toHijri() doesn't use your app locale, so set it explicitly: $date->toHijri()->locale('ar'). The @hijri directive uses app()->getLocale(), so check the app locale is set before the view renders.
InvalidHijriDateException when parsing
Carbon::parseHijri() only accepts YYYY-MM-DD with an optional HH:MM[:SS] time, and the day must exist in that Hijri month. Validate user input with HijriDateRule first, or catch Pharaonic\Hijri\Exception\InvalidHijriDateException.
Date math on a Hijri object gives odd results
A Hijri object stores Hijri values in a Carbon date, so Carbon helpers like addMonth(), daysInMonth or diffInDays() still follow Gregorian rules. Do your date math on the Gregorian Carbon date, then call toHijri() on the result.
1// Avoid2$date->toHijri()->addDays(30);3 4// Prefer5$date->copy()->addDays(30)->toHijri();The validation message shows hijri::validation.hijri_date
The translation wasn't found for the current locale. The package ships en and ar. For other locales, publish the translations with --tag=hijri-translations and add resources/lang/vendor/hijri/{locale}/validation.php, or set a fallback_locale of en.
Contributors
Amazing people who made this package possible
This package wouldn't exist without the contributions of our amazing community. A huge thank you to everyone who has helped improve Hijri!
Want to Contribute?
We welcome contributions from everyone! Whether it's bug fixes, new features, or documentation improvements - every contribution counts.