Skip to content

Hijri date math and comparisons (8.4.x) - #39

Merged
MoamenEltouny merged 2 commits into
8.4.xfrom
fix/hijri-calendar-math-8.4.x
Oct 8, 2026
Merged

MoamenEltouny merged 2 commits into
8.4.xfrom
fix/hijri-calendar-math-8.4.x

Conversation

@MoamenEltouny

Copy link
Copy Markdown
Member

Summary

Fixes the two remaining issues from the laravel-hijri PR #3 review: date math and comparisons on a Hijri instance.

  • Comparisons: a Hijri instance used to store the Hijri date as a Gregorian one (year 1445 AD), so getTimestamp(), eq(), lt(), between(), isPast(), diffInDays() and diffForHumans() were about 579 years off. For example, Hijri::fromGregorian('2024-03-11')->diffInDays(Carbon::parse('2024-03-11')) returned 211302. The instance now holds the real date and time, and reads the year, month and day from it with the adjustment it was converted with.
  • Date math:
    • Days and smaller units move the real date. Months, quarters, years, decades and centuries are Hijri ones, with Carbon's overflow settings. That covers addMonths(), add(), sub(), CarbonInterval, DateInterval and CarbonPeriod. From 29 Safar 1445, addDay() now gives 1 Rabi' Al-Awwal (it gave the 2nd) and addMonth() gives 29 Rabi' Al-Awwal (it gave 1 Rabi' Al-Akher).
    • setDate() and the year/month/day setters take Hijri values.
    • The start/end and first/last/nth boundaries use Hijri months and years.
    • daysInMonth, dayOfYear, isLeapYear(), diffInMonths(), diffInYears(), age, isSameMonth(), isSameYear() and isBirthday() are Hijri.
  • Output: format(), isoFormat(), toDateString(), toDateTimeString() and (string) still print the Hijri date. rawFormat(), getTimestamp(), toISOString() and json_encode() give the Gregorian date, so APIs receive a standard date.
  • Factories: Hijri::now(), Hijri::create() and Hijri::createFromFormat() now return Hijri dates with Hijri month names.
  • Exact moment: fromGregorian() and toHijri() keep the exact moment of a DateTimeInterface.
  • Serialization: serialize() keeps the date, timezone and adjustment.
  • Carbon 2 and 3: diffInMonths()/diffInYears() return int in Carbon 2 and float in Carbon 3, so src/Concerns/HijriDifference.php loads the trait for the installed version. tests/phpstan-carbon.php makes PHPStan analyse only that trait.
  • Tests: new HijriDateMathTest covering:
    • comparisons with Gregorian dates;
    • every day from 2019 to 2026 adding up to the next day;
    • month math, with and without overflow, from every 5th day of 2022–2025 against the converter;
    • setters, boundaries, first/last/nth weekdays, differences, same-unit checks, adjustments, timezones, strings and serialization.
  • Docs: new "Date Math & Comparisons" section, plus updates to Basic Usage, Hijri to Gregorian, Formatting, the API reference, Examples, Troubleshooting (the old "Limitations" section is gone), the overview, landing features and FAQ, and the changelog.

Behaviour changes (listed under "Changed" in the changelog):

  • getTimestamp() and the ISO/JSON strings are now Gregorian.
  • toGregorian() no longer converts a changed instance's values or throws.
  • Internal Hijri properties used for the old representation were removed.

Verified on each line's own PHP version with the lowest and highest dependencies, and also with Carbon 2.55.0, 2.62.1, 2.73, 3.0.0 and 3.14.

A Hijri instance stored the Hijri date as a Gregorian one (1445 AD), so
its timestamp, comparisons and differences were about 579 years off, and
Carbon's date math applied Gregorian month lengths to the Hijri numbers.

Keep the real date and time, and read the year, month and day from it
with the instance's adjustment. Day and time math, comparisons and
timestamps now work with any other date, and months, quarters, years and
larger units, setDate() and the year/month/day setters, start/end and
first/last/nth boundaries, month and year differences and same-month
checks follow the Hijri calendar. format(), isoFormat() and the readable
to*String() methods still print the Hijri date; rawFormat(), the
timestamp and the ISO/JSON strings give the Gregorian one.

diffInMonths() and diffInYears() return an int in Carbon 2 and a float
in Carbon 3, so their overrides are loaded for the installed version.
@MoamenEltouny MoamenEltouny self-assigned this Oct 7, 2026
@MoamenEltouny
MoamenEltouny merged commit 58353c8 into 8.4.x Oct 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant