+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions holidays/calendars/ethiopian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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 <dr.prodigy.github@gmail.com> (c) 2017-2023
# ryanss <ryanssdev@icloud.com> (c) 2014-2017
# Website: https://github.com/vacanza/holidays
# License: MIT (see LICENSE file)

ETHIOPIAN_CALENDAR = "ETHIOPIAN_CALENDAR"


def is_ethiopian_leap_year(year: int) -> bool:
"""Determine if the Ethiopian year that starts in the given Gregorian year is a leap year.

Ethiopian leap years follow the Coptic/Julian rule:
* Every 4 years without exception (no century rule).
* The Ethiopian year starts on September 11 (or 12 in an Ethiopian leap year).

Args:
year:
Gregorian year to evaluate.

Returns:
`True` if an Ethiopian leap year starts in the given Gregorian year, `False` otherwise.
"""

return (year + 1) % 4 == 0
14 changes: 14 additions & 0 deletions holidays/calendars/julian.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@
# License: MIT (see LICENSE file)

JULIAN_CALENDAR = "JULIAN_CALENDAR"


def julian_calendar_drift(year: int) -> int:
"""Return the Julian–Gregorian date drift relative to the 1899–2099 baseline.

Args:
year:
Gregorian year to check.

Returns:
Number of days to add/subtract relative to the 1899–2099 baseline.
"""

