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

Ext Calendar Laravel Package

fisharebest/ext-calendar

A pure-PHP implementation of PHP's calendar extension, providing cal_* functions and conversions between Julian day counts and Gregorian, Julian, Jewish, and French Republican calendars. Useful as a polyfill when ext-calendar is unavailable.

Deep Wiki
Context7

Getting Started

composer require fisharebest/ext-calendar

There is nothing to configure: no service provider, no config file, no published assets. It is a plain, dependency-free PHP library whose whole purpose is to be a pure-PHP replacement for ext/calendar (which most shared hosts no longer compile in). Recent releases require PHP 8.x; check composer.json for the exact floor.

Where to look first

  1. src/CalendarInterface.php — the contract every calendar implements.
  2. src/Calendar.php — the abstract base with the shared helpers (daysInMonth(), monthsInYear(), leapYear(), dayOfWeek()).
  3. The six concrete classes: Gregorian, Julian, French, Jewish, Persian, Arabic.

The one idea that makes the whole package click: every conversion goes through the Julian Day Number, an integer day count. You never convert calendar A directly to calendar B — you convert A → JD → B. Each concrete class exposes the generic pair calendarToJd() / jdToCalendar() plus a named convenience pair (gregorianToJd() / jdToGregorian(), jewishToJd() / jdToJewish(), and so on).

First use case — you have a Gregorian date in the database and need to show the Hijri equivalent, or you are importing GEDCOM date escapes (@#DJULIAN@, @#DHEBREW@, @#DFRENCH R@) and need a sortable key:

use Fisharebest\ExtCalendar\Gregorian;
use Fisharebest\ExtCalendar\Arabic;

$jd = (new Gregorian())->gregorianToJd(2024, 3, 25); // int

[$y, $m, $d] = (new Arabic())->jdToCalendar($jd);    // [1445, 9, 15]

Implementation Patterns

Store the JD, not the string. The highest-leverage decision is to add an indexed INT column (date_jd) alongside the original human-entered date. Sorting, range queries, "born before", and age arithmetic all collapse to integer comparison. Render into whatever calendar the viewer's locale wants at display time.

Build a small factory/registry. Map the GEDCOM escape or a locale calendar code to an instance, and memoize it — the objects are stateless and cheap, but re-instantiating them in a render loop is pure waste:

final class Calendars
{
    private static array $instances = [];

    public static function for(string $code): CalendarInterface
    {
        return self::$instances[$code] ??= match ($code) {
            'gregorian' => new Gregorian(),
            'julian'    => new Julian(),
            'hebrew'    => new Jewish(),
            'french'    => new French(),
            'persian'   => new Persian(),
            'hijri'     => new Arabic(),
        };
    }
}

In Laravel, bind this as a singleton in a service provider if you want it injectable and swappable in tests.

Validate before you convert. Parsing a user string like 30 February 1700 into a silently wrong JD is the classic bug. Gate every calendarToJd() call behind daysInMonth($y, $m) and 1 <= $m <= monthsInYear($y), and reject with a message that names the calendar.

Round-trip everything in tests. The cheapest possible correctness harness, and it catches leap-month and epoch bugs instantly:

foreach ($samples as [$y, $m, $d]) {
    $jd = $cal->calendarToJd($y, $m, $d);
    self::assertSame([$y, $m, $d], $cal->jdToCalendar($jd));
}

Keep timezones out of the library. The package is date-only. Convert a DateTimeImmutable to Y-m-d in the intended timezone first, then hand those three integers to calendarToJd(). Do not pass a Unix timestamp through unixtojd(); midnight boundaries and DST will shift the day.

Iterate, don't assume. Month loops must be for ($m = 1; $m <= $cal->monthsInYear($y); $m++) and day loops must use daysInMonth($y, $m). Twelve months, 30 days, and 31 days are all wrong assumptions for at least three of these calendars. For a month-grid UI:

$firstJd  = $cal->calendarToJd($y, $m, 1);
$offset   = $cal->dayOfWeek($firstJd);       // verify 0=Sunday vs 0=Monday
$dayCount = $cal->daysInMonth($y, $m);

Cross-calendar comparisons are integer math. "Days between these two Hebrew dates", "is this Julian date before that Persian one" — convert both to JD and subtract. Age and anniversary logic becomes trivial and exact.

Separate formatting from conversion. Use intl / ICU skeletons for month names and digit shaping, keyed by calendar name, and use this package only for the numeric conversion. Don't try to make it do localization; it won't.

Extension point. To add Ethiopian, Coptic, or Bahá'í, extend the abstract Calendar (or implement CalendarInterface directly) and supply calendarToJd() / jdToCalendar() plus the epoch offset and leap rule. Everything downstream only sees CalendarInterface, so your pipeline needs no changes.

Gotchas and Tips

  • GPL-3.0 — read this first. It is a strong copyleft license with no linking exception. If you ship closed-source software, this is a real constraint, not a footnote. Decide before you build on it; the alternatives are isolating it behind a separate process, finding a permissively licensed equivalent, or accepting the license.
  • These are arithmetic calendars, not observational ones. The Arabic class is the tabular Islamic calendar; real Umm al-Qura or moon-sighting dates can differ by one or two days. Persian uses the arithmetic cycle rather than an observatory-derived equinox. French is the arithmetic (Romme) variant. Never label output "official" or use it for religious/legal obligations without a caveat.
  • No 1582 cutover. Gregorian is proleptic Gregorian and Julian is proleptic Julian. There is no automatic switch on 1582-10-15, and the library cannot tell you which one a historical date should use — that is a policy decision you encode.
  • Leap months break naive validation. Year 13 months exist in the Jewish calendar and Arabic months drift roughly 11 days per solar year. Any $month <= 12 check is a latent bug; always consult monthsInYear() and daysInMonth().
  • Pin down dayOfWeek()'s origin. Whether 0 means Sunday or Monday varies by library and has changed across versions in the wider ecosystem. Assert it in a test against a known date (2024-01-01 was a Monday) rather than reading it from memory.
  • Jewish month numbering is the trap. The civil year starts at Tishri but the religious ordering starts at Nisan; the numbering the class uses internally is not necessarily the one your UI labels expect. Verify against a known anchor (1 Tishri 5785 = 2024-10-03) before mapping numbers to names like "Adar II".
  • JD is a day number, not a timestamp. It has no time, no timezone, and no leap seconds, and the noon-origin convention of classical JDN does not apply to how this library treats the integer. Passing floats or datetimes will produce off-by-one results.
  • Eyeball the JD when debugging. JD 2460000 is 2023-02-24 Gregorian. Printing the intermediate integer is usually faster than stepping through calendar math.
  • Proleptic edge cases. Year 0 and negative years may behave inconsistently across the implementations. If you handle antiquity, test explicitly rather than assuming symmetry around the epoch.
  • Guard your supply chain. With a small project and modest star count, pin the exact version in composer.json ("fisharebest/ext-calendar": "6.*") and keep your own round-trip and anchor-date tests in CI, so an upstream correction to a leap rule shows up as a red build rather than a silent data shift.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource