Repository: vacanza/holidays (python)

Issue:
Add Qatar holidays
Links:
  - https://en.wikipedia.org/wiki/Public_holidays_in_Qatar
  - https://www.timeanddate.com/holidays/qatar/

Pull request description:
Add Qatar holidays
<!--
  Thanks for contributing to holidays!
-->

## Add Qatar Holidays

<!--
  Describe the big picture of your changes.
  Don't forget to link your PR to an existing issue if any.
-->

I've added Qatar holidays. It included Bank Holidays too. I've added `l10n` support in ar_QA and en_US as well.
Closes #1253.

## Type of change

<!--
  Type of change you want to introduce. Please, check one (1) box only!
  If your PR requires multiple boxes to be checked, most likely it needs to
  be split into multiple PRs.
-->

- [x] New country/market holidays support (thank you!)
- [ ] Supported country/market holidays update (calendar discrepancy fix, localization)
- [ ] Existing code/documentation/test/process quality improvement (best practice, cleanup, refactoring, optimization)
- [ ] Dependency update (version deprecation/pin/upgrade)
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] Breaking change (a code change causing existing functionality to break)
- [ ] New feature (new `holidays` functionality in general)

## Checklist

<!--
  Put an `x` in the boxes that apply. You can change them after PR is created.
-->

- [x] I've followed the [contributing guidelines][contributing-guidelines]
- [x] I've successfully run `make check`, all checks and tests are green

<!--
  Thanks again for your contribution!
-->

[contributing-guidelines]: https://github.com/vacanza/holidays/blob/dev/CONTRIBUTING.md
[docs]: https://github.com/vacanza/holidays/tree/dev/docs/source


Patch:
diff --git a/README.md b/README.md
index f88ed33a..5cd84a37 100644
--- a/README.md
+++ b/README.md
@@ -99,7 +99,7 @@ and detailed information.
 
 ## Available Countries
 
