Repository: vacanza/holidays (python)

Issue:
Add Montserrat holidays
URLs:
- https://en.wikipedia.org/wiki/Public_holidays_in_Montserrat

Pull request:
Add Montserrat holidays
<!--
  Thanks for contributing to holidays!
-->

## Proposed change

<!--
  Describe the big picture of your changes.
  Don't forget to link your PR to an existing issue if any.
-->
Resolves #2665.

Add Montserrat holidays. Holidays start earlier than 2017 but the first official documentation available is of 2017 so I used 2017 as `start_year`. 

There are static holidays in 2015/16 (one each year) as per official sources but there is no source for public holidays before 2017 so I didn't know what to do hence, I skipped it at this moment of time. I'll add the link to those static holidays in half an hour to the PR description.

[Static holiday 2016]( https://share.google/ititQu48MbOH9PnsL)
Update: The static holiday in 2015 is actually December 31st so I suppose it was added in 2016 or 2017 which again, we don't have enough information of...

## 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 e86cf1bd..f551d39b 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,7 @@ and detailed information.
 
 ## Available Countries
 
-We currently support 212 country codes. The standard way to refer to a country is by using its [ISO
+We currently support 213 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
@@ -1043,6 +1043,13 @@ any) in brackets, available languages and additional holiday categories. All cou
 <td>CATHOLIC, HEBREW, ISLAMIC, ORTHODOX, WORKDAY</td>
 </tr>
 <tr>
+<td>Montserrat</td>
+<td>MS</td>
+<td></td>
+<td><strong>en_MS</strong>, en_US</td>
+<td></td>
+</tr>
+<tr>
 <td>Morocco</td>
 <td>MA</td>
 <td></td>
diff --git a/holidays/countries/__init__.py b/holidays/countries/__init__.py
index 277ce5eb..ccd24969 100644
--- a/holidays/countries/__init__.py
+++ b/holidays/countries/__init__.py
@@ -145,6 +145,7 @@ from holidays.countries.moldova import Moldova, MD, MDA
 from holidays.countries.monaco import Monaco, MC, MCO
 from holidays.countries.mongolia import Mongolia, MN, MNG
 from holidays.countries.montenegro import Montenegro, ME, MNE
+from holidays.countries.montserrat import Montserrat, MS, MSR
 from holidays.countries.morocco import Morocco, MA, MOR
 from holidays.countries.mozambique import Mozambique, MZ, MOZ
 from holidays.countries.namibia import Namibia, NA, NAM
diff --git a/holidays/countries/montserrat.py b/holidays/countries/montserrat.py
new file mode 100644
index 00000000..ddc2be1a
--- /dev/null
+++ b/holidays/countries/montserrat.py
@@ -0,0 +1,177 @@
+#  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)
+
+from gettext import gettext as tr
+
+from holidays.calendars.gregorian import APR, MAY, JUN, JUL, SEP, DEC
+from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
+from holidays.observed_holiday_base import (
+    ObservedHolidayBase,
+    MON_TO_NEXT_TUE,
+    SAT_TO_NEXT_MON,
+    SAT_SUN_TO_NEXT_MON,
+    SAT_SUN_TO_NEXT_MON_TUE,
+)
+
+
+class Montserrat(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, StaticHolidays):
+    """Montserrat holidays.
+
+    References:
+        * <https://en.wikipedia.org/wiki/Public_holidays_in_Montserrat>
+        * [Public holidays Act, 2017](https://web.archive.org/web/20240619074948/http://agc.gov.ms/wp-content/uploads/2011/10/Act-No.-3-of-2017-Public-Holidays-Amendment-Act-20171.pdf)
+        * [Public holidays Act, 2019](https://web.archive.org/web/20241025070627/https://www.gov.ms/wp-content/uploads/2020/06/Public-Holidays-Act.pdf)
+        * [National Day of Prayer and Thanksgiving, 2021](https://web.archive.org/web/20220809144840/https://www.gov.ms/2021/07/16/wednesday-july-21-2021-public-holiday-national-day-of-prayer-thanksgiving/)
+        * [National Day of Prayer and Thanksgiving, 2022](https://web.archive.org/web/20220816124619/https://www.gov.ms/2022/07/19/public-holiday-for-national-day-of-prayer-and-thanksgiving-to-be-held-on-july-20-2022/)
+        * [National Day of Prayer and Thanksgiving](https://web.archive.org/web/20240711221313/https://www.gov.ms/wp-content/uploads/2023/05/SRO-No.-15-of-2023-Public-Holidays-Amendment-of-Schedule-Order.pdf)
+        * [Queen's Platinum Jubilee, 2022](https://web.archive.org/web/20250711160439/https://www.gov.ms/tag/public-holidays/)
+        * [Montserrat Public holidays, 2022](https://web.archive.org/web/20220809030551/https://www.gov.ms/wp-content/uploads/2021/12/Public-Holidays-2022-1.jpeg)
+        * [Montserrat Public holidays, 2023](https://web.archive.org/web/20241126232715/https://www.gov.ms/wp-content/uploads/2023/02/Public-Holidays-Montserrat-2023_page-0001.jpg)
+        * [Montserrat Public holidays, 2024](https://web.archive.org/web/20240421112540/https://www.gov.ms/wp-content/uploads/2023/12/Public-Holidays-Montserrat-2024.docx.pdf)
+        * [King's Birthday, 2024](https://web.archive.org/web/20250711202228/https://parliament.ms/wp-content/uploads/2024/09/SRO-No.-18-of-2024-PROCOLAMATION-APPOINTING-MONDAY-17-JUNE-2024-AS-A-PUBLIC-HOLIDAY.pdf)
+        * [Montserrat Public holidays, 2025](https://web.archive.org/web/20250711160324/https://www.gov.ms/wp-content/uploads/2025/01/2025-Public-Holidays-on-Montserrat.pdf)
+    """
+
+    country = "MS"
+    default_language = "en_MS"
+    # %s (observed).
+    observed_label = "%s (observed)"
+    # Public holidays Act, 2017.
+    start_year = 2017
+    supported_languages = ("en_MS", "en_US")
+
+    def __init__(self, *args, **kwargs):
+        ChristianHolidays.__init__(self)
+        InternationalHolidays.__init__(self)
+        StaticHolidays.__init__(self, MontserratStaticHolidays)
+        kwargs.setdefault("observed_rule", SAT_SUN_TO_NEXT_MON)
+        super().__init__(*args, **kwargs)
+
+    def _populate_public_holidays(self):
+        self._add_observed(
+            # New Year's Day.
+            self._add_new_years_day(tr("New Year's Day")),
+            rule=SAT_SUN_TO_NEXT_MON_TUE + MON_TO_NEXT_TUE,
+        )
+
+        # Saint Patrick's Day.
+        self._add_observed(self._add_holiday_mar_17(tr("Saint Patrick's Day")))
+
+        # Good Friday.
+        self._add_good_friday(tr("Good Friday"))
+
+        # Easter Monday.
+        self._add_easter_monday(tr("Easter Monday"))
+
+        # Labor Day.
+        self._add_holiday_1st_mon_of_may(tr("Labour Day"))
+
+        name = (
+            # King's Birthday.
+            tr("King's Birthday")
+            if self._year >= 2023
+            # Queen's Birthday.
+            else tr("Queen's Birthday")
+        )
+        sovereign_birthday_dts = {
+            2022: (JUN, 2),
+            2023: (JUN, 19),
+            2024: (JUN, 17),
+        }
+        sovereign_birthday_dt = (
+            self._add_holiday(name, dt)
+            if (dt := sovereign_birthday_dts.get(self._year))
+            else self._add_holiday_2_days_past_2nd_sat_of_jun(name)
+        )
+
+        # Whit Monday.
+        name = tr("Whit Monday")
+        whit_monday_dt = self._add_whit_monday(name)
+        if whit_monday_dt == sovereign_birthday_dt:
+            self._add_observed(whit_monday_dt, name=name, rule=MON_TO_NEXT_TUE)
+
+        if self._year >= 2021:
+            day_of_prayer_dts = {
+                2021: (JUL, 21),
+                2022: (JUL, 20),
+            }
+            name = (
+                # National Day of Prayer and Thanksgiving.
+                tr("National Day of Prayer and Thanksgiving")
+                if self._year <= 2023
+                # Day of Prayer and Thanksgiving.
+                else tr("Day of Prayer and Thanksgiving")
+            )
+            if dt := day_of_prayer_dts.get(self._year):
+                self._add_holiday(name, dt)
+            else:
+                self._add_holiday_2nd_wed_of_jul(name)
+
+        # Emancipation Day.
+        self._add_holiday_1st_mon_of_aug(tr("Emancipation Day"))
+
+        self._add_observed(
+            # Christmas Day.
+            self._add_christmas_day(tr("Christmas Day")),
+            rule=SAT_SUN_TO_NEXT_MON_TUE,
+        )
+
+        self._add_observed(
+            # Boxing Day.
+            self._add_christmas_day_two(tr("Boxing Day")),
+            rule=SAT_SUN_TO_NEXT_MON_TUE,
+        )
+
+        # Festival Day.
+        name = tr("Festival Day")
+        self._add_new_years_eve(name)
+        self._add_observed((self._year - 1, DEC, 31), name, rule=SAT_TO_NEXT_MON)
+
+
+class MS(Montserrat):
+    pass
+
+
+class MSR(Montserrat):
+    pass
+
+
+class MontserratStaticHolidays(StaticHolidays):
+    """Montserrat special holidays.
+
+    References:
+        * [September 14th, 2018](https://web.archive.org/web/20220810224300/https://www.gov.ms/wp-content/uploads/2020/11/SRO.-No.-35-of-2018-Proclamation-Declaring-Friday-14-September-2018-as-a-public-holiday.pdf)
+        * [May 10th, 2019](https://web.archive.org/web/20240626113529/https://parliament.ms/wp-content/uploads/2022/02/SRO-No-13-of-2019-Proclamation-Declaring-Friday-10-May-2019-as-a-publi._.pdf)
+        * [July 15th, 2020](https://web.archive.org/web/20220810225456/https://www.gov.ms/wp-content/uploads/2020/08/SRO.-No.-40-of-2020-Proclamation-Declaring-Wednesday-15-July-2020-as-a-Public-Holiday.pdf)
+        * [National Day of Mourning](https://web.archive.org/web/20240617072858/https://www.parliament.ms/wp-content/uploads/2022/09/SRO-No.43-of-2022-Proclamation-Appointing-Monday-19-September-2022-a-Public-Holiday.pdf)
+        * [Coronation of King Charles III](https://web.archive.org/web/20241126232715/https://www.gov.ms/wp-content/uploads/2023/02/Public-Holidays-Montserrat-2023_page-0001.jpg)
+    """
+
+    # Special Public Holiday.
+    name = tr("Special Public Holiday")
+
+    special_public_holidays = {
+        2018: (SEP, 14, name),
+        2019: (MAY, 10, name),
+        2020: (JUL, 15, name),
+        2022: (
+            # Platinum Jubilee of Elizabeth II.
+            (JUN, 3, tr("Platinum Jubilee of Elizabeth II")),
+            # National Day of Mourning.
+            (SEP, 19, tr("National Day of Mourning")),
+        ),
+        2023: (
+            (APR, 12, name),
+            # Coronation of King Charles III.
+            (MAY, 6, tr("Coronation of King Charles III")),
+        ),
+    }
diff --git a/holidays/holiday_base.py b/holidays/holiday_base.py
index c9463bb8..0449b6b4 100644
--- a/holidays/holiday_base.py
+++ b/holidays/holiday_base.py
@@ -44,7 +44,7 @@ from holidays.constants import HOLIDAY_NAME_DELIMITER, PUBLIC, DEFAULT_START_YEA
 from holidays.helpers import _normalize_arguments, _normalize_tuple
 
 CategoryArg = Union[str, Iterable[str]]
-DateArg = Union[date, tuple[int, int]]
+DateArg = Union[date, tuple[int, int], tuple[int, int, int]]
 DateLike = Union[date, datetime, str, float, int]
 SpecialHoliday = Union[tuple[int, int, str], tuple[tuple[int, int, str], ...]]
 SubstitutedHoliday = Union[
diff --git a/holidays/locale/en_MS/LC_MESSAGES/MS.po b/holidays/locale/en_MS/LC_MESSAGES/MS.po
new file mode 100644
index 00000000..0f378064
--- /dev/null
+++ b/holidays/locale/en_MS/LC_MESSAGES/MS.po
@@ -0,0 +1,99 @@
+#  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)
+#
+# Montserrat holidays.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Holidays 0.77\n"
+"POT-Creation-Date: 2025-07-11 06:02+0500\n"
+"PO-Revision-Date: 2025-07-11 06:02+0500\n"
+"Last-Translator: Wasif Shahzad <wasif_shahzad_1@outlook.com>\n"
+"Language-Team: Holidays Localization Team\n"
+"Language: en_MS\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingva 5.0.6\n"
+"X-Source-Language: en_MS\n"
+
+#. New Year's Day.
+msgid "New Year's Day"
+msgstr ""
+
+#. Saint Patrick's Day.
+msgid "Saint Patrick's Day"
+msgstr ""
+
+#. Good Friday.
+msgid "Good Friday"
+msgstr ""
+
+#. Easter Monday.
+msgid "Easter Monday"
+msgstr ""
+
+#. Labor Day.
+msgid "Labour Day"
+msgstr ""
+
+#. King's Birthday.
+msgid "King's Birthday"
+msgstr ""
+
+#. Queen's Birthday.
+msgid "Queen's Birthday"
+msgstr ""
+
+#. Whit Monday.
+msgid "Whit Monday"
+msgstr ""
+
+#. National Day of Prayer and Thanksgiving.
+msgid "National Day of Prayer and Thanksgiving"
+msgstr ""
+
+#. Day of Prayer and Thanksgiving.
+msgid "Day of Prayer and Thanksgiving"
+msgstr ""
+
+#. Emancipation Day.
+msgid "Emancipation Day"
+msgstr ""
+
+#. Christmas Day.
+msgid "Christmas Day"
+msgstr ""
+
+#. Boxing Day.
+msgid "Boxing Day"
+msgstr ""
+
+#. Festival Day.
+msgid "Festival Day"
+msgstr ""
+
+#. Special Public Holiday.
+msgid "Special Public Holiday"
+msgstr ""
+
+#. Platinum Jubilee of Elizabeth II.
+msgid "Platinum Jubilee of Elizabeth II"
+msgstr ""
+
+#. National Day of Mourning.
+msgid "National Day of Mourning"
+msgstr ""
+
+#. Coronation of King Charles III.
+msgid "Coronation of King Charles III"
+msgstr ""
diff --git a/holidays/locale/en_US/LC_MESSAGES/MS.po b/holidays/locale/en_US/LC_MESSAGES/MS.po
new file mode 100644
index 00000000..7df1f8bc
--- /dev/null
+++ b/holidays/locale/en_US/LC_MESSAGES/MS.po
@@ -0,0 +1,100 @@
+#  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)
+#
+# Montserrat holidays en_US localization.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Holidays 0.77\n"
+"POT-Creation-Date: 2025-07-11 06:02+0500\n"
+"PO-Revision-Date: 2025-07-11 06:07+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.6\n"
+"X-Generator: Poedit 3.6\n"
+"X-Source-Language: en_MS\n"
+
+#. New Year's Day.
+msgid "New Year's Day"
+msgstr "New Year's Day"
+
+#. Saint Patrick's Day.
+msgid "Saint Patrick's Day"
+msgstr "Saint Patrick's Day"
+
+#. Good Friday.
+msgid "Good Friday"
+msgstr "Good Friday"
+
+#. Easter Monday.
+msgid "Easter Monday"
+msgstr "Easter Monday"
+
+#. Labor Day.
+msgid "Labour Day"
+msgstr "Labor Day"
+
+#. King's Birthday.
+msgid "King's Birthday"
+msgstr "King's Birthday"
+
+#. Queen's Birthday.
+msgid "Queen's Birthday"
+msgstr "Queen's Birthday"
+
+#. Whit Monday.
+msgid "Whit Monday"
+msgstr "Whit Monday"
+
+#. National Day of Prayer and Thanksgiving.
+msgid "National Day of Prayer and Thanksgiving"
+msgstr "National Day of Prayer and Thanksgiving"
+
+#. Day of Prayer and Thanksgiving.
+msgid "Day of Prayer and Thanksgiving"
+msgstr "Day of Prayer and Thanksgiving"
+
+#. Emancipation Day.
+msgid "Emancipation Day"
+msgstr "Emancipation Day"
+
+#. Christmas Day.
+msgid "Christmas Day"
+msgstr "Christmas Day"
+
+#. Boxing Day.
+msgid "Boxing Day"
+msgstr "Boxing Day"
+
+#. Festival Day.
+msgid "Festival Day"
+msgstr "Festival Day"
+
+#. Special Public Holiday.
+msgid "Special Public Holiday"
+msgstr "Special Public Holiday"
+
+#. Platinum Jubilee of Elizabeth II.
+msgid "Platinum Jubilee of Elizabeth II"
+msgstr "Platinum Jubilee of Elizabeth II"
+
+#. National Day of Mourning.
+msgid "National Day of Mourning"
+msgstr "National Day of Mourning"
+
+#. Coronation of King Charles III.
+msgid "Coronation of King Charles III"
+msgstr "Coronation of King Charles III"
diff --git a/holidays/observed_holiday_base.py b/holidays/observed_holiday_base.py
index 1ad4a215..b4b76c7d 100644
--- a/holidays/observed_holiday_base.py
+++ b/holidays/observed_holiday_base.py
@@ -151,7 +151,9 @@ class ObservedHolidayBase(HolidayBase):
         if dt is None:
             return False, None
 
-        dt = dt if isinstance(dt, date) else date(self._year, *dt)
+        # Use as is if already a date.
+        # Convert to date: (m, d) → use self._year; (y, m, d) → use directly.
+        dt = dt if isinstance(dt, date) else date(self._year, *dt) if len(dt) == 2 else date(*dt)
 
         if not self.observed or not self._is_observed(dt):
             return False, dt
diff --git a/holidays/registry.py b/holidays/registry.py
index e043c67c..883d391a 100644
--- a/holidays/registry.py
+++ b/holidays/registry.py
@@ -148,6 +148,7 @@ COUNTRIES: RegistryDict = {
     "monaco": ("Monaco", "MC", "MCO"),
     "mongolia": ("Mongolia", "MN", "MNG"),
     "montenegro": ("Montenegro", "ME", "MNE"),
+    "montserrat": ("Montserrat", "MS", "MSR"),
     "morocco": ("Morocco", "MA", "MOR"),
     "mozambique": ("Mozambique", "MZ", "MOZ"),
     "namibia": ("Namibia", "NA", "NAM"),


Test patch:
diff --git a/tests/countries/test_montserrat.py b/tests/countries/test_montserrat.py
new file mode 100644
index 00000000..1c67b14f
--- /dev/null
+++ b/tests/countries/test_montserrat.py
@@ -0,0 +1,234 @@
+#  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)
+
+from unittest import TestCase
+
+from holidays.countries.montserrat import Montserrat, MS, MSR
+from tests.common import CommonCountryTests
+
+
+class TestMontserrat(CommonCountryTests, TestCase):
+    @classmethod
+    def setUpClass(cls):
+        years = range(2017, 2050)
+        super().setUpClass(Montserrat, years=years, years_non_observed=years)
+
+    def test_country_aliases(self):
+        self.assertAliases(Montserrat, MS, MSR)
+
+    def test_no_holidays(self):
+        self.assertNoHolidays(Montserrat(years=2016))
+
+    def test_special_holidays(self):
+        self.assertHolidayName(
+            "Special Public Holiday",
+            "2018-09-14",
+            "2019-05-10",
+            "2020-07-15",
+            "2023-04-12",
+        )
+
+        for dt, name in (
+            ("2022-06-03", "Platinum Jubilee of Elizabeth II"),
+            ("2022-09-19", "National Day of Mourning"),
+            ("2023-05-06", "Coronation of King Charles III"),
+        ):
+            self.assertHolidayName(name, dt)
+
+    def test_new_years_day(self):
+        name = "New Year's Day"
+        self.assertHolidayName(name, (f"{year}-01-01" for year in range(2017, 2050)))
+        obs_dt = (
+            "2017-01-03",
+            "2018-01-02",
+            "2022-01-03",
+            "2023-01-03",
+            "2024-01-02",
+        )
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_saint_patricks_day(self):
+        name = "Saint Patrick's Day"
+        self.assertHolidayName(name, (f"{year}-03-17" for year in range(2017, 2050)))
+        obs_dt = (
+            "2018-03-19",
+            "2019-03-18",
+            "2024-03-18",
+        )
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_good_friday(self):
+        name = "Good Friday"
+        dts = (
+            "2022-04-15",
+            "2023-04-07",
+            "2024-03-29",
+            "2025-04-18",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2017, 2050))
+
+    def test_easter_monday(self):
+        name = "Easter Monday"
+        dts = (
+            "2022-04-18",
+            "2023-04-10",
+            "2024-04-01",
+            "2025-04-21",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2017, 2050))
+
+    def test_labor_day(self):
+        name = "Labour Day"
+        dts = (
+            "2022-05-02",
+            "2023-05-01",
+            "2024-05-06",
+            "2025-05-05",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2017, 2050))
+
+    def test_queens_birthday(self):
+        name = "Queen's Birthday"
+        dts = (
+            "2019-06-10",
+            "2020-06-15",
+            "2021-06-14",
+            "2022-06-02",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2017, 2023))
+        self.assertNoHolidayName(name, range(2023, 2050))
+
+    def test_kings_birthday(self):
+        name = "King's Birthday"
+        dts = (
+            "2023-06-19",
+            "2024-06-17",
+            "2025-06-16",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2023, 2050))
+        self.assertNoHolidayName(name, range(2017, 2023))
+
+    def test_whit_monday(self):
+        name = "Whit Monday"
+        dts = (
+            "2022-06-06",
+            "2023-05-29",
+            "2024-05-20",
+            "2025-06-09",
+        )
+        self.assertHolidayName(name, dts)
+        obs_dt = ("2019-06-11",)
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_day_of_prayer_and_thanksgiving(self):
+        first_name = "National Day of Prayer and Thanksgiving"
+        self.assertHolidayName(
+            first_name,
+            "2021-07-21",
+            "2022-07-20",
+            "2023-07-12",
+        )
+        self.assertNoHolidayName(first_name, range(2017, 2021), range(2024, 2050))
+        second_name = "Day of Prayer and Thanksgiving"
+        self.assertHolidayName(
+            second_name,
+            "2024-07-10",
+            "2025-07-09",
+        )
+        self.assertHolidayName(second_name, range(2024, 2050))
+        self.assertNoHolidayName(second_name, range(2017, 2024))
+
+    def test_emancipation_day(self):
+        name = "Emancipation Day"
+        dts = (
+            "2022-08-01",
+            "2023-08-07",
+            "2024-08-05",
+            "2025-08-04",
+        )
+        self.assertHolidayName(name, dts)
+        self.assertHolidayName(name, range(2017, 2050))
+
+    def test_christmas_day(self):
+        name = "Christmas Day"
+        self.assertHolidayName(name, (f"{year}-12-25" for year in range(2017, 2050)))
+        obs_dt = (
+            "2021-12-27",
+            "2022-12-27",
+        )
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_boxing_day(self):
+        name = "Boxing Day"
+        self.assertHolidayName(name, (f"{year}-12-26" for year in range(2017, 2050)))
+        obs_dt = (
+            "2020-12-28",
+            "2021-12-28",
+        )
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_festival_day(self):
+        name = "Festival Day"
+        self.assertHolidayName(name, (f"{year}-12-31" for year in range(2017, 2050)))
+        obs_dt = (
+            "2017-01-02",
+            "2023-01-02",
+        )
+        self.assertHolidayName(f"{name} (observed)", obs_dt)
+        self.assertNoNonObservedHoliday(obs_dt)
+
+    def test_l10n_default(self):
+        self.assertLocalizedHolidays(
+            ("2024-01-01", "New Year's Day"),
+            ("2024-01-02", "New Year's Day (observed)"),
+            ("2024-03-17", "Saint Patrick's Day"),
+            ("2024-03-18", "Saint Patrick's Day (observed)"),
+            ("2024-03-29", "Good Friday"),
+            ("2024-04-01", "Easter Monday"),
+            ("2024-05-06", "Labour Day"),
+            ("2024-05-20", "Whit Monday"),
+            ("2024-06-17", "King's Birthday"),
+            ("2024-07-10", "Day of Prayer and Thanksgiving"),
+            ("2024-08-05", "Emancipation Day"),
+            ("2024-12-25", "Christmas Day"),
+            ("2024-12-26", "Boxing Day"),
+            ("2024-12-31", "Festival Day"),
+        )
+
+    def test_l10n_en_us(self):
+        self.assertLocalizedHolidays(
+            "en_US",
+            ("2024-01-01", "New Year's Day"),
+            ("2024-01-02", "New Year's Day (observed)"),
+            ("2024-03-17", "Saint Patrick's Day"),
+            ("2024-03-18", "Saint Patrick's Day (observed)"),
+            ("2024-03-29", "Good Friday"),
+            ("2024-04-01", "Easter Monday"),
+            ("2024-05-06", "Labor Day"),
+            ("2024-05-20", "Whit Monday"),
+            ("2024-06-17", "King's Birthday"),
+            ("2024-07-10", "Day of Prayer and Thanksgiving"),
+            ("2024-08-05", "Emancipation Day"),
+            ("2024-12-25", "Christmas Day"),
+            ("2024-12-26", "Boxing Day"),
+            ("2024-12-31", "Festival Day"),
+        )