Laravel Packagev7.0.0MIT License

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 .

Quick Tip

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

Terminal
composer require pharaonic/laravel-hijri

Publish Configuration

Publishing the config is optional. Without it, the package uses its defaults (adjustment -1, format D MMMM YYYY).

Terminal
php artisan vendor:publish --tag=hijri-config

This creates config/pharaonic/hijri.php.

Publish Translations

Publish the validation messages only if you want to change them or add a language.

Terminal
php artisan vendor:publish --tag=hijri-translations

The files are copied to resources/lang/vendor/hijri.

Publish Tags

--tag=laravel-hijri (or --tag=pharaonic) publishes the config and the translations together. pharaonic-config and pharaonic-translations are also available.

Installation Complete

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.

config/pharaonic/hijri.php
1return [
2 'adjustment' => (int) env('HIJRI_ADJUSTMENT', -1),
3 
4 'format' => 'D MMMM YYYY',
5];

Options

KeyDefaultDescription
adjustment-1 (from HIJRI_ADJUSTMENT)Days added to every Hijri conversion when no per-call adjustment is given.
formatD MMMM YYYYThe 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:

.env
HIJRI_ADJUSTMENT=0
tinker
1Carbon::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"
Applied at Boot

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:

FormatOutput (en)
D MMMM YYYY1 Ramadan 1445
dddd D MMMM YYYYMonday 1 Ramadan 1445
YYYY/MM/DD1445/09/01
ddd, D MMM YYYYMon, 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; // 1445
6$hijri->month; // 9
7$hijri->day; // 1
8$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(); // -1
2 
3Carbon::setHijriAdjustment(0);
Shared State

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)
ArgumentTypeDefault
$dateDateTimeInterface, string or nullRequired
$formatstring (Carbon isoFormat tokens)config('pharaonic.hijri.format'), D MMMM YYYY
$localestringapp()->getLocale()
$adjustmentintThe global adjustment

Default Format

With only a date, the directive uses the configured format and the current app locale:

resources/views/posts/show.blade.php
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.

app/Http/Controllers/BookingController.php
1use Pharaonic\Laravel\Hijri\Rules\HijriDateRule;
2 
3$request->validate([
4 'date' => ['required', new HijriDateRule()],
5]);

What Passes

ValueResult
1445-09-01Passes
1445-9-1Passes
1445-09-01 14:30Passes
1445-02-30Fails (Safar has 29 days in the tabular calendar)
01/09/1445Fails (wrong format)
null, 123, blank stringFails (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:

LocaleMessage
enThe :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.

Nullable Fields

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:

Terminal
php artisan vendor:publish --tag=hijri-translations

Then add a folder for your locale with the same key:

resources/lang/vendor/hijri/fr/validation.php
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.

MethodDescriptionReturns
$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.

MethodDescriptionReturns
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, ->dayThe Hijri year, month and day.int
$hijri->monthNameThe Hijri month name in the current locale.string
$hijri->dayNameThe weekday name in the current locale.string

Package Classes

Class / MemberDescriptionReturns
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

ItemValue
Config keypharaonic.hijri (adjustment, format)
Config fileconfig/pharaonic/hijri.php
Translation namespacehijri (key hijri::validation.hijri_date)
Config tagshijri-config, pharaonic-config
Translation tagshijri-translations, pharaonic-translations
All assetslaravel-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.

app/Post.php
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(): string
11 {
12 return HijriFormatter::format($this->published_at);
13 }
14}
resources/views/posts/show.blade.php
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>
app/Http/Resources/PostResource.php
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.

app/Http/Requests/StoreBookingRequest.php
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}
app/Http/Controllers/BookingController.php
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-11
14 ]);
15 
16 return back()->with('status', 'Booking saved.');
17 }
18}
resources/views/bookings/create.blade.php
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.

app/Http/View/Composers/RamadanComposer.php
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}
resources/views/layouts/app.blade.php
1@if ($isRamadan)
2 <div class="banner">Ramadan Kareem! Day {{ $ramadanDay }} of Ramadan.</div>
3@endif

4. Find the Gregorian Date of an Occasion

Get the Gregorian start of a Hijri month, for example to schedule a campaign or a reminder.

app/Console/Commands/ScheduleEidReminder.php
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.

app/User.php
1use Pharaonic\Laravel\Hijri\Support\HijriFormatter;
2 
3// Assumes `locale` and `hijri_adjustment` columns on your users table.
4public function hijri($date): string
5{
6 return HijriFormatter::format($date, null, $this->locale, $this->hijri_adjustment);
7}
resources/views/dashboard.blade.php
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-discover in your app's composer.json). If it is, add Pharaonic\Laravel\Hijri\HijriServiceProvider::class to providers in config/app.php.
  • You're not calling it before the app has booted, for example in a config file.
  • Run php artisan package:discover after 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// Avoid
2$date->toHijri()->addDays(30);
3 
4// Prefer
5$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!

Moamen Eltouny

@MoamenEltouny

46 contributions

Want to Contribute?

We welcome contributions from everyone! Whether it's bug fixes, new features, or documentation improvements - every contribution counts.