Repository: dr-prodigy/python-holidays

Issue:
Add Chad holidays
Useful links:
  - https://en.wikipedia.org/wiki/Public_holidays_in_Chad
  - https://www.timeanddate.com/holidays/chad/

Comments:

Patch:
diff --git a/README.rst b/README.rst
index 211fceee..c7cae973 100644
--- a/README.rst
+++ b/README.rst
@@ -108,7 +108,7 @@ Available Countries
 .. _ISO 3166-2 code: https://en.wikipedia.org/wiki/ISO_3166-2
 .. _ISO 639-1 code: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
 
-We currently support 128 country codes. The standard way to refer to a country
+We currently support 129 country codes. The standard way to refer to a country
 is by using its `ISO 3166-1 alpha-2 code`_, the same used for domain names, and
 for a subdivision its `ISO 3166-2 code`_. Some of the countries support more
 than one language for holiday names output.
@@ -235,6 +235,10 @@ The list of supported countries, their subdivisions and supported languages
      - CA
      - Provinces and territories: AB, BC, MB, NB, NL, NS, NT, NU, **ON**, PE, QC, SK, YT
      - ar, **en**, en_US, fr, th
+   * - Chad
+     - TD
+     -
+     -
    * - Chile
      - CL
      - Regions: AI, AN, AP, AR, AT, BI, CO, LI, LL, LR, MA, ML, NB, RM, TA, VS
diff --git a/holidays/countries/__init__.py b/holidays/countries/__init__.py
index e75e4960..aa860a45 100644
--- a/holidays/countries/__init__.py
+++ b/holidays/countries/__init__.py
@@ -35,6 +35,7 @@ from .burkina_faso import BurkinaFaso, BF, BFA
 from .burundi import Burundi, BI, BDI
 from .cameroon import Cameroon, CM, CMR
 from .canada import Canada, CA, CAN
+from .chad import Chad, TD, TCD
 from .chile import Chile, CL, CHL
 from .china import China, CN, CHN
 from .colombia import Colombia, CO, COL
