Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Calendar Laravel Package

islamic-network/calendar

A PHP library for working with the Islamic (Hijri) calendar. Convert dates between Gregorian and Hijri, format Islamic dates, and handle calendar calculations with a simple, lightweight API suitable for Laravel and other PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a small, framework-agnostic PHP library — there is no Laravel service provider, no facade, and no auto-discovery. You install it and use its classes directly.

composer require islamic-network/calendar

First things to do, in order:

  1. Read the README on the repo, not the Packagist page. The public surface is tiny (two conversion directions plus month-length/leap-year helpers), and the exact class/method names matter more than usual because there's no facade to smooth them over. Confirm the signature before you write a wrapper — the examples below use a placeholder Hijri class.
  2. Smoke-test the round trip in Tinker before designing anything:
use IslamicNetwork\Calendar\Hijri; // confirm exact class in README

$h = Hijri::gregorianToHijri(2024, 3, 11);   // -> [year, month, day]
$g = Hijri::hijriToGregorian($h[0], $h[1], $h[2]);
  1. Pick your first use case. The one that pays for itself immediately is display-side: keep every date column in Gregorian (your created_at, published_at, starts_at stay untouched) and render a Hijri line next to it. The second, riskier one is input-side: accept a Hijri date from a form and store the converted Gregorian value.

If conversion is core to your domain, install it and write a 20-line test with a handful of known date pairs on day one. That test is your upgrade safety net.

Implementation Patterns

Wrap it. Don't call it from controllers or Blade. Treat the library as an implementation detail behind your own interface, so you can swap in Umm al-Qura or a moon-sighting-adjusted calculator later without touching call sites.

namespace App\Support;

use IslamicNetwork\Calendar\Hijri;

class HijriConverter
{
    public function __construct(private int $offsetDays = 0) {}

    public function fromGregorian(\DateTimeInterface $date): HijriDate
    {
        [$y, $m, $d] = Hijri::gregorianToHijri(
            (int) $date->format('Y'),
            (int) $date->format('n'),
            (int) $date->format('j'),
        );

        return new HijriDate($y, $m, $d);
    }

    public function toGregorian(HijriDate $h): \Carbon\CarbonImmutable
    {
        [$y, $m, $d] = Hijri::hijriToGregorian($h->year, $h->month, $h->day);

        return CarbonImmutable::createFromDate($y, $m, $d, config('app.timezone'))
            ->addDays($this->offsetDays);
    }
}

Bind it as a singleton in AppServiceProvider and drive $offsetDays from config/calendar.php — that's your only knob for regional adjustment.

Value object + accessor for Eloquent. Never store a Hijri string in a date column. Store Gregorian, expose Hijri:

protected function hijriPublishedAt(): Attribute
{
    return Attribute::get(
        fn () => app(HijriConverter::class)->fromGregorian($this->published_at)
    );
}

A custom cast is fine too, but keep the underlying column Gregorian and timezone-aware.

Blade component, not a directive. A <x-hijri-date :date="$post->published_at" /> component lets you centralize month names, the "calculated" disclaimer, and <time datetime="..."> markup. Directive-based formatting is where you'll accidentally emit unescaped user-controlled strings.

Range queries are where the calendars fight you. "Everything in Rajab 1445" is not a whereMonth. Convert the Hijri month start and the next month's start to Gregorian and whereBetween:

[$sy, $sm, $sd] = Hijri::hijriToGregorian(1445, 7, 1);
[$ey, $em, $ed] = Hijri::hijriToGregorian(1445, 8, 1);

$q->whereBetween('starts_at', [
    Carbon::create($sy, $sm, $sd)->startOfDay(),
    Carbon::create($ey, $em, $ed)->startOfDay(),
]);

Validation rule for Hijri form input. Validate day against the actual month length (29 vs 30) rather than hardcoding 30 — use the library's month-length helper if it exposes one, otherwise convert (y, m, 1) and (y, m+1, 1) and diff.

API resources: emit both, canonicalize Gregorian.

return [
    'date'        => $model->starts_at->toIso8601String(),
    'hijri'       => ['year' => 1445, 'month' => 7, 'day' => 1],
    'hijri_label' => '1 Rajab 1445',
];

Consumers who need Hijri get it precomputed; consumers who don't aren't forced to parse it.

Bulk work belongs in a job with a memoized converter. If you're converting a 5k-row export, instantiate the converter once outside the loop and cache any repeated day arithmetic — repeated gregorianToHijri calls in a tight loop are pure function calls, so it should be cheap, but don't re-resolve from the container per row.

Gotchas and Tips

This is a calculated calendar, not an observed one. The library implements an arithmetic/tabular conversion. Real-world Hijri dates depend on moon sighting and vary by country by ±1 day, sometimes ±2. Do not use this for Ramadan/Eid announcements, prayer-time boundaries, or anything a user's mosque might contradict. Surface it in the UI as "calculated" and keep the offset config.

Timezone off-by-one is the #1 bug. Always extract Y/n/j from a date already normalized to the display timezone. Carbon::parse('2024-03-11') in a UTC-configured app is fine, but a DateTime that's 2024-03-11 23:30 +05:00 formats to the 11th while its UTC instant is still the 10th. Convert first, format second.

Round-tripping can be lossy at boundaries. Verify greg → hijri → greg is identity across a 29-day month edge, a 30-day month edge, and a Hijri leap year (1445, 1446, 1447). If a tabular implementation disagrees with your expected pairs, you found it in a test rather than in production.

No facade means no IDE help. use the class explicitly, or add your own Hijri facade over HijriConverter for readability. Don't assume Calendar:: resolves to anything.

Pin an exact version and treat upgrades as breaking. Zero dependents and a near-zero score means the API stability guarantee is "whatever the maintainer felt like." "islamic-network/calendar": "1.0.3" in composer.json, plus the round-trip test above, is the cheapest insurance available.

Don't sort on the Hijri string. '1445-9-1' sorts before '1445-10-1' lexicographically. Sort on the Gregorian column and only format to Hijri at render time. If you must sort Hijri strings, zero-pad (1445-09-01).

Hijri years aren't 354 or 355 uniformly. There are 11 leap days in a 30-year cycle; don't compute year length as a constant anywhere in your code.

Arabic month names ship with the package (if at all) in one language. Map to your own locale in the Blade component via a 12-element array keyed on month number. Don't string-translate the package's output — you'll break on the next release.

Extension point to keep open: if your users are Gulf-based, Umm al-Qura may be expected rather than tabular; if South Asian, moon-sighting. The library likely implements one variant. Wrap it behind an interface now (HijriCalculator) so replacing the engine is a one-file change and not a refactor.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager