Calendrical. Islamic. UmmAlQura. Astronomical
(Calendrical v2.2.0)
Copy Markdown
Implements the Umm al-Qura calendar for determining the first day of each Hijri month using the astronomical rules documented by R. H. van Gent.
Eras
Era 2 (1392–1419 AH / 1972–1999 CE) — Conjunction rule (best effort). The first day of the month is the Gregorian day following the geocentric conjunction. This reproduces 96.7 % of van Gent's dates for the era; the remaining ≈ 3 % differ by one day due to undocumented details in the historical Saudi determination process.
Era 3 (1420–1422 AH / 1999–2002 CE) — Moonset-only rule. On the 29th of the current month, if moonset occurs after sunset at Mecca the next day is 1st of the new month; otherwise the month is extended to 30 days.
Era 4 (1423 AH onward / 2002 CE onward) — Full Umm al-Qura rule. On the 29th day of the current Hijri month, if both of the following conditions hold as observed from Mecca (21.4225° N, 39.8262° E), then the following day is the 1st day of the new Hijri month:
- The geocentric conjunction occurs before sunset in Mecca.
- Moonset in Mecca occurs after sunset in Mecca.
If either condition fails, the current month is extended to 30 days.
Years before 1392 AH are not supported because no reliable astronomical rule has been established for that period. After them the calendar reaches as far as the installed JPL ephemeris: to about 1524 AH with the ephemeris Astro bundles, and about 1575 AH with the full one.
Accuracy
The astronomical computation uses centre-of-disk sunset and centre-of-disk moonset conventions (matching Skyfield/van Gent) and achieves 99.7 % agreement (934 of 937 months) with the van Gent reference dataset for Era 4 (1423–1500 AH). The three boundary-case months where the algorithm disagrees are:
| Hijri month | Gregorian (van Gent) | Gregorian (algorithm) | Root cause |
|---|---|---|---|
| 1446/6 | 2024-12-02 | 2024-12-03 | Moonset 7 s before sunset |
| 1475/11 | 2053-06-17 | 2053-06-18 | Moonset 3 s before sunset (Skyfield also fails) |
| 1485/10 | 2063-01-30 | 2063-01-31 | Moonset 5 s before sunset |
All three involve moonset-sunset gaps of less than 10 seconds — below the precision floor of any rise/set calculation given atmospheric refraction uncertainty (±2 arcmin ≈ ±10 s).
This module is tested against van Gent's tables
(Calendrical.Islamic.UmmAlQura.ReferenceData), which apply the same
rules.
Against KACST's table and ICU
For the official Saudi calendar use Calendrical.Islamic.UmmAlQura,
which is built from KACST's published month lengths and, after 1500 AH,
ICU's. This calendar computes the published rule; the tables follow it
only so far.
To 1450 AH the rule is KACST's. 335 of the 336 months of 1423 to 1450 AH begin on the day KACST's table begins them.
From 1451 AH the tables begin a third of months a day later. 209 of the 600 months of 1451 to 1500 AH begin a day after this calendar's, and so do 92 of the 287 months of 1501 to 1524 AH in ICU's table, which is the same data. Never a day earlier.
They are the months whose moon barely sets after the sun. On the 29th each of those months meets the rule, but its moon sets less than about 20 minutes after the sun; the months the tables begin as this calendar does have a median of 35 minutes.
We think the reason is that the tables' months from 1451 AH, which begins in 2029 CE, are KACST's projections rather than announced dates, and are computed by a stricter procedure than the published rule. That procedure is not documented: it is not a crescent-visibility criterion (Yallop's and Odeh's separate the months only far below any of their zones), not another site (Riyadh fits worse than Mecca), and not a difference of rise and set conventions, which is a minute or two. ICU and the JDK carry KACST's table without a rule of their own, so they do not explain it either. Until KACST's criterion is known, this calendar keeps the published rule.
Reference
hijridate (dralshehri), which ships van Gent's tables.
Dershowitz & Reingold, Calendrical Calculations (4th ed.), Chapter 6.
Summary
Functions
Returns a map with the detailed astronomical data used in evaluating the Umm al-Qura rule for the given Gregorian date (which should be the 29th day of the current Hijri month).
Calculates the Gregorian date of the 1st day of the Hijri month identified by
hijri_year and hijri_month (1-based, 1 = Muharram … 12 = Dhū al-Ḥijja).
Functions
@spec evaluate_era_4_conditions(Date.t()) :: {:ok, map()} | {:error, Exception.t()}
Returns a map with the detailed astronomical data used in evaluating the Umm al-Qura rule for the given Gregorian date (which should be the 29th day of the current Hijri month).
Arguments
dateis the GregorianDate.t/0of the candidate 29th day of the Hijri month.
Returns
{:ok, map}where the map has these keys::dateis the candidate 29th Gregorian date.:conjunction_utcis theDateTime.t/0of the geocentric conjunction (new moon).:sunset_meccais theDateTime.t/0of sunset in Mecca (UTC).:moonset_meccais theDateTime.t/0of moonset in Mecca (UTC), or:no_time.:conjunction_before_sunset?is a boolean.:moonset_after_sunset?is a boolean.:new_month_starts_next_day?istruewhen both conditions are met, otherwisefalse.
{:error, %Calendrical.UnsupportedDateRangeError{}}for a date the installed ephemeris does not cover, or{:error, %Calendrical.AstronomyError{}}where the sun does not set at Mecca that day.
Examples
iex> {:ok, evaluation} =
...> Calendrical.Islamic.UmmAlQura.Astronomical.evaluate_era_4_conditions(~D[2025-02-28])
iex> evaluation.new_month_starts_next_day?
true
@spec first_day_of_month(pos_integer(), 1..12) :: {:ok, Date.t()} | {:error, Exception.t()}
Calculates the Gregorian date of the 1st day of the Hijri month identified by
hijri_year and hijri_month (1-based, 1 = Muharram … 12 = Dhū al-Ḥijja).
Era 4 (≥ 1423 AH): full Umm al-Qura rule (conjunction before sunset AND moonset after sunset).
Era 3 (1420–1422 AH): moonset-after-sunset only.
Era 2 (1392–1419 AH): conjunction within 3 h of 0 h UTC.
Arguments
hijri_yearis the Hijri year, from 1392 AH.hijri_monthis the Hijri month, from1to12.
Returns
{:ok, date}wheredateis the GregorianDate.t/0of the first day of the month.{:error, %Calendrical.UnsupportedDateRangeError{}}for a year before 1392 AH, or a month the installed ephemeris does not cover, or{:error, %Calendrical.InvalidMonthError{}}for a month not from 1 to 12.
Examples
iex> Calendrical.Islamic.UmmAlQura.Astronomical.first_day_of_month(1446, 9)
{:ok, ~D[2025-03-01]} # Era 4 — full rule
iex> Calendrical.Islamic.UmmAlQura.Astronomical.first_day_of_month(1421, 1)
{:ok, ~D[2000-04-06]} # Era 3 — moonset only
iex> Calendrical.Islamic.UmmAlQura.Astronomical.first_day_of_month(1400, 9)
{:ok, ~D[1980-07-13]} # Era 2 — conjunction rule