diff --git a/holidays/calendars/__init__.py b/holidays/calendars/__init__.py
index 1bc4f45b3f..9c3759ec8e 100644
--- a/holidays/calendars/__init__.py
+++ b/holidays/calendars/__init__.py
@@ -18,7 +18,7 @@
from holidays.calendars.gregorian import GREGORIAN_CALENDAR
from holidays.calendars.hebrew import _HebrewLunisolar
from holidays.calendars.hindu import _CustomHinduHolidays, _HinduLunisolar
-from holidays.calendars.islamic import _CustomIslamicHolidays, _IslamicLunar
+from holidays.calendars.islamic import _CustomIslamicHolidays, _IslamicLunar, _IslamicMabimsLunar
from holidays.calendars.julian import JULIAN_CALENDAR
from holidays.calendars.julian_revised import JULIAN_REVISED_CALENDAR
from holidays.calendars.mongolian import _CustomMongolianHolidays, _MongolianLunisolar
diff --git a/holidays/calendars/islamic.py b/holidays/calendars/islamic.py
index d98ad94bc9..97cdc73ff1 100644
--- a/holidays/calendars/islamic.py
+++ b/holidays/calendars/islamic.py
@@ -60,6 +60,33 @@
class _IslamicLunar:
+ ali_al_rida_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ ali_birthday_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ ali_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ arbaeen_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ ashura_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ eid_al_adha_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ eid_al_fitr_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ eid_al_ghadir_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ fatima_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ grand_magal_of_touba_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ hari_hol_johor_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ hasan_al_askari_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ hijri_new_year_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ imam_mahdi_birthday_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ isra_and_miraj_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ jumuatul_wida_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ laylat_al_qadr_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ maldives_embraced_islam_day_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ mawlid_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ nuzul_al_quran_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ prophet_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ quamee_dhuvas_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ ramadan_beginning_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ sadiq_birthday_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ sadiq_death_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+ tasua_dates: tuple[int, int] | tuple[tuple[int, int], ...]
+
ALI_AL_RIDA_DEATH_DATES = {
1924: (SEP, 28),
1925: (SEP, 18),
@@ -998,6 +1025,9 @@ class _IslamicLunar:
2077: (OCT, 27),
}
+ EID_AL_FITR_DATES_CONFIRMED_YEARS = (2001, 2026)
+ EID_AL_ADHA_DATES_CONFIRMED_YEARS = (2001, 2026)
+
EID_AL_FITR_DATES = {
1925: (APR, 24),
1926: (APR, 14),
@@ -4264,3 +4294,952 @@ def tasua_dates(self, year: int) -> Iterable[tuple[date, bool]]:
class _CustomIslamicHolidays(_CustomCalendar, _IslamicLunar):
pass
+
+
+class _IslamicMabimsLunar(_CustomCalendar, _IslamicLunar):
+ """Islamic calendar based on MABIMS crescent visibility criteria.
+
+ Used by Brunei Darussalam, Indonesia, Malaysia, and Singapore.
+ Dates are calculated using Singapore as the reference location
+ (primary MABIMS Hisab observer) with the MABIMS 2021 criteria:
+ - Moon altitude >= 3 degrees at sunset
+ - Geocentric elongation >= 6.4 degrees
+
+ References:
+ *
+ *
+ *
+ """
+
+ EID_AL_ADHA_DATES_CONFIRMED_YEARS = (1925, 2052)
+ EID_AL_FITR_DATES_CONFIRMED_YEARS = (1925, 2052)
+ HIJRI_NEW_YEAR_DATES_CONFIRMED_YEARS = (1925, 2052)
+ ISRA_AND_MIRAJ_DATES_CONFIRMED_YEARS = (1925, 2052)
+ MAWLID_DATES_CONFIRMED_YEARS = (1925, 2052)
+ NUZUL_AL_QURAN_DATES_CONFIRMED_YEARS = (1925, 2052)
+ RAMADAN_BEGINNING_DATES_CONFIRMED_YEARS = (1925, 2052)
+
+ EID_AL_ADHA_DATES = {
+ 1924: (JUL, 13),
+ 1925: (JUL, 2),
+ 1926: (JUN, 21),
+ 1927: (JUN, 10),
+ 1928: (MAY, 30),
+ 1929: (MAY, 20),
+ 1930: (MAY, 9),
+ 1931: (APR, 29),
+ 1932: (APR, 17),
+ 1933: (APR, 6),
+ 1934: (MAR, 26),
+ 1935: (MAR, 16),
+ 1936: (MAR, 4),
+ 1937: (FEB, 22),
+ 1938: (FEB, 11),
+ 1939: (JAN, 31),
+ 1940: (JAN, 20),
+ 1941: ((JAN, 8), (DEC, 29)),
+ 1942: (DEC, 19),
+ 1943: (DEC, 8),
+ 1944: (NOV, 27),
+ 1945: (NOV, 16),
+ 1946: (NOV, 5),
+ 1947: (OCT, 25),
+ 1948: (OCT, 13),
+ 1949: (OCT, 3),
+ 1950: (SEP, 23),
+ 1951: (SEP, 12),
+ 1952: (AUG, 31),
+ 1953: (AUG, 20),
+ 1954: (AUG, 10),
+ 1955: (JUL, 30),
+ 1956: (JUL, 19),
+ 1957: (JUL, 8),
+ 1958: (JUN, 28),
+ 1959: (JUN, 17),
+ 1960: (JUN, 5),
+ 1961: (MAY, 25),
+ 1962: (MAY, 15),
+ 1963: (MAY, 4),
+ 1964: (APR, 23),
+ 1965: (APR, 13),
+ 1966: (APR, 2),
+ 1967: (MAR, 22),
+ 1968: (MAR, 10),
+ 1969: (FEB, 27),
+ 1970: (FEB, 17),
+ 1971: (FEB, 7),
+ 1972: (JAN, 27),
+ 1973: (JAN, 15),
+ 1974: ((JAN, 4), (DEC, 24)),
+ 1975: (DEC, 14),
+ 1976: (DEC, 2),
+ 1977: (NOV, 22),
+ 1978: (NOV, 11),
+ 1979: (NOV, 1),
+ 1980: (OCT, 20),
+ 1981: (OCT, 9),
+ 1982: (SEP, 28),
+ 1983: (SEP, 18),
+ 1984: (SEP, 6),
+ 1985: (AUG, 27),
+ 1986: (AUG, 16),
+ 1987: (AUG, 5),
+ 1988: (JUL, 24),
+ 1989: (JUL, 14),
+ 1990: (JUL, 3),
+ 1991: (JUN, 23),
+ 1992: (JUN, 12),
+ 1993: (JUN, 1),
+ 1994: (MAY, 21),
+ 1995: (MAY, 10),
+ 1996: (APR, 29),
+ 1997: (APR, 18),
+ 1998: (APR, 8),
+ 1999: (MAR, 28),
+ 2000: (MAR, 17),
+ 2001: (MAR, 6),
+ 2002: (FEB, 23),
+ 2003: (FEB, 12),
+ 2004: (FEB, 1),
+ 2005: (JAN, 21),
+ 2006: ((JAN, 11), (DEC, 31)),
+ 2007: (DEC, 20),
+ 2008: (DEC, 8),
+ 2009: (NOV, 27),
+ 2010: (NOV, 17),
+ 2011: (NOV, 6),
+ 2012: (OCT, 26),
+ 2013: (OCT, 16),
+ 2014: (OCT, 5),
+ 2015: (SEP, 24),
+ 2016: (SEP, 12),
+ 2017: (SEP, 1),
+ 2018: (AUG, 22),
+ 2019: (AUG, 12),
+ 2020: (JUL, 31),
+ 2021: (JUL, 21),
+ 2022: (JUL, 10),
+ 2023: (JUN, 29),
+ 2024: (JUN, 17),
+ 2025: (JUN, 7),
+ 2026: (MAY, 27),
+ 2027: (MAY, 17),
+ 2028: (MAY, 5),
+ 2029: (APR, 25),
+ 2030: (APR, 14),
+ 2031: (APR, 3),
+ 2032: (MAR, 22),
+ 2033: (MAR, 12),
+ 2034: (MAR, 2),
+ 2035: (FEB, 19),
+ 2036: (FEB, 8),
+ 2037: (JAN, 27),
+ 2038: (JAN, 16),
+ 2039: ((JAN, 6), (DEC, 26)),
+ 2040: (DEC, 15),
+ 2041: (DEC, 4),
+ 2042: (NOV, 23),
+ 2043: (NOV, 12),
+ 2044: (NOV, 1),
+ 2045: (OCT, 21),
+ 2046: (OCT, 11),
+ 2047: (SEP, 30),
+ 2048: (SEP, 19),
+ 2049: (SEP, 8),
+ 2050: (AUG, 28),
+ 2051: (AUG, 17),
+ 2052: (AUG, 6),
+ }
+
+ EID_AL_FITR_DATES = {
+ 1924: (MAY, 5),
+ 1925: (APR, 25),
+ 1926: (APR, 14),
+ 1927: (APR, 4),
+ 1928: (MAR, 23),
+ 1929: (MAR, 13),
+ 1930: (MAR, 2),
+ 1931: (FEB, 19),
+ 1932: (FEB, 8),
+ 1933: (JAN, 28),
+ 1934: (JAN, 17),
+ 1935: ((JAN, 7), (DEC, 27)),
+ 1936: (DEC, 16),
+ 1937: (DEC, 5),
+ 1938: (NOV, 24),
+ 1939: (NOV, 13),
+ 1940: (NOV, 1),
+ 1941: (OCT, 22),
+ 1942: (OCT, 12),
+ 1943: (OCT, 1),
+ 1944: (SEP, 19),
+ 1945: (SEP, 8),
+ 1946: (AUG, 28),
+ 1947: (AUG, 18),
+ 1948: (AUG, 7),
+ 1949: (JUL, 27),
+ 1950: (JUL, 17),
+ 1951: (JUL, 6),
+ 1952: (JUN, 24),
+ 1953: (JUN, 13),
+ 1954: (JUN, 3),
+ 1955: (MAY, 23),
+ 1956: (MAY, 12),
+ 1957: (MAY, 2),
+ 1958: (APR, 21),
+ 1959: (APR, 10),
+ 1960: (MAR, 29),
+ 1961: (MAR, 18),
+ 1962: (MAR, 8),
+ 1963: (FEB, 26),
+ 1964: (FEB, 15),
+ 1965: (FEB, 3),
+ 1966: (JAN, 23),
+ 1967: (JAN, 12),
+ 1968: ((JAN, 2), (DEC, 21)),
+ 1969: (DEC, 11),
+ 1970: (NOV, 30),
+ 1971: (NOV, 20),
+ 1972: (NOV, 8),
+ 1973: (OCT, 28),
+ 1974: (OCT, 17),
+ 1975: (OCT, 7),
+ 1976: (SEP, 25),
+ 1977: (SEP, 15),
+ 1978: (SEP, 4),
+ 1979: (AUG, 24),
+ 1980: (AUG, 12),
+ 1981: (AUG, 2),
+ 1982: (JUL, 22),
+ 1983: (JUL, 12),
+ 1984: (JUL, 1),
+ 1985: (JUN, 20),
+ 1986: (JUN, 9),
+ 1987: (MAY, 29),
+ 1988: (MAY, 17),
+ 1989: (MAY, 7),
+ 1990: (APR, 27),
+ 1991: (APR, 16),
+ 1992: (APR, 5),
+ 1993: (MAR, 25),
+ 1994: (MAR, 14),
+ 1995: (MAR, 3),
+ 1996: (FEB, 20),
+ 1997: (FEB, 9),
+ 1998: (JAN, 30),
+ 1999: (JAN, 19),
+ 2000: ((JAN, 8), (DEC, 27)),
+ 2001: (DEC, 17),
+ 2002: (DEC, 6),
+ 2003: (NOV, 26),
+ 2004: (NOV, 14),
+ 2005: (NOV, 4),
+ 2006: (OCT, 24),
+ 2007: (OCT, 13),
+ 2008: (OCT, 1),
+ 2009: (SEP, 20),
+ 2010: (SEP, 10),
+ 2011: (AUG, 31),
+ 2012: (AUG, 19),
+ 2013: (AUG, 9),
+ 2014: (JUL, 29),
+ 2015: (JUL, 18),
+ 2016: (JUL, 6),
+ 2017: (JUN, 26),
+ 2018: (JUN, 15),
+ 2019: (JUN, 5),
+ 2020: (MAY, 24),
+ 2021: (MAY, 13),
+ 2022: (MAY, 3),
+ 2023: (APR, 22),
+ 2024: (APR, 10),
+ 2025: (MAR, 31),
+ 2026: (MAR, 21),
+ 2027: (MAR, 10),
+ 2028: (FEB, 27),
+ 2029: (FEB, 15),
+ 2030: (FEB, 4),
+ 2031: (JAN, 25),
+ 2032: (JAN, 14),
+ 2033: ((JAN, 3), (DEC, 23)),
+ 2034: (DEC, 12),
+ 2035: (DEC, 1),
+ 2036: (NOV, 20),
+ 2037: (NOV, 9),
+ 2038: (OCT, 30),
+ 2039: (OCT, 19),
+ 2040: (OCT, 8),
+ 2041: (SEP, 27),
+ 2042: (SEP, 16),
+ 2043: (SEP, 5),
+ 2044: (AUG, 25),
+ 2045: (AUG, 14),
+ 2046: (AUG, 4),
+ 2047: (JUL, 25),
+ 2048: (JUL, 13),
+ 2049: (JUL, 2),
+ 2050: (JUN, 21),
+ 2051: (JUN, 10),
+ 2052: (MAY, 30),
+ }
+
+ HIJRI_NEW_YEAR_DATES = {
+ 1923: (AUG, 14),
+ 1924: (AUG, 2),
+ 1925: (JUL, 23),
+ 1926: (JUL, 12),
+ 1927: (JUL, 1),
+ 1928: (JUN, 19),
+ 1929: (JUN, 9),
+ 1930: (MAY, 30),
+ 1931: (MAY, 19),
+ 1932: (MAY, 7),
+ 1933: (APR, 26),
+ 1934: (APR, 16),
+ 1935: (APR, 5),
+ 1936: (MAR, 25),
+ 1937: (MAR, 14),
+ 1938: (MAR, 4),
+ 1939: (FEB, 21),
+ 1940: (FEB, 10),
+ 1941: (JAN, 29),
+ 1942: (JAN, 18),
+ 1943: ((JAN, 8), (DEC, 29)),
+ 1944: (DEC, 17),
+ 1945: (DEC, 6),
+ 1946: (NOV, 25),
+ 1947: (NOV, 14),
+ 1948: (NOV, 3),
+ 1949: (OCT, 23),
+ 1950: (OCT, 13),
+ 1951: (OCT, 3),
+ 1952: (SEP, 21),
+ 1953: (SEP, 10),
+ 1954: (AUG, 30),
+ 1955: (AUG, 19),
+ 1956: (AUG, 8),
+ 1957: (JUL, 29),
+ 1958: (JUL, 18),
+ 1959: (JUL, 8),
+ 1960: (JUN, 26),
+ 1961: (JUN, 15),
+ 1962: (JUN, 4),
+ 1963: (MAY, 25),
+ 1964: (MAY, 13),
+ 1965: (MAY, 3),
+ 1966: (APR, 23),
+ 1967: (APR, 12),
+ 1968: (MAR, 31),
+ 1969: (MAR, 20),
+ 1970: (MAR, 9),
+ 1971: (FEB, 27),
+ 1972: (FEB, 17),
+ 1973: (FEB, 5),
+ 1974: (JAN, 25),
+ 1975: (JAN, 14),
+ 1976: ((JAN, 3), (DEC, 23)),
+ 1977: (DEC, 12),
+ 1978: (DEC, 2),
+ 1979: (NOV, 21),
+ 1980: (NOV, 9),
+ 1981: (OCT, 29),
+ 1982: (OCT, 19),
+ 1983: (OCT, 8),
+ 1984: (SEP, 27),
+ 1985: (SEP, 16),
+ 1986: (SEP, 6),
+ 1987: (AUG, 26),
+ 1988: (AUG, 14),
+ 1989: (AUG, 3),
+ 1990: (JUL, 24),
+ 1991: (JUL, 13),
+ 1992: (JUL, 2),
+ 1993: (JUN, 22),
+ 1994: (JUN, 11),
+ 1995: (MAY, 31),
+ 1996: (MAY, 19),
+ 1997: (MAY, 8),
+ 1998: (APR, 28),
+ 1999: (APR, 18),
+ 2000: (APR, 6),
+ 2001: (MAR, 27),
+ 2002: (MAR, 16),
+ 2003: (MAR, 5),
+ 2004: (FEB, 22),
+ 2005: (FEB, 10),
+ 2006: (JAN, 31),
+ 2007: (JAN, 21),
+ 2008: ((JAN, 10), (DEC, 29)),
+ 2009: (DEC, 18),
+ 2010: (DEC, 7),
+ 2011: (NOV, 27),
+ 2012: (NOV, 15),
+ 2013: (NOV, 5),
+ 2014: (OCT, 26),
+ 2015: (OCT, 15),
+ 2016: (OCT, 3),
+ 2017: (SEP, 22),
+ 2018: (SEP, 11),
+ 2019: (SEP, 1),
+ 2020: (AUG, 21),
+ 2021: (AUG, 10),
+ 2022: (JUL, 30),
+ 2023: (JUL, 19),
+ 2024: (JUL, 7),
+ 2025: (JUN, 27),
+ 2026: (JUN, 17),
+ 2027: (JUN, 6),
+ 2028: (MAY, 26),
+ 2029: (MAY, 15),
+ 2030: (MAY, 4),
+ 2031: (APR, 23),
+ 2032: (APR, 12),
+ 2033: (APR, 1),
+ 2034: (MAR, 22),
+ 2035: (MAR, 12),
+ 2036: (FEB, 29),
+ 2037: (FEB, 17),
+ 2038: (FEB, 6),
+ 2039: (JAN, 26),
+ 2040: (JAN, 16),
+ 2041: ((JAN, 4), (DEC, 25)),
+ 2042: (DEC, 14),
+ 2043: (DEC, 3),
+ 2044: (NOV, 21),
+ 2045: (NOV, 10),
+ 2046: (OCT, 31),
+ 2047: (OCT, 21),
+ 2048: (OCT, 9),
+ 2049: (SEP, 29),
+ 2050: (SEP, 18),
+ 2051: (SEP, 7),
+ }
+
+ ISRA_AND_MIRAJ_DATES = {
+ 1924: (MAR, 4),
+ 1925: (FEB, 21),
+ 1926: (FEB, 11),
+ 1927: (JAN, 31),
+ 1928: (JAN, 21),
+ 1929: ((JAN, 9), (DEC, 29)),
+ 1930: (DEC, 18),
+ 1931: (DEC, 7),
+ 1932: (NOV, 26),
+ 1933: (NOV, 16),
+ 1934: (NOV, 5),
+ 1935: (OCT, 25),
+ 1936: (OCT, 13),
+ 1937: (OCT, 3),
+ 1938: (SEP, 22),
+ 1939: (SEP, 12),
+ 1940: (AUG, 31),
+ 1941: (AUG, 21),
+ 1942: (AUG, 10),
+ 1943: (JUL, 30),
+ 1944: (JUL, 18),
+ 1945: (JUL, 8),
+ 1946: (JUN, 27),
+ 1947: (JUN, 17),
+ 1948: (JUN, 6),
+ 1949: (MAY, 26),
+ 1950: (MAY, 15),
+ 1951: (MAY, 4),
+ 1952: (APR, 22),
+ 1953: (APR, 12),
+ 1954: (APR, 2),
+ 1955: (MAR, 22),
+ 1956: (MAR, 11),
+ 1957: (FEB, 27),
+ 1958: (FEB, 16),
+ 1959: (FEB, 6),
+ 1960: (JAN, 26),
+ 1961: (JAN, 15),
+ 1962: ((JAN, 4), (DEC, 25)),
+ 1963: (DEC, 14),
+ 1964: (DEC, 2),
+ 1965: (NOV, 21),
+ 1966: (NOV, 11),
+ 1967: (OCT, 31),
+ 1968: (OCT, 20),
+ 1969: (OCT, 9),
+ 1970: (SEP, 29),
+ 1971: (SEP, 18),
+ 1972: (SEP, 6),
+ 1973: (AUG, 26),
+ 1974: (AUG, 16),
+ 1975: (AUG, 6),
+ 1976: (JUL, 25),
+ 1977: (JUL, 14),
+ 1978: (JUL, 3),
+ 1979: (JUN, 23),
+ 1980: (JUN, 11),
+ 1981: (JUN, 1),
+ 1982: (MAY, 21),
+ 1983: (MAY, 11),
+ 1984: (APR, 29),
+ 1985: (APR, 18),
+ 1986: (APR, 7),
+ 1987: (MAR, 28),
+ 1988: (MAR, 16),
+ 1989: (MAR, 6),
+ 1990: (FEB, 23),
+ 1991: (FEB, 13),
+ 1992: (FEB, 2),
+ 1993: (JAN, 21),
+ 1994: ((JAN, 10), (DEC, 31)),
+ 1995: (DEC, 20),
+ 1996: (DEC, 9),
+ 1997: (NOV, 28),
+ 1998: (NOV, 17),
+ 1999: (NOV, 6),
+ 2000: (OCT, 25),
+ 2001: (OCT, 15),
+ 2002: (OCT, 5),
+ 2003: (SEP, 24),
+ 2004: (SEP, 13),
+ 2005: (SEP, 2),
+ 2006: (AUG, 22),
+ 2007: (AUG, 11),
+ 2008: (JUL, 31),
+ 2009: (JUL, 20),
+ 2010: (JUL, 10),
+ 2011: (JUN, 30),
+ 2012: (JUN, 18),
+ 2013: (JUN, 7),
+ 2014: (MAY, 27),
+ 2015: (MAY, 16),
+ 2016: (MAY, 5),
+ 2017: (APR, 25),
+ 2018: (APR, 14),
+ 2019: (APR, 3),
+ 2020: (MAR, 22),
+ 2021: (MAR, 11),
+ 2022: (MAR, 1),
+ 2023: (FEB, 18),
+ 2024: (FEB, 8),
+ 2025: (JAN, 27),
+ 2026: (JAN, 17),
+ 2027: ((JAN, 6), (DEC, 26)),
+ 2028: (DEC, 14),
+ 2029: (DEC, 4),
+ 2030: (NOV, 23),
+ 2031: (NOV, 13),
+ 2032: (NOV, 1),
+ 2033: (OCT, 21),
+ 2034: (OCT, 10),
+ 2035: (SEP, 30),
+ 2036: (SEP, 18),
+ 2037: (SEP, 8),
+ 2038: (AUG, 29),
+ 2039: (AUG, 18),
+ 2040: (AUG, 6),
+ 2041: (JUL, 26),
+ 2042: (JUL, 15),
+ 2043: (JUL, 5),
+ 2044: (JUN, 24),
+ 2045: (JUN, 13),
+ 2046: (JUN, 3),
+ 2047: (MAY, 23),
+ 2048: (MAY, 11),
+ 2049: (APR, 30),
+ 2050: (APR, 20),
+ 2051: (APR, 9),
+ 2052: (MAR, 29),
+ }
+
+ MAWLID_DATES = {
+ 1923: (OCT, 23),
+ 1924: (OCT, 11),
+ 1925: (OCT, 1),
+ 1926: (SEP, 20),
+ 1927: (SEP, 9),
+ 1928: (AUG, 28),
+ 1929: (AUG, 18),
+ 1930: (AUG, 7),
+ 1931: (JUL, 28),
+ 1932: (JUL, 16),
+ 1933: (JUL, 6),
+ 1934: (JUN, 25),
+ 1935: (JUN, 14),
+ 1936: (JUN, 2),
+ 1937: (MAY, 23),
+ 1938: (MAY, 13),
+ 1939: (MAY, 2),
+ 1940: (APR, 21),
+ 1941: (APR, 10),
+ 1942: (MAR, 30),
+ 1943: (MAR, 19),
+ 1944: (MAR, 8),
+ 1945: (FEB, 25),
+ 1946: (FEB, 15),
+ 1947: (FEB, 4),
+ 1948: (JAN, 24),
+ 1949: (JAN, 12),
+ 1950: ((JAN, 1), (DEC, 22)),
+ 1951: (DEC, 12),
+ 1952: (NOV, 30),
+ 1953: (NOV, 19),
+ 1954: (NOV, 8),
+ 1955: (OCT, 28),
+ 1956: (OCT, 17),
+ 1957: (OCT, 6),
+ 1958: (SEP, 26),
+ 1959: (SEP, 16),
+ 1960: (SEP, 4),
+ 1961: (AUG, 24),
+ 1962: (AUG, 13),
+ 1963: (AUG, 2),
+ 1964: (JUL, 22),
+ 1965: (JUL, 12),
+ 1966: (JUL, 1),
+ 1967: (JUN, 21),
+ 1968: (JUN, 9),
+ 1969: (MAY, 29),
+ 1970: (MAY, 18),
+ 1971: (MAY, 8),
+ 1972: (APR, 26),
+ 1973: (APR, 16),
+ 1974: (APR, 6),
+ 1975: (MAR, 26),
+ 1976: (MAR, 14),
+ 1977: (MAR, 3),
+ 1978: (FEB, 20),
+ 1979: (FEB, 10),
+ 1980: (JAN, 30),
+ 1981: (JAN, 19),
+ 1982: ((JAN, 8), (DEC, 28)),
+ 1983: (DEC, 17),
+ 1984: (DEC, 6),
+ 1985: (NOV, 25),
+ 1986: (NOV, 15),
+ 1987: (NOV, 4),
+ 1988: (OCT, 24),
+ 1989: (OCT, 13),
+ 1990: (OCT, 2),
+ 1991: (SEP, 21),
+ 1992: (SEP, 10),
+ 1993: (AUG, 30),
+ 1994: (AUG, 20),
+ 1995: (AUG, 9),
+ 1996: (JUL, 28),
+ 1997: (JUL, 17),
+ 1998: (JUL, 7),
+ 1999: (JUN, 26),
+ 2000: (JUN, 15),
+ 2001: (JUN, 5),
+ 2002: (MAY, 25),
+ 2003: (MAY, 14),
+ 2004: (MAY, 2),
+ 2005: (APR, 21),
+ 2006: (APR, 11),
+ 2007: (APR, 1),
+ 2008: (MAR, 20),
+ 2009: (MAR, 10),
+ 2010: (FEB, 27),
+ 2011: (FEB, 16),
+ 2012: (FEB, 5),
+ 2013: (JAN, 24),
+ 2014: (JAN, 14),
+ 2015: ((JAN, 4), (DEC, 24)),
+ 2016: (DEC, 12),
+ 2017: (DEC, 1),
+ 2018: (NOV, 20),
+ 2019: (NOV, 10),
+ 2020: (OCT, 29),
+ 2021: (OCT, 19),
+ 2022: (OCT, 9),
+ 2023: (SEP, 28),
+ 2024: (SEP, 16),
+ 2025: (SEP, 5),
+ 2026: (AUG, 25),
+ 2027: (AUG, 15),
+ 2028: (AUG, 4),
+ 2029: (JUL, 24),
+ 2030: (JUL, 14),
+ 2031: (JUL, 3),
+ 2032: (JUN, 21),
+ 2033: (JUN, 10),
+ 2034: (MAY, 31),
+ 2035: (MAY, 20),
+ 2036: (MAY, 9),
+ 2037: (APR, 28),
+ 2038: (APR, 17),
+ 2039: (APR, 6),
+ 2040: (MAR, 26),
+ 2041: (MAR, 15),
+ 2042: (MAR, 5),
+ 2043: (FEB, 22),
+ 2044: (FEB, 12),
+ 2045: (JAN, 31),
+ 2046: (JAN, 20),
+ 2047: ((JAN, 9), (DEC, 30)),
+ 2048: (DEC, 18),
+ 2049: (DEC, 8),
+ 2050: (NOV, 27),
+ 2051: (NOV, 16),
+ }
+
+ NUZUL_AL_QURAN_DATES = {
+ 1924: (APR, 22),
+ 1925: (APR, 11),
+ 1926: (APR, 1),
+ 1927: (MAR, 21),
+ 1928: (MAR, 10),
+ 1929: (FEB, 27),
+ 1930: (FEB, 16),
+ 1931: (FEB, 5),
+ 1932: (JAN, 25),
+ 1933: (JAN, 14),
+ 1934: ((JAN, 4), (DEC, 24)),
+ 1935: (DEC, 14),
+ 1936: (DEC, 2),
+ 1937: (NOV, 21),
+ 1938: (NOV, 10),
+ 1939: (OCT, 30),
+ 1940: (OCT, 19),
+ 1941: (OCT, 9),
+ 1942: (SEP, 28),
+ 1943: (SEP, 17),
+ 1944: (SEP, 5),
+ 1945: (AUG, 26),
+ 1946: (AUG, 15),
+ 1947: (AUG, 5),
+ 1948: (JUL, 24),
+ 1949: (JUL, 14),
+ 1950: (JUL, 3),
+ 1951: (JUN, 22),
+ 1952: (JUN, 10),
+ 1953: (MAY, 31),
+ 1954: (MAY, 20),
+ 1955: (MAY, 10),
+ 1956: (APR, 29),
+ 1957: (APR, 18),
+ 1958: (APR, 7),
+ 1959: (MAR, 27),
+ 1960: (MAR, 15),
+ 1961: (MAR, 5),
+ 1962: (FEB, 23),
+ 1963: (FEB, 12),
+ 1964: (FEB, 2),
+ 1965: (JAN, 21),
+ 1966: ((JAN, 10), (DEC, 30)),
+ 1967: (DEC, 19),
+ 1968: (DEC, 8),
+ 1969: (NOV, 27),
+ 1970: (NOV, 17),
+ 1971: (NOV, 6),
+ 1972: (OCT, 25),
+ 1973: (OCT, 14),
+ 1974: (OCT, 4),
+ 1975: (SEP, 23),
+ 1976: (SEP, 12),
+ 1977: (SEP, 1),
+ 1978: (AUG, 22),
+ 1979: (AUG, 11),
+ 1980: (JUL, 30),
+ 1981: (JUL, 19),
+ 1982: (JUL, 9),
+ 1983: (JUN, 29),
+ 1984: (JUN, 17),
+ 1985: (JUN, 7),
+ 1986: (MAY, 27),
+ 1987: (MAY, 16),
+ 1988: (MAY, 4),
+ 1989: (APR, 24),
+ 1990: (APR, 13),
+ 1991: (APR, 3),
+ 1992: (MAR, 22),
+ 1993: (MAR, 11),
+ 1994: (FEB, 28),
+ 1995: (FEB, 17),
+ 1996: (FEB, 7),
+ 1997: (JAN, 27),
+ 1998: (JAN, 16),
+ 1999: ((JAN, 6), (DEC, 26)),
+ 2000: (DEC, 14),
+ 2001: (DEC, 3),
+ 2002: (NOV, 22),
+ 2003: (NOV, 12),
+ 2004: (NOV, 1),
+ 2005: (OCT, 21),
+ 2006: (OCT, 10),
+ 2007: (SEP, 29),
+ 2008: (SEP, 17),
+ 2009: (SEP, 7),
+ 2010: (AUG, 28),
+ 2011: (AUG, 17),
+ 2012: (AUG, 6),
+ 2013: (JUL, 26),
+ 2014: (JUL, 15),
+ 2015: (JUL, 4),
+ 2016: (JUN, 23),
+ 2017: (JUN, 12),
+ 2018: (JUN, 2),
+ 2019: (MAY, 22),
+ 2020: (MAY, 11),
+ 2021: (APR, 30),
+ 2022: (APR, 19),
+ 2023: (APR, 8),
+ 2024: (MAR, 28),
+ 2025: (MAR, 18),
+ 2026: (MAR, 7),
+ 2027: (FEB, 24),
+ 2028: (FEB, 13),
+ 2029: (FEB, 1),
+ 2030: (JAN, 22),
+ 2031: (JAN, 11),
+ 2032: ((JAN, 1), (DEC, 20)),
+ 2033: (DEC, 10),
+ 2034: (NOV, 29),
+ 2035: (NOV, 18),
+ 2036: (NOV, 6),
+ 2037: (OCT, 27),
+ 2038: (OCT, 16),
+ 2039: (OCT, 6),
+ 2040: (SEP, 24),
+ 2041: (SEP, 13),
+ 2042: (SEP, 2),
+ 2043: (AUG, 23),
+ 2044: (AUG, 11),
+ 2045: (AUG, 1),
+ 2046: (JUL, 22),
+ 2047: (JUL, 11),
+ 2048: (JUN, 29),
+ 2049: (JUN, 18),
+ 2050: (JUN, 7),
+ 2051: (MAY, 28),
+ 2052: (MAY, 17),
+ }
+
+ RAMADAN_BEGINNING_DATES = {
+ 1924: (APR, 6),
+ 1925: (MAR, 26),
+ 1926: (MAR, 16),
+ 1927: (MAR, 5),
+ 1928: (FEB, 23),
+ 1929: (FEB, 11),
+ 1930: (JAN, 31),
+ 1931: (JAN, 20),
+ 1932: ((JAN, 9), (DEC, 29)),
+ 1933: (DEC, 19),
+ 1934: (DEC, 8),
+ 1935: (NOV, 28),
+ 1936: (NOV, 16),
+ 1937: (NOV, 5),
+ 1938: (OCT, 25),
+ 1939: (OCT, 14),
+ 1940: (OCT, 3),
+ 1941: (SEP, 23),
+ 1942: (SEP, 12),
+ 1943: (SEP, 1),
+ 1944: (AUG, 20),
+ 1945: (AUG, 10),
+ 1946: (JUL, 30),
+ 1947: (JUL, 20),
+ 1948: (JUL, 8),
+ 1949: (JUN, 28),
+ 1950: (JUN, 17),
+ 1951: (JUN, 6),
+ 1952: (MAY, 25),
+ 1953: (MAY, 15),
+ 1954: (MAY, 4),
+ 1955: (APR, 24),
+ 1956: (APR, 13),
+ 1957: (APR, 2),
+ 1958: (MAR, 22),
+ 1959: (MAR, 11),
+ 1960: (FEB, 28),
+ 1961: (FEB, 17),
+ 1962: (FEB, 7),
+ 1963: (JAN, 27),
+ 1964: (JAN, 17),
+ 1965: ((JAN, 5), (DEC, 25)),
+ 1966: (DEC, 14),
+ 1967: (DEC, 3),
+ 1968: (NOV, 22),
+ 1969: (NOV, 11),
+ 1970: (NOV, 1),
+ 1971: (OCT, 21),
+ 1972: (OCT, 9),
+ 1973: (SEP, 28),
+ 1974: (SEP, 18),
+ 1975: (SEP, 7),
+ 1976: (AUG, 27),
+ 1977: (AUG, 16),
+ 1978: (AUG, 6),
+ 1979: (JUL, 26),
+ 1980: (JUL, 14),
+ 1981: (JUL, 3),
+ 1982: (JUN, 23),
+ 1983: (JUN, 13),
+ 1984: (JUN, 1),
+ 1985: (MAY, 22),
+ 1986: (MAY, 11),
+ 1987: (APR, 30),
+ 1988: (APR, 18),
+ 1989: (APR, 8),
+ 1990: (MAR, 28),
+ 1991: (MAR, 18),
+ 1992: (MAR, 6),
+ 1993: (FEB, 23),
+ 1994: (FEB, 12),
+ 1995: (FEB, 1),
+ 1996: (JAN, 22),
+ 1997: ((JAN, 11), (DEC, 31)),
+ 1998: (DEC, 21),
+ 1999: (DEC, 10),
+ 2000: (NOV, 28),
+ 2001: (NOV, 17),
+ 2002: (NOV, 6),
+ 2003: (OCT, 27),
+ 2004: (OCT, 16),
+ 2005: (OCT, 5),
+ 2006: (SEP, 24),
+ 2007: (SEP, 13),
+ 2008: (SEP, 1),
+ 2009: (AUG, 22),
+ 2010: (AUG, 12),
+ 2011: (AUG, 1),
+ 2012: (JUL, 21),
+ 2013: (JUL, 10),
+ 2014: (JUN, 29),
+ 2015: (JUN, 18),
+ 2016: (JUN, 7),
+ 2017: (MAY, 27),
+ 2018: (MAY, 17),
+ 2019: (MAY, 6),
+ 2020: (APR, 25),
+ 2021: (APR, 14),
+ 2022: (APR, 3),
+ 2023: (MAR, 23),
+ 2024: (MAR, 12),
+ 2025: (MAR, 2),
+ 2026: (FEB, 19),
+ 2027: (FEB, 8),
+ 2028: (JAN, 28),
+ 2029: (JAN, 16),
+ 2030: ((JAN, 6), (DEC, 26)),
+ 2031: (DEC, 16),
+ 2032: (DEC, 4),
+ 2033: (NOV, 24),
+ 2034: (NOV, 13),
+ 2035: (NOV, 2),
+ 2036: (OCT, 21),
+ 2037: (OCT, 11),
+ 2038: (SEP, 30),
+ 2039: (SEP, 20),
+ 2040: (SEP, 8),
+ 2041: (AUG, 28),
+ 2042: (AUG, 17),
+ 2043: (AUG, 7),
+ 2044: (JUL, 26),
+ 2045: (JUL, 16),
+ 2046: (JUL, 6),
+ 2047: (JUN, 25),
+ 2048: (JUN, 13),
+ 2049: (JUN, 2),
+ 2050: (MAY, 22),
+ 2051: (MAY, 12),
+ 2052: (MAY, 1),
+ }
+
diff --git a/scripts/calendar/mabims_generator.py b/scripts/calendar/mabims_generator.py
new file mode 100644
index 0000000000..0472384d71
--- /dev/null
+++ b/scripts/calendar/mabims_generator.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+
+# holidays
+# --------
+# A fast, efficient Python library for generating country, province and state
+# specific sets of holidays on the fly. It aims to make determining whether a
+# specific date is a holiday as fast and flexible as possible.
+#
+# Authors: Vacanza Team and individual contributors (see CONTRIBUTORS file)
+# dr-prodigy (c) 2017-2023
+# ryanss (c) 2014-2017
+# Website: https://github.com/vacanza/holidays
+# License: MIT (see LICENSE file)
+
+"""Generate Gregorian dates for Islamic holidays based on MABIMS crescent visibility criteria.
+
+Uses Singapore as the reference location (primary MABIMS Hisab observer).
+MABIMS 2021 criteria: Moon altitude >= 3°, geocentric elongation >= 6.4°.
+
+Run with:
+
+ python -m scripts.calendar.mabims_generator
+
+Alternatively, run with uv:
+
+ uv run -m scripts.calendar.mabims_generator
+
+This generates the file `holidays/calendars/islamic_mabims_dates.py`,
+whose data can then be copied to `holidays/calendars/islamic.py`.
+
+References:
+ *
+ *
+"""
+
+from __future__ import annotations
+
+import math
+from collections import defaultdict
+from datetime import date, timedelta
+from functools import cache
+
+from skyfield import almanac
+from skyfield.api import N, E, load, wgs84
+
+from .generator import CalendarGenerator
+
+# Singapore coordinates (primary MABIMS Hisab reference).
+SINGAPORE_LAT = 1.3521
+SINGAPORE_LON = 103.8198
+SINGAPORE_ELEV = 15.0 # meters
+
+# MABIMS 2021 crescent visibility criteria.
+MIN_ALTITUDE = 3.0 # degrees
+MIN_ELONGATION = 6.4 # degrees
+
+# Hijri calendar constants.
+HIJRI_EPOCH = date(622, 7, 16) # Approximate start of Hijri calendar.
+MEAN_SYNODIC_MONTH = 29.530588853 # days
+
+# Islamic holidays: (hijri_month, hijri_day).
+MABIMS_HOLIDAYS = {
+ "HIJRI_NEW_YEAR": (1, 1),
+ "ISRA_AND_MIRAJ": (7, 27),
+ "RAMADAN_BEGINNING": (9, 1),
+ "NUZUL_AL_QURAN": (9, 17),
+ "EID_AL_FITR": (10, 1),
+ "EID_AL_ADHA": (12, 10),
+ "MAWLID": (3, 12),
+}
+
+
+class _MabimsLunar:
+ def __init__(self) -> None:
+ self.ts = load.timescale()
+ self.eph = load("de421.bsp")
+ self.observer = wgs84.latlon(
+ SINGAPORE_LAT * N, SINGAPORE_LON * E, elevation_m=SINGAPORE_ELEV
+ )
+ self.earth = self.eph["earth"]
+ self.moon = self.eph["moon"]
+ self.sun = self.eph["sun"]
+ self.obs = self.earth + self.observer
+ self.sunset_func = almanac.sunrise_sunset(self.eph, self.observer)
+
+ # Precalculate all new moons for the entire timeframe (1923-2052) chunked by year
+ self.new_moons = []
+ for year in range(1923, 2053):
+ t0 = self.ts.utc(year, 1, 1)
+ t1 = self.ts.utc(year + 1, 1, 1)
+ times, events = almanac.find_discrete(t0, t1, almanac.moon_phases(self.eph))
+ self.new_moons.extend([date(*t.utc[:3]) for t, e in zip(times, events) if e == 0])
+ # Calculate index offset to map months_since_epoch directly to self.new_moons
+ # We use Hijri 1342-01 as our reference point since that's where generation starts.
+ ref_h_year = 1342
+ ref_h_month = 1
+ ref_mse = (ref_h_year - 1) * 12 + (ref_h_month - 1)
+
+ approx_days = int(ref_mse * MEAN_SYNODIC_MONTH)
+ ref_approx = HIJRI_EPOCH + timedelta(days=approx_days)
+
+ ref_idx = min(
+ range(len(self.new_moons)), key=lambda i: abs((self.new_moons[i] - ref_approx).days)
+ )
+ self.month_offset = ref_mse - ref_idx
+
+ def get_new_moon_date(self, h_year: int, h_month: int) -> date:
+ """Get the exact precalculated new moon (conjunction) for a given Hijri month."""
+ months_since_epoch = (h_year - 1) * 12 + (h_month - 1)
+ return self.new_moons[months_since_epoch - self.month_offset]
+
+ def check_mabims_visibility(self, check_date: date) -> bool:
+ """Check if crescent moon meets MABIMS criteria at Singapore sunset."""
+ t0 = self.ts.utc(check_date.year, check_date.month, check_date.day, 9, 0)
+ t1 = self.ts.utc(check_date.year, check_date.month, check_date.day, 12, 0)
+ times, events = almanac.find_discrete(t0, t1, self.sunset_func)
+ t = None
+ for st, e in zip(times, events):
+ if e == 0: # sunset
+ t = st
+ break
+ if t is None:
+ # Fallback to approximate Singapore sunset (~6:50pm SGT = 10:50 UTC).
+ t = self.ts.utc(check_date.year, check_date.month, check_date.day, 10, 50, 0)
+
+ moon_pos = self.obs.at(t).observe(self.moon).apparent()
+ sun_pos = self.obs.at(t).observe(self.sun).apparent()
+
+ moon_alt, _, _ = moon_pos.altaz()
+
+ # Elongation.
+ moon_ra, moon_dec, _ = moon_pos.radec()
+ sun_ra, sun_dec, _ = sun_pos.radec()
+
+ d_ra = (moon_ra.hours - sun_ra.hours) * 15
+ elongation = math.degrees(
+ math.acos(
+ max(
+ -1.0,
+ min(
+ 1.0,
+ math.sin(math.radians(moon_dec.degrees))
+ * math.sin(math.radians(sun_dec.degrees))
+ + math.cos(math.radians(moon_dec.degrees))
+ * math.cos(math.radians(sun_dec.degrees))
+ * math.cos(math.radians(d_ra)),
+ ),
+ )
+ )
+ )
+
+ return moon_alt.degrees >= MIN_ALTITUDE and elongation >= MIN_ELONGATION
+
+ @cache
+ def get_hijri_month_start(self, h_year: int, h_month: int) -> date:
+ """Calculate the Gregorian start date of a Hijri month using MABIMS criteria."""
+ new_moon = self.get_new_moon_date(h_year, h_month)
+
+ # Check visibility on day after new moon, then day after that.
+ for delta in range(1, 4):
+ check_date = new_moon + timedelta(days=delta)
+ if self.check_mabims_visibility(check_date):
+ return check_date + timedelta(days=1)
+
+ # Fallback: assume crescent was visible on first checked day.
+ return new_moon + timedelta(days=1)
+
+
+def generate_data() -> None:
+ """Generate MABIMS Islamic holiday dates."""
+ cal = _MabimsLunar()
+
+ # Start from approximate Hijri new year 1342 (≈1924 CE).
+ h_start = 1342
+ h_end = 1474 # de421.bsp covers up to ~2053 CE
+
+ dates: dict[str, dict[int, list[date]]] = defaultdict(lambda: defaultdict(list))
+
+ print(f"Generating MABIMS dates for Hijri years {h_start}-{h_end}...") # noqa: T201
+
+ for name, (h_month, h_day) in MABIMS_HOLIDAYS.items():
+ for h_year in range(h_start, h_end + 1):
+ if h_year % 10 == 0 and name == "HIJRI_NEW_YEAR":
+ print(f"Processing Hijri year {h_year}...") # noqa: T201
+
+ month_start = cal.get_hijri_month_start(h_year, h_month)
+ holiday_date = month_start + timedelta(days=h_day - 1)
+ dates[name][holiday_date.year].append(holiday_date)
+
+ cal_gen = CalendarGenerator("islamic_mabims", "_IslamicMabimsLunar")
+ cal_gen.generate(dates)
+ print("Done! Generated holidays/calendars/islamic_mabims_dates.py") # noqa: T201
+
+
+if __name__ == "__main__":
+ generate_data()
diff --git a/tests/calendars/test_islamic.py b/tests/calendars/test_islamic.py
new file mode 100644
index 0000000000..15fdd17111
--- /dev/null
+++ b/tests/calendars/test_islamic.py
@@ -0,0 +1,79 @@
+# holidays
+# --------
+# A fast, efficient Python library for generating country, province and state
+# specific sets of holidays on the fly. It aims to make determining whether a
+# specific date is a holiday as fast and flexible as possible.
+#
+# Authors: Vacanza Team and individual contributors (see CONTRIBUTORS file)
+# dr-prodigy (c) 2017-2023
+# ryanss (c) 2014-2017
+# Website: https://github.com/vacanza/holidays
+# License: MIT (see LICENSE file)
+
+import unittest
+from datetime import date
+
+from holidays.calendars.gregorian import FEB, MAR, APR, MAY, JUN, JUL, OCT, NOV, DEC
+from holidays.calendars.islamic import _IslamicMabimsLunar
+
+
+class TestIslamicMabimsLunar(unittest.TestCase):
+ def setUp(self) -> None:
+ super().setUp()
+ self.calendar = _IslamicMabimsLunar()
+
+ def test_eid_al_fitr_dates(self):
+ eid_al_fitr_dates = {
+ 2003: date(2003, NOV, 25),
+ 2006: date(2006, OCT, 24),
+ 2019: date(2019, JUN, 5),
+ 2022: date(2022, MAY, 3),
+ 2023: date(2023, APR, 22),
+ 2024: date(2024, APR, 10),
+ 2025: date(2025, MAR, 31),
+ 2026: date(2026, MAR, 21),
+ }
+ for year, expected_date in eid_al_fitr_dates.items():
+ dates = {dt: est for dt, est in self.calendar.eid_al_fitr_dates(year)}
+ self.assertIn(expected_date, dates)
+ self.assertFalse(dates[expected_date], f"Eid al-Fitr {year} should be confirmed")
+
+ def test_eid_al_adha_dates(self):
+ eid_al_adha_dates = {
+ 2002: date(2002, FEB, 23),
+ 2010: date(2010, NOV, 17),
+ 2022: date(2022, JUL, 10),
+ 2023: date(2023, JUN, 29),
+ 2024: date(2024, JUN, 17),
+ 2025: date(2025, JUN, 7),
+ 2026: date(2026, MAY, 27),
+ }
+ for year, expected_date in eid_al_adha_dates.items():
+ dates = {dt: est for dt, est in self.calendar.eid_al_adha_dates(year)}
+ self.assertIn(expected_date, dates)
+ self.assertFalse(dates[expected_date], f"Eid al-Adha {year} should be confirmed")
+
+ def test_eid_al_adha_2006_dual_date(self):
+ """2006 has two Eid al-Adha dates."""
+ dates = {dt for dt, _ in self.calendar.eid_al_adha_dates(2006)}
+ self.assertIn(date(2006, 12, 31), dates)
+ self.assertTrue(len(dates) >= 2)
+
+ def test_hijri_new_year_dates(self):
+ dates_2025 = {dt for dt, _ in self.calendar.hijri_new_year_dates(2025)}
+ self.assertIn(date(2025, 6, 27), dates_2025)
+
+ def test_coverage_range(self):
+ """Dates from 1925-2052 should be confirmed."""
+ dates_1930 = list(self.calendar.eid_al_fitr_dates(1930))
+ self.assertTrue(len(dates_1930) > 0)
+ for _, is_estimated in dates_1930:
+ self.assertFalse(is_estimated)
+
+ def test_mawlid_dates(self):
+ dates_2025 = {dt for dt, _ in self.calendar.mawlid_dates(2025)}
+ self.assertIn(date(2025, 9, 5), dates_2025)
+
+
+if __name__ == "__main__":
+ unittest.main()