-We currently support 164 country codes. The standard way to refer to a country is by using its [ISO
+We currently support 165 country codes. The standard way to refer to a country is by using its [ISO
 3166-1 alpha-2 code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes), the same used
 for domain names, and for a subdivision its [ISO 3166-2
 code](https://en.wikipedia.org/wiki/ISO_3166-2). Some countries have common or foreign names or
@@ -1010,6 +1010,13 @@ any) in brackets, available languages and additional holiday categories. All cou
 <td>GOVERNMENT, UNOFFICIAL</td>
 </tr>
 <tr>
+<td>Qatar</td>
+<td>QA</td>
+<td></td>
+<td><strong>ar_QA</strong>, en_US</td>
+<td>BANK</td>
+</tr>
+<tr>
 <td>Romania</td>
 <td>RO</td>
 <td></td>
diff --git a/holidays/countries/__init__.py b/holidays/countries/__init__.py
index 7eaa0c6f..1bc15baf 100644
--- a/holidays/countries/__init__.py
+++ b/holidays/countries/__init__.py
@@ -136,6 +136,7 @@ from .philippines import Philippines, PH, PHL
 from .poland import Poland, PL, POL
 from .portugal import Portugal, PT, PRT
 from .puerto_rico import PuertoRico, PR, PRI, HolidaysPR
+from .qatar import Qatar, QA, QAT
 from .romania import Romania, RO, ROU
 from .russia import Russia, RU, RUS
 from .saint_kitts_and_nevis import SaintKittsAndNevis, KN, KNA
diff --git a/holidays/countries/qatar.py b/holidays/countries/qatar.py
new file mode 100644
index 00000000..8cbfdba3
--- /dev/null
+++ b/holidays/countries/qatar.py
@@ -0,0 +1,172 @@
+#  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 AUTHORS.md 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)
+
+from gettext import gettext as tr
+
+from holidays.calendars import _CustomIslamicHolidays
+from holidays.calendars.gregorian import (
+    JAN,
+    MAR,
+    APR,
+    MAY,
+    JUN,
+    JUL,
+    AUG,
+    SEP,
+    OCT,
+    NOV,
+    DEC,
+    THU,
+    FRI,
+    SAT,
+)
+from holidays.constants import BANK, PUBLIC
+from holidays.groups import InternationalHolidays, IslamicHolidays, StaticHolidays
+from holidays.holiday_base import HolidayBase
+
+
+class Qatar(HolidayBase, InternationalHolidays, IslamicHolidays, StaticHolidays):
+    """Qatar holidays.
+
+    References:
+        * <https://en.wikipedia.org/wiki/Public_holidays_in_Qatar>
+        * [National Sports Day](https://hukoomi.gov.qa/en/national-sport-day)
+        * [Qatar National Day](https://www.qatar.qa/en/qatar/history-of-qatar-qatar-national-day-committee/)
+        * [Weekend](https://www.arabnews.com/node/234601)
+    """
+
+    country = "QA"
+    default_language = "ar_QA"
+    # %s (estimated).
+    estimated_label = tr("%s (المقدرة)")
+    start_year = 1971
+    supported_categories = (BANK, PUBLIC)
+    supported_languages = ("ar_QA", "en_US")
+
+    def __init__(self, islamic_show_estimated: bool = True, *args, **kwargs):
+        """
+        Args:
+            islamic_show_estimated:
+                Whether to add "estimated" label to Islamic holidays name
+                if holiday date is estimated.
+        """
+        InternationalHolidays.__init__(self)
+        IslamicHolidays.__init__(
+            self, cls=QatarIslamicHolidays, show_estimated=islamic_show_estimated
+        )
+        StaticHolidays.__init__(self, QatarStaticHolidays)
+        super().__init__(*args, **kwargs)
+
+    def _populate_public_holidays(self):
+        # Qatar switches from THU-FRI to FRI-SAT on Aug 1, 2003.
+        self.weekend = {THU, FRI} if self._year <= 2003 else {FRI, SAT}
+
+        if self._year >= 2012:
+            # National Sports Day.
+            self._add_holiday_2nd_tue_of_feb(tr("اليوم الوطني للرياضة"))
+
+        if self._year >= 2007:
+            # Qatar National Day.
+            self._add_holiday_dec_18(tr("اليوم الوطني لقطر"))
+
+        # Eid al-Fitr.
+        name = tr("عيد الفطر")
+        self._add_eid_al_fitr_day(name)
+        self._add_eid_al_fitr_day_two(name)
+        self._add_eid_al_fitr_day_three(name)
+
+        # Eid al-Adha.
+        name = tr("عيد الأضحى")
+        self._add_eid_al_adha_day(name)
+        self._add_eid_al_adha_day_two(name)
+        self._add_eid_al_adha_day_three(name)
+
+    def _populate_bank_holidays(self):
+        # New Year's Day.
+        self._add_new_years_day(tr("رأس السنة الميلادية"))
+
+        if self._year >= 2010:
+            # March Bank Holiday.
+            self._add_holiday_1st_sun_of_mar(tr("عطلة البنك"))
+
+
+class QA(Qatar):
+    pass
+
+
+class QAT(Qatar):
+    pass
+
+
+class QatarIslamicHolidays(_CustomIslamicHolidays):
+    # https://www.timeanddate.com/holidays/qatar/eid-al-adha
+    EID_AL_ADHA_DATES = {
+        2005: (JAN, 21),
+        2006: ((JAN, 10), (DEC, 31)),
+        2007: (DEC, 20),
+        2008: (DEC, 9),
+        2009: (NOV, 28),
+        2010: (NOV, 15),
+        2011: (NOV, 6),
+        2012: (OCT, 26),
+        2013: (OCT, 15),
+        2014: (OCT, 4),
+        2015: (SEP, 23),
+        2016: (SEP, 10),
+        2017: (AUG, 31),
+        2018: (AUG, 22),
+        2019: (AUG, 11),
+        2020: (JUL, 31),
+        2021: (JUL, 20),
+        2022: (JUL, 9),
+        2023: (JUN, 28),
+        2024: (JUN, 16),
+    }
+
+    # https://www.timeanddate.com/holidays/qatar/eid-al-fitr
+    EID_AL_FITR_DATES = {
+        2005: (NOV, 4),
+        2006: (OCT, 24),
+        2007: (OCT, 13),
+        2008: (OCT, 2),
+        2009: (SEP, 21),
+        2010: (SEP, 10),
+        2011: (AUG, 31),
+        2012: (AUG, 19),
+        2013: (AUG, 8),
+        2014: (JUL, 28),
+        2015: (JUL, 18),
+        2016: (JUL, 6),
+        2017: (JUN, 25),
+        2018: (JUN, 15),
+        2019: (JUN, 4),
+        2020: (MAY, 24),
+        2021: (MAY, 13),
+        2022: (MAY, 2),
+        2023: (APR, 21),
+        2024: (APR, 10),
+        2025: (MAR, 30),
+    }
+
+
+class QatarStaticHolidays:
+    """Qatar special holidays.
+
+    References:
+        * [New Year's Holiday](https://www.expatica.com/qa/lifestyle/holidays/qatar-public-holidays-74585/)
+    """
+
+    # New Year's Holiday.
+    name = tr("عطلة رأس السنة")
+    special_public_holidays = {
+        2025: (JAN, 2, name),
+    }
diff --git a/holidays/locale/ar_QA/LC_MESSAGES/QA.po b/holidays/locale/ar_QA/LC_MESSAGES/QA.po
new file mode 100644
index 00000000..01645221
--- /dev/null
+++ b/holidays/locale/ar_QA/LC_MESSAGES/QA.po
@@ -0,0 +1,60 @@
+#  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 AUTHORS.md 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)
+#
+# Qatar holidays.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Holidays 0.70\n"
+"POT-Creation-Date: 2025-04-03 09:28+0500\n"
+"PO-Revision-Date: 2025-04-03 09:28+0500\n"
+"Last-Translator: Wasif Shahzad <wasif_shahzad_1@outlook.com>\n"
+"Language-Team: Holidays Localization Team\n"
+"Language: ar_QA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingva 5.0.5\n"
+"X-Source-Language: ar_QA\n"
+
+#. %s (estimated).
+#, c-format
+msgid "%s (المقدرة)"
+msgstr ""
+
+#. National Sports Day.
+msgid "اليوم الوطني للرياضة"
+msgstr ""
+
+#. Qatar National Day.
+msgid "اليوم الوطني لقطر"
+msgstr ""
+
+#. Eid al-Fitr.
+msgid "عيد الفطر"
+msgstr ""
+
+#. Eid al-Adha.
+msgid "عيد الأضحى"
+msgstr ""
+
+#. New Year's Day.
+msgid "رأس السنة الميلادية"
+msgstr ""
+
+#. New Year's Holiday.
+msgid "عطلة رأس السنة"
+msgstr ""
+
+#. March Bank Holiday.
+msgid "عطلة البنك"
+msgstr ""
diff --git a/holidays/locale/en_US/LC_MESSAGES/QA.po b/holidays/locale/en_US/LC_MESSAGES/QA.po
new file mode 100644
index 00000000..dae2d883
--- /dev/null
+++ b/holidays/locale/en_US/LC_MESSAGES/QA.po
@@ -0,0 +1,61 @@
+#  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 AUTHORS.md 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)
+#
+# Qatar holidays en_US localization.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Holidays 0.70\n"
+"POT-Creation-Date: 2025-04-03 09:48+0500\n"
+"PO-Revision-Date: 2025-04-03 10:42+0500\n"
+"Last-Translator: Wasif Shahzad <wasif_shahzad_1@outlook.com>\n"
+"Language-Team: Holidays Localization Team\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingva 5.0.5\n"
+"X-Generator: Poedit 3.5\n"
+"X-Source-Language: ar_QA\n"
+
+#. %s (estimated).
+#, c-format
+msgid "%s (المقدرة)"
+msgstr "%s (estimated)"
+
+#. National Sports Day.
+msgid "اليوم الوطني للرياضة"
+msgstr "National Sports Day"
+
+#. Qatar National Day.
+msgid "اليوم الوطني لقطر"
+msgstr "Qatar National Day"
+
+#. Eid al-Fitr.
+msgid "عيد الفطر"
+msgstr "Eid al-Fitr"
+
+#. Eid al-Adha.
+msgid "عيد الأضحى"
+msgstr "Eid al-Adha"
+
+#. New Year's Day.
+msgid "رأس السنة الميلادية"
+msgstr "New Year's Day"
+
+#. New Year's Holiday.
+msgid "عطلة رأس السنة"
+msgstr "New Year's Holiday"
+
+#. March Bank Holiday.
+msgid "عطلة البنك"
+msgstr "March Bank Holiday"
diff --git a/holidays/registry.py b/holidays/registry.py
index b3edc55e..538f49d3 100644
--- a/holidays/registry.py
+++ b/holidays/registry.py
@@ -144,6 +144,7 @@ COUNTRIES: RegistryDict = {
     "poland": ("Poland", "PL", "POL"),
     "portugal": ("Portugal", "PT", "PRT"),
     "puerto_rico": ("PuertoRico", "PR", "PRI", "HolidaysPR"),
+    "qatar": ("Qatar", "QA", "QAT"),
     "romania": ("Romania", "RO", "ROU"),
     "russia": ("Russia", "RU", "RUS"),
     "saint_kitts_and_nevis": ("SaintKittsAndNevis", "KN", "KNA"),


Test patch:
diff --git a/tests/countries/test_qatar.py b/tests/countries/test_qatar.py
new file mode 100644
index 00000000..674a7b6e
--- /dev/null
+++ b/tests/countries/test_qatar.py
@@ -0,0 +1,209 @@
+#  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 AUTHORS.md 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)
+
+from unittest import TestCase
+
+from holidays.constants import BANK
+from holidays.countries.qatar import Qatar, QA, QAT
+from tests.common import CommonCountryTests
+
+
+class TestQatar(CommonCountryTests, TestCase):
+    @classmethod
+    def setUpClass(cls):
+        years = range(1971, 2050)
+        super().setUpClass(Qatar, years=years)
+        cls.no_estimated_holidays = Qatar(years=years, islamic_show_estimated=False)
+        cls.bank_holidays = Qatar(categories=BANK, years=years)
+
+    def test_country_aliases(self):
+        self.assertAliases(Qatar, QA, QAT)
+
+    def test_no_holidays(self):
+        self.assertNoHolidays(Qatar(years=1970))
+        self.assertNoHolidays(Qatar(categories=BANK, years=1970))
+
+    def test_special_holidays(self):
+        self.assertHoliday(
+            "2025-01-02",
+        )
+
+    def test_sports_day(self):
+        name = "اليوم الوطني للرياضة"
+        dts = (
+            "2012-02-14",
+            "2013-02-12",
+            "2014-02-11",
+            "2015-02-10",
+            "2016-02-09",
+            "2017-02-14",
+            "2018-02-13",
+            "2019-02-12",
+            "2020-02-11",
+            "2021-02-09",
+            "2022-02-08",
+            "2023-02-14",
+            "2024-02-13",
+            "2025-02-11",
+            "2026-02-10",
+            "2027-02-09",
+            "2028-02-08",
+            "2029-02-13",
+            "2030-02-12",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2012, 2050))
+        self.assertNoHolidayName(name, range(1971, 2011))
+
+    def test_national_day(self):
+        name = "اليوم الوطني لقطر"
+        self.assertHolidayName(name, (f"{year}-12-18" for year in range(2007, 2050)))
+        self.assertNoHolidayName(name, range(1971, 2007))
+
+    def test_eid_al_fitr(self):
+        name = "عيد الفطر"
+        dts = (
+            "2018-06-15",
+            "2018-06-16",
+            "2018-06-17",
+            "2019-06-04",
+            "2019-06-05",
+            "2019-06-06",
+            "2020-05-24",
+            "2020-05-25",
+            "2020-05-26",
+            "2021-05-13",
+            "2021-05-14",
+            "2021-05-15",
+            "2022-05-02",
+            "2022-05-03",
+            "2022-05-04",
+            "2023-04-21",
+            "2023-04-22",
+            "2023-04-23",
+            "2024-04-10",
+            "2024-04-11",
+            "2024-04-12",
+            "2025-03-30",
+            "2025-03-31",
+            "2025-04-01",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, self.no_estimated_holidays, range(1971, 2050))
+
+    def test_eid_al_adha(self):
+        name = "عيد الأضحى"
+        dts = (
+            "2018-08-22",
+            "2018-08-23",
+            "2018-08-24",
+            "2019-08-11",
+            "2019-08-12",
+            "2019-08-13",
+            "2020-07-31",
+            "2020-08-01",
+            "2020-08-02",
+            "2021-07-20",
+            "2021-07-21",
+            "2021-07-22",
+            "2022-07-09",
+            "2022-07-10",
+            "2022-07-11",
+            "2023-06-28",
+            "2023-06-29",
+            "2023-06-30",
+            "2024-06-16",
+            "2024-06-17",
+            "2024-06-18",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, self.no_estimated_holidays, range(1971, 2050))
+
+    def test_new_years_day(self):
+        name = "رأس السنة الميلادية"
+        self.assertHolidayName(
+            name, self.bank_holidays, (f"{year}-01-01" for year in range(1971, 2050))
+        )
+        self.assertNoHolidayName(name)
+
+    def test_march_bank_holiday(self):
+        name = "عطلة البنك"
+        dts = (
+            "2010-03-07",
+            "2011-03-06",
+            "2012-03-04",
+            "2013-03-03",
+            "2014-03-02",
+            "2015-03-01",
+            "2016-03-06",
+            "2017-03-05",
+            "2018-03-04",
+            "2019-03-03",
+            "2020-03-01",
+            "2021-03-07",
+            "2022-03-06",
+            "2023-03-05",
+            "2024-03-03",
+            "2025-03-02",
+        )
+        self.assertHolidayName(name, self.bank_holidays, dts)
+        self.assertHolidayName(name, self.bank_holidays, range(2010, 2050))
+        self.assertNoHolidayName(name, self.bank_holidays, range(1971, 2010))
+        self.assertNoHolidayName(name)
+
+    def test_2011(self):
+        self.assertHolidays(
+            Qatar(years=2011),
+            ("2011-08-31", "عيد الفطر"),
+            ("2011-09-01", "عيد الفطر"),
+            ("2011-09-02", "عيد الفطر"),
+            ("2011-11-06", "عيد الأضحى"),
+            ("2011-11-07", "عيد الأضحى"),
+            ("2011-11-08", "عيد الأضحى"),
+            ("2011-12-18", "اليوم الوطني لقطر"),
+        )
+
+    def test_bank_2024(self):
+        self.assertHolidays(
+            Qatar(categories=BANK, years=2024),
+            ("2024-01-01", "رأس السنة الميلادية"),
+            ("2024-03-03", "عطلة البنك"),
+        )
+
+    def test_l10n_default(self):
+        self.assertLocalizedHolidays(
+            ("2012-01-01", "رأس السنة الميلادية"),
+            ("2012-02-14", "اليوم الوطني للرياضة"),
+            ("2012-03-04", "عطلة البنك"),
+            ("2012-08-19", "عيد الفطر"),
+            ("2012-08-20", "عيد الفطر"),
+            ("2012-08-21", "عيد الفطر"),
+            ("2012-10-26", "عيد الأضحى"),
+            ("2012-10-27", "عيد الأضحى"),
+            ("2012-10-28", "عيد الأضحى"),
+            ("2012-12-18", "اليوم الوطني لقطر"),
+        )
+
+    def test_l10n_en_us(self):
+        self.assertLocalizedHolidays(
+            "en_US",
+            ("2012-01-01", "New Year's Day"),
+            ("2012-02-14", "National Sports Day"),
+            ("2012-03-04", "March Bank Holiday"),
+            ("2012-08-19", "Eid al-Fitr"),
+            ("2012-08-20", "Eid al-Fitr"),
+            ("2012-08-21", "Eid al-Fitr"),
+            ("2012-10-26", "Eid al-Adha"),
+            ("2012-10-27", "Eid al-Adha"),
+            ("2012-10-28", "Eid al-Adha"),
+            ("2012-12-18", "Qatar National Day"),
+        )