Repository: vacanza/python-holidays

Issue:
Cannot "Pop" Combined Texas Emancipation/Juneteenth Holiday
Creating a holiday using TX as the state, there is the combined holiday 'Emancipation Day In Texas; Juneteenth National Independence Day'

Previous versions of python-holidays seemed to treat this concatenation with a comma a such: 'Emancipation Day In Texas, Juneteenth National Independence Day'

I am able to 'pop_named' the holiday in the older version, not in the newer with the semicolon seperator:

python3.6/holidays 0.11.3.1:
![image](https://github.com/dr-prodigy/python-holidays/assets/38263272/0de82f34-1c46-400b-859a-b7d87f7c8703)

python 3.10/holidays .25:
![image](https://github.com/dr-prodigy/python-holidays/assets/38263272/381017f6-0d49-49d3-9b09-f4e5c85b31f0)




Discussion:
I guess technically these are two separate holidays. I can pop_named them individually but it ends up removing both anyways since it pops the date itself. 
seems like 'get_names' has changed since version .20 with this code from #991 

![image](https://github.com/dr-prodigy/python-holidays/assets/38263272/320c667c-b6b0-4a8e-82aa-f260b76000ca)

Hi @jasonalsing,
I agree w/ your point that `pop_named()` should have kept the second holiday present. We'll spend some time to make sure the fix is ready before the next release.

Thanks for bringing this up!
thanks, that makes sense to keep secondary holidays if one is pop_named()

However, what will the behavior be if both are pop_named() in the concatenated form:

pop_named('Emancipation Day In Texas; Juneteenth National Independence Day')

currently this just fails. Should users of this module expect to have to pop them individually as well? (like two separate pops) can pop_named() method support sending multiple with the HOLIDAY_NAME_DELIMITER included?

Thanks!
Ideally it'd cover all of these use cases:
  - delete both for `pop_named('Emancipation Day In Texas; Juneteenth National Independence Day')`
  - delete respective holidays for `pop_named('Emancipation Day In Texas)` and `pop_named('Juneteenth National Independence Day')`

As for now I don't see any reason why it should be implemented in a different way. I'll confirm when we get closer to the implementation process.

Resolving patch:
diff --git a/holidays/holiday_base.py b/holidays/holiday_base.py
--- a/holidays/holiday_base.py
+++ b/holidays/holiday_base.py
@@ -720,7 +720,12 @@ def get_list(self, key: DateLike) -> List[str]:
             if name
         ]
 
-    def get_named(self, holiday_name: str, lookup="icontains") -> List[date]:
+    def get_named(
+        self,
+        holiday_name: str,
+        lookup="icontains",
+        split_multiple_names=True,
+    ) -> List[date]:
         """Return a list of all holiday dates matching the provided holiday
         name. The match will be made case insensitively and partial matches
         will be included by default.
@@ -735,12 +740,17 @@ def get_named(self, holiday_name: str, lookup="icontains") -> List[date]:
                 icontains - case insensitive contains match;
                 iexact - case insensitive exact match;
                 istartswith - case insensitive starts with match;
+        :param split_multiple_names:
+            Either use the exact name for each date or split it by holiday
+            name delimiter.
 
         :return:
             A list of all holiday dates matching the provided holiday name.
         """
         holiday_date_names_mapping: Dict[date, List[str]] = {
             key: value.split(HOLIDAY_NAME_DELIMITER)
+            if split_multiple_names
+            else [value]
             for key, value in self.items()
         }
 
@@ -845,14 +855,24 @@ def pop_named(self, name: str) -> List[date]:
         :raise:
             KeyError if date is not a holiday and default is not given.
         """
-        dates = self.get_named(name)
-        if not dates:
+        use_exact_name = HOLIDAY_NAME_DELIMITER in name
+        dts = self.get_named(name, split_multiple_names=not use_exact_name)
+        if len(dts) == 0:
             raise KeyError(name)
 
-        for dt in dates:
+        popped = []
+        for dt in dts:
+            holiday_names = self[dt].split(HOLIDAY_NAME_DELIMITER)
             self.pop(dt)
+            popped.append(dt)
 
-        return dates
+            # Keep the rest of holidays falling on the same date.
+            if not use_exact_name:
+                holiday_names.remove(name)
+                if len(holiday_names) > 0:
+                    self[dt] = HOLIDAY_NAME_DELIMITER.join(holiday_names)
+
+        return popped
 
     def update(  # type: ignore[override]
         self, *args: Union[Dict[DateLike, str], List[DateLike], DateLike]


Test patch:
diff --git a/tests/test_holiday_base.py b/tests/test_holiday_base.py
--- a/tests/test_holiday_base.py
+++ b/tests/test_holiday_base.py
@@ -21,6 +21,7 @@
 
 import holidays
 from holidays.constants import JAN, FEB, OCT, MON, TUE, SAT, SUN
+from holidays.constants import HOLIDAY_NAME_DELIMITER
 
 
 class TestBasics(unittest.TestCase):
@@ -119,15 +120,60 @@ def test_pop(self):
         self.assertNotIn(date(2014, 1, 1), self.holidays)
         self.assertIn(date(2014, 7, 4), self.holidays)
 
-    def test_pop_named(self):
+    def test_pop_named_single(self):
         self.assertIn(date(2014, 1, 1), self.holidays)
-        self.holidays.pop_named("New Year's Day")
-        self.assertNotIn(date(2014, 1, 1), self.holidays)
+        dts = self.holidays.pop_named("New Year's Day")
+        for dt in dts:
+            self.assertNotIn(dt, self.holidays)
+
+    def test_pop_named_multiple(self):
+        dt = date(2022, 2, 22)
+        holiday_name_1 = "Holiday Name 1"
+        holiday_name_2 = "Holiday Name 2"
+        holiday_name_3 = "Holiday Name 3"
+        combined_name = HOLIDAY_NAME_DELIMITER.join(
+            (holiday_name_1, holiday_name_2, holiday_name_3)
+        )
+        self.holidays[dt] = holiday_name_1
+        self.holidays[dt] = holiday_name_2
+        self.holidays[dt] = holiday_name_3
+        self.assertEqual(self.holidays[dt], combined_name)
+
+        # Pop the entire date by multiple holidays exact name.
+        self.holidays.pop_named(combined_name)
+        self.assertNotIn(dt, self.holidays)
+
+        # Pop only one holiday by a single name.
+        self.holidays[dt] = holiday_name_1
+        self.holidays[dt] = holiday_name_2
+        self.holidays[dt] = holiday_name_3
+        self.assertEqual(self.holidays[dt], combined_name)
+
+        self.holidays.pop_named(holiday_name_1)
+        self.assertEqual(
+            self.holidays[dt],
+            HOLIDAY_NAME_DELIMITER.join((holiday_name_2, holiday_name_3)),
+        )
+
+        self.holidays.pop_named(holiday_name_3)
+        self.assertEqual(self.holidays[dt], holiday_name_2)
+
+        self.holidays.pop_named(holiday_name_2)
+        self.assertNotIn(dt, self.holidays)
+
+    def test_pop_named_exception(self):
         self.assertRaises(
             KeyError,
             lambda: self.holidays.pop_named("New Year's Dayz"),
         )
 
+        self.assertIn(date(2022, 1, 1), self.holidays)
+        self.holidays.pop_named("New Year's Day")
+        self.assertRaises(
+            KeyError,
+            lambda: self.holidays.pop_named("New Year's Day"),
+        )
+
     def test_setitem(self):
         self.holidays = holidays.US(years=[2014])
         self.assertEqual(len(self.holidays), 10)