Repository: dr-prodigy/python-holidays

Issue:
Add Burkina Faso holidays
Useful links:
  - https://en.wikipedia.org/wiki/Public_holidays_in_Burkina_Faso
  - https://www.timeanddate.com/holidays/burkina-faso/

Comments:

Patch:
diff --git a/README.rst b/README.rst
index 88119e36..211fceee 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 127 country codes. The standard way to refer to a country
+We currently support 128 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.
@@ -219,6 +219,10 @@ The list of supported countries, their subdivisions and supported languages
      - BG
      -
      - **bg**, en_US
+   * - Burkina Faso
+     - BF
+     -
+     -
    * - Burundi
      - BI
      -
diff --git a/holidays/countries/__init__.py b/holidays/countries/__init__.py
index 65ad53c0..e75e4960 100644
--- a/holidays/countries/__init__.py
+++ b/holidays/countries/__init__.py
@@ -31,6 +31,7 @@ from .botswana import Botswana, BW, BWA
 from .brazil import Brazil, BR, BRA
 from .brunei import Brunei, BN, BRN
 from .bulgaria import Bulgaria, BG, BLG
+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
diff --git a/holidays/countries/burkina_faso.py b/holidays/countries/burkina_faso.py
new file mode 100644
index 00000000..34c1fbc4
--- /dev/null
+++ b/holidays/countries/burkina_faso.py
@@ -0,0 +1,144 @@
+#  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 BurkinaFaso(
+    HolidayBase, ChristianHolidays, InternationalHolidays, IslamicHolidays
+):
+    """
+    References:
+      - https://en.wikipedia.org/wiki/Public_holidays_in_Burkina_Faso
+    """
+
+    country = "BF"
+
+    def __init__(self, *args, **kwargs) -> None:
+        ChristianHolidays.__init__(self)
+        InternationalHolidays.__init__(self)
+        IslamicHolidays.__init__(self, calendar=BurkinaFasoIslamicCalendar())
+        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 5 August 1960, Burkina Faso (Republic of Upper Volta at that time)
+        # 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"))
+
+        if year >= 1967:
+            # Revolution Day.
+            self._add_observed(self._add_holiday("Revolution Day", JAN, 3))
+
+        # 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"))
+
+        # Ascension Day.
+        self._add_ascension_thursday("Ascension Day")
+
+        # Independence Day.
+        self._add_observed(self._add_holiday("Independence Day", AUG, 5))
+
+        # Assumption Day.
+        self._add_observed(self._add_assumption_of_mary_day("Assumption Day"))
+
+        if year >= 2016:
+            # Martyrs' Day.
+            self._add_observed(self._add_holiday("Martyrs' Day", OCT, 31))
+
+        # All Saints' Day.
+        self._add_observed(self._add_all_saints_day("All Saints' Day"))
+
+        self._add_observed(
+            # Proclamation of Independence Day.
+            self._add_holiday("Proclamation of Independence Day", DEC, 11)
+        )
+
+        # Christmas Day.
+        self._add_observed(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 BF(BurkinaFaso):
+    pass
+
+
+class BFA(BurkinaFaso):
+    pass
+
+
+class BurkinaFasoIslamicCalendar(_CustomIslamicCalendar):
+    EID_AL_ADHA_DATES = {
+        2014: ((OCT, 5),),
+        2015: ((SEP, 24),),
+        2016: ((SEP, 13),),
+        2017: ((SEP, 2),),
+        2018: ((AUG, 21),),
+        2019: ((AUG, 11),),
+        2020: ((JUL, 31),),
+        2021: ((JUL, 20),),
+        2022: ((JUL, 9),),
+    }
+
+    EID_AL_FITR_DATES = {
+        2014: ((JUL, 29),),
+        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 = {
+        2014: ((JAN, 14),),
+        2015: ((JAN, 3), (DEC, 24)),
+        2016: ((DEC, 12),),
+        2017: ((DEC, 1),),
+        2018: ((NOV, 21),),
+        2019: ((NOV, 10),),
+        2020: ((OCT, 29),),
+        2021: ((OCT, 19),),
+        2022: ((OCT, 9),),
+    }
diff --git a/holidays/registry.py b/holidays/registry.py
index 241b8fa0..698be8e0 100644
--- a/holidays/registry.py
+++ b/holidays/registry.py
@@ -39,6 +39,7 @@ COUNTRIES: RegistryDict = {
     "brazil": ("Brazil", "BR", "BRA"),
     "brunei": ("Brunei", "BN", "BRN"),
     "bulgaria": ("Bulgaria", "BG", "BLG"),
+    "burkina_faso": ("BurkinaFaso", "BF", "BFA"),
     "burundi": ("Burundi", "BI", "BDI"),
     "cameroon": ("Cameroon", "CM", "CMR"),
     "canada": ("Canada", "CA", "CAN"),


Test patch:
diff --git a/tests/countries/test_burkina_faso.py b/tests/countries/test_burkina_faso.py
new file mode 100644
index 00000000..4758e192
--- /dev/null
+++ b/tests/countries/test_burkina_faso.py
@@ -0,0 +1,101 @@
+#  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.burkina_faso import BurkinaFaso, BF, BFA
+from tests.common import TestCase
+
+
+class TestBurkinaFaso(TestCase):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass(BurkinaFaso)
+
+    def test_country_aliases(self):
+        self.assertCountryAliases(BurkinaFaso, BF, BFA)
+
+    def test_no_holidays(self):
+        self.assertNoHolidays(BurkinaFaso(years=1960))
+
+    def test_revolution_day(self):
+        name = "Revolution Day"
+        self.assertHolidaysName(
+            name, (f"{year}-01-03" for year in range(1967, 2050))
+        )
+        self.assertNoHolidayName(name, BurkinaFaso(years=range(1961, 1967)))
+        self.assertNoHoliday(f"{year}-01-03" for year in range(1961, 1967))
+
+    def test_martyrs_day(self):
+        name = "Martyrs' Day"
+        self.assertHolidaysName(
+            name, (f"{year}-10-31" for year in range(2016, 2050))
+        )
+        self.assertNoHolidayName(name, BurkinaFaso(years=range(1961, 2016)))
+        self.assertNoHoliday(
+            f"{year}-10-31"
+            for year in set(range(1961, 2016)).difference({1979})
+        )
+
+    def test_observed(self):
+        dt = (
+            # New Year's Day
+            "2012-01-02",
+            "2017-01-02",
+            "2023-01-02",
+            # Revolution Day
+            "2010-01-04",
+            "2016-01-04",
+            "2021-01-04",
+            # International Women's Day
+            "2015-03-09",
+            "2020-03-09",
+            # Labour Day
+            "2011-05-02",
+            "2016-05-02",
+            # Independence Day
+            "2012-08-06",
+            "2018-08-06",
+            # Assumption Day
+            "2010-08-16",
+            "2021-08-16",
+            # All Saints' Day
+            "2015-11-02",
+            "2020-11-02",
+            # Proclamation of Independence Day
+            "2011-12-12",
+            "2022-12-12",
+            # Christmas Day
+            "2011-12-26",
+            "2016-12-26",
+            "2022-12-26",
+        )
+        self.assertHoliday(dt)
+        self.assertNoNonObservedHoliday(dt)
+
+    def test_2022(self):
+        self.assertHolidays(
+            ("2022-01-01", "New Year's Day"),
+            ("2022-01-03", "Revolution 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-05-26", "Ascension Day"),
+            ("2022-07-09", "Eid al-Adha"),
+            ("2022-08-05", "Independence Day"),
+            ("2022-08-15", "Assumption Day"),
+            ("2022-10-09", "Mawlid"),
+            ("2022-10-31", "Martyrs' Day"),
+            ("2022-11-01", "All Saints' Day"),
+            ("2022-12-11", "Proclamation of Independence Day"),
+            ("2022-12-12", "Proclamation of Independence Day (Observed)"),
+            ("2022-12-25", "Christmas Day"),
+            ("2022-12-26", "Christmas Day (Observed)"),
+        )