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()