return -13 if year <= 1582 else (year // 100) - (year // 400) - 15
45 changes: 17 additions & 28 deletions holidays/countries/ethiopia.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
# Website: https://github.com/vacanza/holidays
# License: MIT (see LICENSE file)

from calendar import isleap
from gettext import gettext as tr

from holidays.calendars import _CustomIslamicHolidays
from holidays.calendars.gregorian import JAN, AUG, SEP, NOV
from holidays.calendars.julian import JULIAN_CALENDAR
from holidays.calendars.ethiopian import ETHIOPIAN_CALENDAR, is_ethiopian_leap_year
from holidays.calendars.gregorian import AUG, SEP, NOV
from holidays.constants import PUBLIC, WORKDAY
from holidays.groups import ChristianHolidays, InternationalHolidays, IslamicHolidays
from holidays.holiday_base import HolidayBase
Expand All @@ -30,7 +29,7 @@ class Ethiopia(HolidayBase, ChristianHolidays, InternationalHolidays, IslamicHol
* <https://web.archive.org/web/20250427173714/https://www.edarabia.com/ethiopia/public-holidays/>
* <https://en.wikipedia.org/wiki/Public_holidays_in_Ethiopia>
* <https://en.wikipedia.org/wiki/Nations,_Nationalities_and_Peoples'_Day>
* <https://web.archive.org/web/20250408213218/https://www.timeanddate.com/holidays/ethiopia/>
* <https://web.archive.org/web/20250811041201/https://www.timeanddate.com/holidays/ethiopia/2025>
"""

country = "ET"
Expand All @@ -42,30 +41,14 @@ class Ethiopia(HolidayBase, ChristianHolidays, InternationalHolidays, IslamicHol
supported_categories = (PUBLIC, WORKDAY)
supported_languages = ("am", "ar", "en_ET", "en_US")

def _is_leap_year(self) -> bool:
"""Determine if the Ethiopian calendar year is a leap year.

Ethiopian leap years generally align with Gregorian leap years until
February 2100. However, the Ethiopian calendar starts earlier (on September 11),
which affects holidays between September 11 and January 1.

To account for this shift, the method checks whether next year is a leap year
in the Gregorian calendar.

Returns:
`True` if the Ethiopian year is a leap year, `False` otherwise.
"""

return isleap(self._year + 1)

def __init__(self, *args, islamic_show_estimated: bool = True, **kwargs):
"""
Args:
islamic_show_estimated:
Whether to add "estimated" label to Islamic holidays name
if holiday date is estimated.
"""
ChristianHolidays.__init__(self, JULIAN_CALENDAR)
ChristianHolidays.__init__(self, ETHIOPIAN_CALENDAR)
InternationalHolidays.__init__(self)
IslamicHolidays.__init__(
self, cls=EthiopiaIslamicHolidays, show_estimated=islamic_show_estimated
Expand All @@ -77,7 +60,7 @@ def _populate_public_holidays(self):
self._add_christmas_day(tr("የገና ወይም የልደት በዓል"))

# Epiphany.
self._add_holiday(tr("የጥምቀት በዓል"), JAN, 20 if super()._is_leap_year() else 19)
self._add_epiphany_day(tr("የጥምቀት በዓል"))

if self._year >= 1996:
# Adwa Victory Day.
Expand All @@ -100,14 +83,19 @@ def _populate_public_holidays(self):
self._add_holiday_may_28(tr("ደርግ የወደቀበት ቀን"))

# Ethiopian New Year.
self._add_holiday(tr("የዘመን መለወጫ (እንቁጣጣሽ) በዓል"), SEP, 12 if self._is_leap_year() else 11)
self._add_ethiopian_new_year(tr("የዘመን መለወጫ (እንቁጣጣሽ) በዓል"))

# Finding of True Cross.
self._add_holiday(tr("የመስቀል በዓል"), SEP, 28 if self._is_leap_year() else 27)
self._add_finding_of_true_cross(tr("የመስቀል በዓል"))

if self._year <= 1990:
# Popular Revolution Commemoration Day.
self._add_holiday(tr("የአብዮት ቀን"), SEP, 13 if self._is_leap_year() else 12)
# Julian Date Drift shouldn't affect this one.
self._add_holiday(
# Popular Revolution Commemoration Day.
tr("የአብዮት ቀን"),
SEP,
13 if is_ethiopian_leap_year(self._year) else 12,
)

# October Revolution Day.
self._add_holiday_nov_7(tr("የጥቅምት አብዮት ቀን"))
Expand Down Expand Up @@ -139,15 +127,16 @@ class ETH(Ethiopia):


class EthiopiaIslamicHolidays(_CustomIslamicHolidays):
EID_AL_ADHA_DATES_CONFIRMED_YEARS = (2018, 2024)
EID_AL_ADHA_DATES_CONFIRMED_YEARS = (2018, 2025)
EID_AL_ADHA_DATES = {
2018: (AUG, 22),
}

EID_AL_FITR_DATES_CONFIRMED_YEARS = (2018, 2025)

MAWLID_DATES_CONFIRMED_YEARS = (2018, 2024)
MAWLID_DATES_CONFIRMED_YEARS = (2018, 2025)
MAWLID_DATES = {
2018: (NOV, 21),
2019: (NOV, 10),
2025: (SEP, 5),
}
60 changes: 48 additions & 12 deletions holidays/groups/christian.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

from dateutil.easter import EASTER_ORTHODOX, EASTER_WESTERN, easter

from holidays.calendars.gregorian import GREGORIAN_CALENDAR, JAN, DEC, _timedelta
from holidays.calendars.julian import JULIAN_CALENDAR
from holidays.calendars.ethiopian import ETHIOPIAN_CALENDAR, is_ethiopian_leap_year
from holidays.calendars.gregorian import GREGORIAN_CALENDAR, JAN, AUG, SEP, DEC, _timedelta
from holidays.calendars.julian import JULIAN_CALENDAR, julian_calendar_drift
from holidays.calendars.julian_revised import JULIAN_REVISED_CALENDAR


Expand All @@ -36,8 +37,8 @@ def __get_christmas_day(self, calendar=None):
self.__verify_calendar(calendar)

return (
date(self._year, JAN, 7)
if self.__is_julian_calendar(calendar)
_timedelta(date(self._year, JAN, 7), julian_calendar_drift(self._year - 1))
if self.__is_julian_calendar(calendar) or self.__is_ethiopian_calendar(calendar)
else date(self._year, DEC, 25)
)

Expand All @@ -53,6 +54,14 @@ def __get_easter_sunday(self, calendar=None):
method=EASTER_WESTERN if self.__is_gregorian_calendar(calendar) else EASTER_ORTHODOX,
)

@staticmethod
def __is_ethiopian_calendar(calendar):
"""
Return True if `calendar` is Ethiopian calendar.
Return False otherwise.
"""
return calendar == ETHIOPIAN_CALENDAR

@staticmethod
def __is_gregorian_calendar(calendar):
"""
Expand All @@ -74,10 +83,15 @@ def __verify_calendar(calendar):
"""
Verify calendar type.
"""
if calendar not in {GREGORIAN_CALENDAR, JULIAN_CALENDAR, JULIAN_REVISED_CALENDAR}:
if calendar not in {
ETHIOPIAN_CALENDAR,
GREGORIAN_CALENDAR,
JULIAN_CALENDAR,
JULIAN_REVISED_CALENDAR,
}:
raise ValueError(
f"Unknown calendar name: {calendar}. "
f"Use `{GREGORIAN_CALENDAR}`, `{JULIAN_CALENDAR}` or `{JULIAN_REVISED_CALENDAR}`."
f"Unknown calendar name: {calendar}. Use `{ETHIOPIAN_CALENDAR}`, "
f"`{GREGORIAN_CALENDAR}`, `{JULIAN_CALENDAR}` or `{JULIAN_REVISED_CALENDAR}`."
)

@property
Expand Down Expand Up @@ -158,7 +172,9 @@ def _add_assumption_of_mary_day(self, name, calendar=None) -> date:
self.__verify_calendar(calendar)

return (
self._add_holiday_aug_28(name)
self._add_holiday(
name, _timedelta(date(self._year, AUG, 28), julian_calendar_drift(self._year))
)
if self.__is_julian_calendar(calendar)
else self._add_holiday_aug_15(name)
)
Expand Down Expand Up @@ -299,10 +315,30 @@ def _add_epiphany_day(self, name, calendar=None) -> date:
calendar = calendar or self.__calendar
self.__verify_calendar(calendar)

return (
self._add_holiday_jan_19(name)
if self.__is_julian_calendar(calendar)
else self._add_holiday_jan_6(name)
if self.__is_julian_calendar(calendar) or self.__is_ethiopian_calendar(calendar):
dt = _timedelta(date(self._year, JAN, 19), julian_calendar_drift(self._year - 1))
return self._add_holiday(
name,
_timedelta(dt, +1)
if self.__is_ethiopian_calendar(calendar)
and is_ethiopian_leap_year(self._year - 1)
else dt,
)
else:
return self._add_holiday_jan_6(name)

def _add_finding_of_true_cross(self, name) -> date:
"""
Add Finding of True Cross.

Finding of True Cross, also known as Meskel, is an Ethiopian and Eritrean Orthodox
Tewahedo Church holiday that commemorates the discovery of the True Cross by the
Roman Empress Saint Helena of Constantinople in the fourth century.
https://en.wikipedia.org/wiki/Meskel
"""
dt = _timedelta(date(self._year, SEP, 27), julian_calendar_drift(self._year))
return self._add_holiday(
name, _timedelta(dt, +1) if is_ethiopian_leap_year(self._year) else dt
)

def _add_good_friday(self, name, calendar=None) -> date:
Expand Down
18 changes: 17 additions & 1 deletion holidays/groups/international.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

from datetime import date

from holidays.calendars.gregorian import JAN
from holidays.calendars.ethiopian import is_ethiopian_leap_year
from holidays.calendars.gregorian import JAN, SEP, _timedelta
from holidays.calendars.julian import julian_calendar_drift


class InternationalHolidays:
Expand Down Expand Up @@ -85,6 +87,20 @@ def _add_columbus_day(self, name):
"""
return self._add_holiday_oct_12(name)

def _add_ethiopian_new_year(self, name) -> date:
"""
Add Ethiopian New Year.

Ethiopian New Year, also known as Enkutatash, is a public holiday celebrated
on Meskerem 1 in the Ethiopian calendar, marking the start of the year in
Ethiopia and Eritrea.
https://en.wikipedia.org/wiki/Enkutatash
"""
dt = _timedelta(date(self._year, SEP, 11), julian_calendar_drift(self._year))
return self._add_holiday(
name, _timedelta(dt, +1) if is_ethiopian_leap_year(self._year) else dt
)

def _add_europe_day(self, name):
"""
Add Europe Day (May 9th)
Expand Down
4 changes: 2 additions & 2 deletions snapshots/countries/ET_COMMON.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,8 @@
"2025-05-01": "International Workers' Day",
"2025-05-05": "Ethiopian Patriots' Victory Day",
"2025-05-28": "Downfall of the Dergue Regime Day",
"2025-06-06": "Eid al-Adha (estimated)",
"2025-09-04": "Prophet's Birthday (estimated)",
"2025-06-06": "Eid al-Adha",
"2025-09-05": "Prophet's Birthday",
"2025-09-11": "Ethiopian New Year",
"2025-09-27": "Finding of True Cross",
"2025-12-09": "Nations, Nationalities and Peoples Day",
Expand Down
37 changes: 37 additions & 0 deletions tests/calendars/test_ethiopian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 <dr.prodigy.github@gmail.com> (c) 2017-2023
# ryanss <ryanssdev@icloud.com> (c) 2014-2017
# Website: https://github.com/vacanza/holidays
# License: MIT (see LICENSE file)

import unittest

from holidays.calendars.ethiopian import is_ethiopian_leap_year


class TestEthiopianCalendar(unittest.TestCase):
def test_is_ethiopian_leap_year(self):
known_ethiopian_leap_years = {
# Known Cases.
2022: False,
2023: True,
2024: False,
2025: False,
# Future Cases.
2098: False,
2099: True,
2100: False,
2101: False,
2198: False,
2199: True,
2200: False,
2201: False,
}
for year in known_ethiopian_leap_years:
self.assertEqual(known_ethiopian_leap_years[year], is_ethiopian_leap_year(year))
36 changes: 36 additions & 0 deletions tests/calendars/test_julian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 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 <dr.prodigy.github@gmail.com> (c) 2017-2023
# ryanss <ryanssdev@icloud.com> (c) 2014-2017
# Website: https://github.com/vacanza/holidays
# License: MIT (see LICENSE file)

import unittest

from holidays.calendars.julian import julian_calendar_drift


class TestJulianCalendar(unittest.TestCase):
def test_julian_calendar_drift(self):
known_julian_calendar_drift = {
1400: -13,
1500: -13,
1581: -13,
1582: -13,
1583: -3,
1600: -3,
1700: -2,
1800: -1,
1900: 0,
2000: 0,
2025: 0,
2100: +1,
2200: +2,
}
for year in known_julian_calendar_drift:
self.assertEqual(known_julian_calendar_drift[year], julian_calendar_drift(year))
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载