We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AW Harburg throws
With 1.28.0 fetching the calendar for aw_harburg_de stopped working.
Last working version probably was 1.26.x +- ... (skipped some updates)
config:```
```

waste_collection_schedule:
  sources:
    - name: aw_harburg_de
      args:
        level_1: "XXX"
        level_2: "YYY"
      customize:
        - type: Hausmüll 14-täglich
          alias: "waste"
          icon: mdi:trash-can
        - type: Gelber Sack
          alias: "recycle"
          icon: mdi:recycle
        - type: Altpapier
          alias: "paper"
          icon: mdi:trash-can-outline
  fetch_time: "04:00"
  day_switch_time: "10:00"
```
level_1 + 2 are set to real levels according to website and were not changed
Pls find log attached
```

Logger: waste_collection_schedule.scraper
Source: custom_components/waste_collection_schedule/waste_collection_schedule/scraper.py:143
Integration: waste_collection_schedule ([documentation](https://github.com/mampfes/hacs_waste_collection_schedule#readme))
First occurred: 17:00:41 (1 occurrences)
Last logged: 17:00:41
fetch failed for source AW Harburg: Traceback (most recent call last): File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/scraper.py", line 141, in fetch entries = self._source.fetch() File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py", line 108, in fetch dates = self._ics.convert(r.text) File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py", line 38, in convert events: List[Any] = icalevents.events( File "/usr/local/lib/python3.10/site-packages/icalevents/icalevents.py", line 50, in events found_events += parse_events(content, start=start, end=end) File "/usr/local/lib/python3.10/site-packages/icalevents/icalparser.py", line 250, in parse_events raise ValueError('Content is invalid!') ValueError: Content is invalid!
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py`
Content:
```
1 import requests
2 from bs4 import BeautifulSoup
3 from waste_collection_schedule import Collection  # type: ignore[attr-defined]
4 from waste_collection_schedule.service.ICS import ICS
5 
6 TITLE = "AW Harburg"
7 DESCRIPTION = "Abfallwirtschaft Landkreis Harburg"
8 URL = "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/"
9 
10 TEST_CASES = {
11     "CityWithTwoLevels": {"level_1": "Hanstedt", "level_2": "Evendorf"},
12     "CityWithThreeLevels": {
13         "level_1": "Buchholz",
14         "level_2": "Buchholz mit Steinbeck (ohne Reindorf)",
15         "level_3": "Seppenser Mühlenweg Haus-Nr. 1 / 2",
16     },
17 }
18 
19 HEADERS = {
20     "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)",
21 }
22 
23 
24 class Source:
25     def __init__(self, level_1, level_2, level_3=None):
26         self._districts = [level_1, level_2, level_3]
27         self._ics = ICS()
28 
29     def fetch(self):
30         # Use a session to keep cookies and stuff
31         session = requests.Session()
32 
33         # Get the IDs of the districts on the first level
34         # Double loading is on purpose because sometimes the webpage has an overlay
35         # which is gone on the second try in a session
36         r = session.get(URL, headers=HEADERS)
37         if "Zur aufgerufenen Seite" in r.text:
38             r = session.get(URL, headers=HEADERS)
39         if r.status_code != 200:
40             raise Exception(f"Error: failed to fetch first url: {URL}")
41 
42         # Get the IDs of the districts on the first level
43         id = self.parse_level(r.text, 1)
44 
45         # Get the IDs of the districts on the second level
46         url = (
47             "https://www.landkreis-harburg.de/ajax/abfall_gebiete_struktur_select.html"
48         )
49         params = {
50             "parent": id,
51             "ebene": 1,
52             "portal": 1,
53             "selected_ebene": 0,
54         }
55         r = session.get(url, params=params, headers=HEADERS)
56         if r.status_code != 200:
57             raise Exception(f"Error: failed to fetch second url: {url}")
58 
59         # Get the IDs of the districts on the second level
60         id = self.parse_level(r.text, 2)
61 
62         # Get the IDs of the third level - if applicable
63         if self._districts[3 - 1] is not None:
64             # Get the IDs of the districts on the third level
65             params = {
66                 "parent": id,
67                 "ebene": 2,
68                 "portal": 1,
69                 "selected_ebene": 0,
70             }
71             r = session.get(url, params=params, headers=HEADERS)
72             if r.status_code != 200:
73                 raise Exception(f"Error: failed to fetch third url: {url}")
74 
75             # Get the IDs of the districts on the third level
76             id = self.parse_level(r.text, 3)
77 
78         # Prepare data for the real web request
79         url = "https://www.landkreis-harburg.de/abfallkalender/abfallkalender_struktur_daten_suche.html"
80         params = {
81             "selected_ebene": id,
82             "owner": 20100,
83         }
84         r = session.get(url, params=params, headers=HEADERS)
85 
86         # Sometimes there is no garbage calendar available
87         if "Es sind keine Abfuhrbezirke hinterlegt." in r.text:
88             raise Exception(
89                 f'Error: "Es sind keine Abfuhrbezirke hinterlegt." for "{self._districts[3-1]}". Please use different input data.'
90             )
91 
92         soup = BeautifulSoup(r.text, features="html.parser")
93         links = soup.find_all("a")
94         ical_url = ""
95         for any_link in links:
96             if " als iCal" in any_link.text:
97                 ical_url = any_link.get("href")
98 
99         if "ical.html" not in ical_url:
100             raise Exception("No ical Link in the result: " + str(links))
101 
102         # Get the final data
103         r = requests.get(ical_url, headers=HEADERS)
104         if not r.ok:
105             raise Exception(f"Error: failed to fetch url: {ical_url}")
106 
107         # Parse ics file
108         dates = self._ics.convert(r.text)
109 
110         entries = []
111         for d in dates:
112             entries.append(Collection(d[0], d[1]))
113         return entries
114 
115     def parse_level(self, response, level):
116         soup = BeautifulSoup(response, features="html.parser")
117         select_content = soup.find_all("select", id=f"strukturEbene{level}")
118         soup = BeautifulSoup(str(select_content), features="html.parser")
119         options_content = soup.find_all("option")
120         level_ids = {}
121         for option in options_content:
122             # Ignore the "Bitte wählen..."
123             if option.get("value") != "0":
124                 level_ids[option.text] = option.get("value")
125 
126         if level_ids == {}:
127             raise Exception(f"Error: Level {level} Dictionary empty")
128 
129         if self._districts[level - 1] not in level_ids:
130             raise Exception(
131                 f"Error: District {self._districts[level]} is not in the dictionary: {level_ids}"
132             )
133 
134         return level_ids[self._districts[level - 1]]
135
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py
--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py
+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py
@@ -91,25 +91,26 @@
 
         soup = BeautifulSoup(r.text, features="html.parser")
         links = soup.find_all("a")
-        ical_url = ""
+        ical_urls = []
         for any_link in links:
             if " als iCal" in any_link.text:
-                ical_url = any_link.get("href")
-
-        if "ical.html" not in ical_url:
-            raise Exception("No ical Link in the result: " + str(links))
-
-        # Get the final data
-        r = requests.get(ical_url, headers=HEADERS)
-        if not r.ok:
-            raise Exception(f"Error: failed to fetch url: {ical_url}")
-
-        # Parse ics file
-        dates = self._ics.convert(r.text)
+                # multiple links occur during year transition
+                ical_urls.append(any_link.get("href"))
 
+        # Get the final data for all links
         entries = []
-        for d in dates:
-            entries.append(Collection(d[0], d[1]))
+        for ical_url in ical_urls:
+            r = requests.get(ical_url, headers=HEADERS)
+            r.raise_for_status()
+
+            # Parse ics file
+            try:
+                dates = self._ics.convert(r.text)
+
+                for d in dates:
+                    entries.append(Collection(d[0], d[1]))
+            except ValueError:
+                pass    # during year transition the ical for the next year may be empty
         return entries
 
     def parse_level(self, response, level):

{"golden_diff": "diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py\n--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py\n+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py\n@@ -91,25 +91,26 @@\n \n         soup = BeautifulSoup(r.text, features=\"html.parser\")\n         links = soup.find_all(\"a\")\n-        ical_url = \"\"\n+        ical_urls = []\n         for any_link in links:\n             if \" als iCal\" in any_link.text:\n-                ical_url = any_link.get(\"href\")\n-\n-        if \"ical.html\" not in ical_url:\n-            raise Exception(\"No ical Link in the result: \" + str(links))\n-\n-        # Get the final data\n-        r = requests.get(ical_url, headers=HEADERS)\n-        if not r.ok:\n-            raise Exception(f\"Error: failed to fetch url: {ical_url}\")\n-\n-        # Parse ics file\n-        dates = self._ics.convert(r.text)\n+                # multiple links occur during year transition\n+                ical_urls.append(any_link.get(\"href\"))\n \n+        # Get the final data for all links\n         entries = []\n-        for d in dates:\n-            entries.append(Collection(d[0], d[1]))\n+        for ical_url in ical_urls:\n+            r = requests.get(ical_url, headers=HEADERS)\n+            r.raise_for_status()\n+\n+            # Parse ics file\n+            try:\n+                dates = self._ics.convert(r.text)\n+\n+                for d in dates:\n+                    entries.append(Collection(d[0], d[1]))\n+            except ValueError:\n+                pass    # during year transition the ical for the next year may be empty\n         return entries\n \n     def parse_level(self, response, level):\n", "issue": "AW Harburg throws\nWith 1.28.0 fetching the calendar for aw_harburg_de stopped working.\r\nLast working version probably was 1.26.x +- ... (skipped some updates)\r\nconfig:```\r\n```\r\n\r\nwaste_collection_schedule:\r\n  sources:\r\n    - name: aw_harburg_de\r\n      args:\r\n        level_1: \"XXX\"\r\n        level_2: \"YYY\"\r\n      customize:\r\n        - type: Hausm\u00fcll 14-t\u00e4glich\r\n          alias: \"waste\"\r\n          icon: mdi:trash-can\r\n        - type: Gelber Sack\r\n          alias: \"recycle\"\r\n          icon: mdi:recycle\r\n        - type: Altpapier\r\n          alias: \"paper\"\r\n          icon: mdi:trash-can-outline\r\n  fetch_time: \"04:00\"\r\n  day_switch_time: \"10:00\"\r\n```\r\nlevel_1 + 2 are set to real levels according to website and were not changed\r\nPls find log attached\r\n```\r\n\r\nLogger: waste_collection_schedule.scraper\r\nSource: custom_components/waste_collection_schedule/waste_collection_schedule/scraper.py:143\r\nIntegration: waste_collection_schedule ([documentation](https://github.com/mampfes/hacs_waste_collection_schedule#readme))\r\nFirst occurred: 17:00:41 (1 occurrences)\r\nLast logged: 17:00:41\r\nfetch failed for source AW Harburg: Traceback (most recent call last): File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/scraper.py\", line 141, in fetch entries = self._source.fetch() File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py\", line 108, in fetch dates = self._ics.convert(r.text) File \"/config/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py\", line 38, in convert events: List[Any] = icalevents.events( File \"/usr/local/lib/python3.10/site-packages/icalevents/icalevents.py\", line 50, in events found_events += parse_events(content, start=start, end=end) File \"/usr/local/lib/python3.10/site-packages/icalevents/icalparser.py\", line 250, in parse_events raise ValueError('Content is invalid!') ValueError: Content is invalid!\r\n```\n", "before_files": [{"content": "import requests\nfrom bs4 import BeautifulSoup\nfrom waste_collection_schedule import Collection  # type: ignore[attr-defined]\nfrom waste_collection_schedule.service.ICS import ICS\n\nTITLE = \"AW Harburg\"\nDESCRIPTION = \"Abfallwirtschaft Landkreis Harburg\"\nURL = \"https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/\"\n\nTEST_CASES = {\n    \"CityWithTwoLevels\": {\"level_1\": \"Hanstedt\", \"level_2\": \"Evendorf\"},\n    \"CityWithThreeLevels\": {\n        \"level_1\": \"Buchholz\",\n        \"level_2\": \"Buchholz mit Steinbeck (ohne Reindorf)\",\n        \"level_3\": \"Seppenser M\u00fchlenweg Haus-Nr. 1 / 2\",\n    },\n}\n\nHEADERS = {\n    \"User-Agent\": \"Mozilla/5.0 (Windows NT 6.1; Win64; x64)\",\n}\n\n\nclass Source:\n    def __init__(self, level_1, level_2, level_3=None):\n        self._districts = [level_1, level_2, level_3]\n        self._ics = ICS()\n\n    def fetch(self):\n        # Use a session to keep cookies and stuff\n        session = requests.Session()\n\n        # Get the IDs of the districts on the first level\n        # Double loading is on purpose because sometimes the webpage has an overlay\n        # which is gone on the second try in a session\n        r = session.get(URL, headers=HEADERS)\n        if \"Zur aufgerufenen Seite\" in r.text:\n            r = session.get(URL, headers=HEADERS)\n        if r.status_code != 200:\n            raise Exception(f\"Error: failed to fetch first url: {URL}\")\n\n        # Get the IDs of the districts on the first level\n        id = self.parse_level(r.text, 1)\n\n        # Get the IDs of the districts on the second level\n        url = (\n            \"https://www.landkreis-harburg.de/ajax/abfall_gebiete_struktur_select.html\"\n        )\n        params = {\n            \"parent\": id,\n            \"ebene\": 1,\n            \"portal\": 1,\n            \"selected_ebene\": 0,\n        }\n        r = session.get(url, params=params, headers=HEADERS)\n        if r.status_code != 200:\n            raise Exception(f\"Error: failed to fetch second url: {url}\")\n\n        # Get the IDs of the districts on the second level\n        id = self.parse_level(r.text, 2)\n\n        # Get the IDs of the third level - if applicable\n        if self._districts[3 - 1] is not None:\n            # Get the IDs of the districts on the third level\n            params = {\n                \"parent\": id,\n                \"ebene\": 2,\n                \"portal\": 1,\n                \"selected_ebene\": 0,\n            }\n            r = session.get(url, params=params, headers=HEADERS)\n            if r.status_code != 200:\n                raise Exception(f\"Error: failed to fetch third url: {url}\")\n\n            # Get the IDs of the districts on the third level\n            id = self.parse_level(r.text, 3)\n\n        # Prepare data for the real web request\n        url = \"https://www.landkreis-harburg.de/abfallkalender/abfallkalender_struktur_daten_suche.html\"\n        params = {\n            \"selected_ebene\": id,\n            \"owner\": 20100,\n        }\n        r = session.get(url, params=params, headers=HEADERS)\n\n        # Sometimes there is no garbage calendar available\n        if \"Es sind keine Abfuhrbezirke hinterlegt.\" in r.text:\n            raise Exception(\n                f'Error: \"Es sind keine Abfuhrbezirke hinterlegt.\" for \"{self._districts[3-1]}\". Please use different input data.'\n            )\n\n        soup = BeautifulSoup(r.text, features=\"html.parser\")\n        links = soup.find_all(\"a\")\n        ical_url = \"\"\n        for any_link in links:\n            if \" als iCal\" in any_link.text:\n                ical_url = any_link.get(\"href\")\n\n        if \"ical.html\" not in ical_url:\n            raise Exception(\"No ical Link in the result: \" + str(links))\n\n        # Get the final data\n        r = requests.get(ical_url, headers=HEADERS)\n        if not r.ok:\n            raise Exception(f\"Error: failed to fetch url: {ical_url}\")\n\n        # Parse ics file\n        dates = self._ics.convert(r.text)\n\n        entries = []\n        for d in dates:\n            entries.append(Collection(d[0], d[1]))\n        return entries\n\n    def parse_level(self, response, level):\n        soup = BeautifulSoup(response, features=\"html.parser\")\n        select_content = soup.find_all(\"select\", id=f\"strukturEbene{level}\")\n        soup = BeautifulSoup(str(select_content), features=\"html.parser\")\n        options_content = soup.find_all(\"option\")\n        level_ids = {}\n        for option in options_content:\n            # Ignore the \"Bitte w\u00e4hlen...\"\n            if option.get(\"value\") != \"0\":\n                level_ids[option.text] = option.get(\"value\")\n\n        if level_ids == {}:\n            raise Exception(f\"Error: Level {level} Dictionary empty\")\n\n        if self._districts[level - 1] not in level_ids:\n            raise Exception(\n                f\"Error: District {self._districts[level]} is not in the dictionary: {level_ids}\"\n            )\n\n        return level_ids[self._districts[level - 1]]\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py"}], "after_files": [{"content": "import requests\nfrom bs4 import BeautifulSoup\nfrom waste_collection_schedule import Collection  # type: ignore[attr-defined]\nfrom waste_collection_schedule.service.ICS import ICS\n\nTITLE = \"AW Harburg\"\nDESCRIPTION = \"Abfallwirtschaft Landkreis Harburg\"\nURL = \"https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/\"\n\nTEST_CASES = {\n    \"CityWithTwoLevels\": {\"level_1\": \"Hanstedt\", \"level_2\": \"Evendorf\"},\n    \"CityWithThreeLevels\": {\n        \"level_1\": \"Buchholz\",\n        \"level_2\": \"Buchholz mit Steinbeck (ohne Reindorf)\",\n        \"level_3\": \"Seppenser M\u00fchlenweg Haus-Nr. 1 / 2\",\n    },\n}\n\nHEADERS = {\n    \"User-Agent\": \"Mozilla/5.0 (Windows NT 6.1; Win64; x64)\",\n}\n\n\nclass Source:\n    def __init__(self, level_1, level_2, level_3=None):\n        self._districts = [level_1, level_2, level_3]\n        self._ics = ICS()\n\n    def fetch(self):\n        # Use a session to keep cookies and stuff\n        session = requests.Session()\n\n        # Get the IDs of the districts on the first level\n        # Double loading is on purpose because sometimes the webpage has an overlay\n        # which is gone on the second try in a session\n        r = session.get(URL, headers=HEADERS)\n        if \"Zur aufgerufenen Seite\" in r.text:\n            r = session.get(URL, headers=HEADERS)\n        if r.status_code != 200:\n            raise Exception(f\"Error: failed to fetch first url: {URL}\")\n\n        # Get the IDs of the districts on the first level\n        id = self.parse_level(r.text, 1)\n\n        # Get the IDs of the districts on the second level\n        url = (\n            \"https://www.landkreis-harburg.de/ajax/abfall_gebiete_struktur_select.html\"\n        )\n        params = {\n            \"parent\": id,\n            \"ebene\": 1,\n            \"portal\": 1,\n            \"selected_ebene\": 0,\n        }\n        r = session.get(url, params=params, headers=HEADERS)\n        if r.status_code != 200:\n            raise Exception(f\"Error: failed to fetch second url: {url}\")\n\n        # Get the IDs of the districts on the second level\n        id = self.parse_level(r.text, 2)\n\n        # Get the IDs of the third level - if applicable\n        if self._districts[3 - 1] is not None:\n            # Get the IDs of the districts on the third level\n            params = {\n                \"parent\": id,\n                \"ebene\": 2,\n                \"portal\": 1,\n                \"selected_ebene\": 0,\n            }\n            r = session.get(url, params=params, headers=HEADERS)\n            if r.status_code != 200:\n                raise Exception(f\"Error: failed to fetch third url: {url}\")\n\n            # Get the IDs of the districts on the third level\n            id = self.parse_level(r.text, 3)\n\n        # Prepare data for the real web request\n        url = \"https://www.landkreis-harburg.de/abfallkalender/abfallkalender_struktur_daten_suche.html\"\n        params = {\n            \"selected_ebene\": id,\n            \"owner\": 20100,\n        }\n        r = session.get(url, params=params, headers=HEADERS)\n\n        # Sometimes there is no garbage calendar available\n        if \"Es sind keine Abfuhrbezirke hinterlegt.\" in r.text:\n            raise Exception(\n                f'Error: \"Es sind keine Abfuhrbezirke hinterlegt.\" for \"{self._districts[3-1]}\". Please use different input data.'\n            )\n\n        soup = BeautifulSoup(r.text, features=\"html.parser\")\n        links = soup.find_all(\"a\")\n        ical_urls = []\n        for any_link in links:\n            if \" als iCal\" in any_link.text:\n                # multiple links occur during year transition\n                ical_urls.append(any_link.get(\"href\"))\n\n        # Get the final data for all links\n        entries = []\n        for ical_url in ical_urls:\n            r = requests.get(ical_url, headers=HEADERS)\n            r.raise_for_status()\n\n            # Parse ics file\n            try:\n                dates = self._ics.convert(r.text)\n\n                for d in dates:\n                    entries.append(Collection(d[0], d[1]))\n            except ValueError:\n                pass    # during year transition the ical for the next year may be empty\n        return entries\n\n    def parse_level(self, response, level):\n        soup = BeautifulSoup(response, features=\"html.parser\")\n        select_content = soup.find_all(\"select\", id=f\"strukturEbene{level}\")\n        soup = BeautifulSoup(str(select_content), features=\"html.parser\")\n        options_content = soup.find_all(\"option\")\n        level_ids = {}\n        for option in options_content:\n            # Ignore the \"Bitte w\u00e4hlen...\"\n            if option.get(\"value\") != \"0\":\n                level_ids[option.text] = option.get(\"value\")\n\n        if level_ids == {}:\n            raise Exception(f\"Error: Level {level} Dictionary empty\")\n\n        if self._districts[level - 1] not in level_ids:\n            raise Exception(\n                f\"Error: District {self._districts[level]} is not in the dictionary: {level_ids}\"\n            )\n\n        return level_ids[self._districts[level - 1]]\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py"}]}