diff --git a/holidays/countries/chad.py b/holidays/countries/chad.py
new file mode 100644
index 00000000..ffb0576d
--- /dev/null
+++ b/holidays/countries/chad.py
@@ -0,0 +1,136 @@
+#  python-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: dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
+#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
+#  Website: https://github.com/dr-prodigy/python-holidays
+#  License: MIT (see LICENSE file)
+
+from datetime import date
+from datetime import timedelta as td
+
+from holidays.calendars import _CustomIslamicCalendar
+from holidays.constants import JAN, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
+from holidays.holiday_base import HolidayBase
+from holidays.holiday_groups import ChristianHolidays, InternationalHolidays
+from holidays.holiday_groups import IslamicHolidays
+
+
+class Chad(
+    HolidayBase, ChristianHolidays, InternationalHolidays, IslamicHolidays
+):
+    """
+    References:
+      - https://en.wikipedia.org/wiki/Public_holidays_in_Chad
+      - https://www.ilo.org/dyn/natlex/docs/ELECTRONIC/97323/115433/F-316075167/TCD-97323.pdf  # noqa: E501
+    """
+
+    country = "TD"
+    special_holidays = {
+        2021: ((APR, 23, "Funeral of Idriss Déby Itno"),),
+    }
+
+    def __init__(self, *args, **kwargs) -> None:
+        ChristianHolidays.__init__(self)
+        InternationalHolidays.__init__(self)
+        IslamicHolidays.__init__(self, calendar=ChadIslamicCalendar())
+        super().__init__(*args, **kwargs)
+
+    def _add_observed(self, dt: date) -> None:
+        if self.observed and self._is_sunday(dt):
+            self._add_holiday("%s (Observed)" % self[dt], dt + td(days=+1))
+
+    def _populate(self, year):
+        # On 11 August 1960, Chad gained independence from France.
+        if year <= 1960:
+            return None
+
+        super()._populate(year)
+
+        # New Year's Day.
+        self._add_observed(self._add_new_years_day("New Year's Day"))
+
+        # International Women's Day.
+        self._add_observed(self._add_womens_day("International Women's Day"))
+
+        # Easter Monday.
+        self._add_easter_monday("Easter Monday")
+
+        # Labour Day.
+        self._add_observed(self._add_labor_day("Labour Day"))
+
+        # Independence Day.
+        self._add_observed(self._add_holiday("Independence Day", AUG, 11))
+
+        # All Saints' Day.
+        self._add_all_saints_day("All Saints' Day")
+
+        self._add_observed(
+            # Republic Day.
+            self._add_holiday("Republic Day", NOV, 28)
+        )
+
+        if year >= 1991:
+            self._add_observed(
+                # Freedom and Democracy Day.
+                self._add_holiday("Freedom and Democracy Day", DEC, 1)
+            )
+
+        # Christmas Day.
+        self._add_christmas_day("Christmas Day")
+
+        # Eid al-Fitr.
+        self._add_eid_al_fitr_day("Eid al-Fitr")
+
+        # Eid al-Adha.
+        self._add_eid_al_adha_day("Eid al-Adha")
+
+        # Mawlid.
+        self._add_mawlid_day("Mawlid")
+
+
+class TD(Chad):
+    pass
+
+
+class TCD(Chad):
+    pass
+
+
+class ChadIslamicCalendar(_CustomIslamicCalendar):
+    EID_AL_ADHA_DATES = {
+        2015: ((SEP, 24),),
+        2016: ((SEP, 13),),
+        2017: ((SEP, 2),),
+        2018: ((AUG, 22),),
+        2019: ((AUG, 11),),
+        2020: ((JUL, 31),),
+        2021: ((JUL, 20),),
+        2022: ((JUL, 9),),
+    }
+
+    EID_AL_FITR_DATES = {
+        2015: ((JUL, 18),),
+        2016: ((JUL, 7),),
+        2017: ((JUN, 26),),
+        2018: ((JUN, 15),),
+        2019: ((JUN, 4),),
+        2020: ((MAY, 24),),
+        2021: ((MAY, 13),),
+        2022: ((MAY, 2),),
+        2023: ((APR, 21),),
+    }
+
+    MAWLID_DATES = {
+        2015: ((JAN, 3), (DEC, 24)),
+        2016: ((DEC, 12),),
+        2017: ((DEC, 1),),
+        2018: ((NOV, 21),),
+        2019: ((NOV, 9),),
+        2020: ((OCT, 29),),
+        2021: ((OCT, 18),),
+        2022: ((OCT, 8),),
+    }
diff --git a/holidays/registry.py b/holidays/registry.py
index 698be8e0..3b6324f1 100644
--- a/holidays/registry.py
+++ b/holidays/registry.py
@@ -43,6 +43,7 @@ COUNTRIES: RegistryDict = {
     "burundi": ("Burundi", "BI", "BDI"),
     "cameroon": ("Cameroon", "CM", "CMR"),
     "canada": ("Canada", "CA", "CAN"),
+    "chad": ("Chad", "TD", "TCD"),
     "chile": ("Chile", "CL", "CHL"),
     "china": ("China", "CN", "CHN"),
     "colombia": ("Colombia", "CO", "COL"),


Test patch:
diff --git a/tests/countries/test_chad.py b/tests/countries/test_chad.py
new file mode 100644
index 00000000..3d61d8c2
--- /dev/null
+++ b/tests/countries/test_chad.py
@@ -0,0 +1,80 @@
+#  python-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: dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
+#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
+#  Website: https://github.com/dr-prodigy/python-holidays
+#  License: MIT (see LICENSE file)
+
+from holidays.countries.chad import Chad, TD, TCD
+from tests.common import TestCase
+
+
+class TestChad(TestCase):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass(Chad)
+
+    def test_country_aliases(self):
+        self.assertCountryAliases(Chad, TD, TCD)
+
+    def test_no_holidays(self):
+        self.assertNoHolidays(Chad(years=1960))
+
+    def test_special_holidays(self):
+        self.assertHoliday("2021-04-23")
+
+    def test_freedom_and_democracy_day(self):
+        name = "Freedom and Democracy Day"
+        self.assertHolidaysName(
+            name, (f"{year}-12-01" for year in range(1991, 2050))
+        )
+        self.assertNoHolidayName(name, Chad(years=range(1961, 1991)))
+        self.assertNoHoliday(
+            f"{year}-12-01"
+            for year in set(range(1961, 1991)).difference({1976})
+        )
+
+    def test_observed(self):
+        dt = (
+            # New Year's Day
+            "2012-01-02",
+            "2017-01-02",
+            "2023-01-02",
+            # International Women's Day
+            "2015-03-09",
+            "2020-03-09",
+            # Labour Day
+            "2011-05-02",
+            "2016-05-02",
+            # Independence Day
+            "2013-08-12",
+            "2019-08-12",
+            # Republic Day
+            "2010-11-29",
+            "2021-11-29",
+            # Freedom and Democracy Day
+            "2013-12-02",
+            "2019-12-02",
+        )
+        self.assertHoliday(dt)
+        self.assertNoNonObservedHoliday(dt)
+
+    def test_2022(self):
+        self.assertHolidays(
+            ("2022-01-01", "New Year's Day"),
+            ("2022-03-08", "International Women's Day"),
+            ("2022-04-18", "Easter Monday"),
+            ("2022-05-01", "Labour Day"),
+            ("2022-05-02", "Eid al-Fitr; Labour Day (Observed)"),
+            ("2022-07-09", "Eid al-Adha"),
+            ("2022-08-11", "Independence Day"),
+            ("2022-10-08", "Mawlid"),
+            ("2022-11-01", "All Saints' Day"),
+            ("2022-11-28", "Republic Day"),
+            ("2022-12-01", "Freedom and Democracy Day"),
+            ("2022-12-25", "Christmas Day"),
+        )