diff --git a/agenda/gwr.py b/agenda/gwr.py index cc7e968..06fc273 100644 --- a/agenda/gwr.py +++ b/agenda/gwr.py @@ -3,6 +3,7 @@ import os import re from datetime import date, datetime +from html import unescape from time import time import httpx @@ -12,6 +13,7 @@ url = "https://www.gwr.com/your-tickets/choosing-your-ticket/advance-tickets" def parse_date_string(date_str: str) -> date: """Parse date string from HTML.""" + date_str = " ".join(unescape(date_str).split()) if not date_str[-1].isdigit(): # If the year is missing, use the current year date_str += f" {date.today().year}" diff --git a/tests/test_gwr.py b/tests/test_gwr.py index 03cd342..60f18bf 100644 --- a/tests/test_gwr.py +++ b/tests/test_gwr.py @@ -2,8 +2,8 @@ import os import tempfile +import typing from datetime import date -from typing import Any from unittest.mock import AsyncMock, patch import pytest @@ -20,6 +20,16 @@ from agenda.gwr import ( class TestParseDateString: """Tests for parse_date_string function.""" + @pytest.mark.parametrize("space", [" ", " ", " ", "\u00a0"]) + @pytest.mark.parametrize("include_year", [True, False]) + def test_parse_html_whitespace(self, space: str, include_year: bool) -> None: + """Decode HTML spaces before parsing dates or inferring the year.""" + date_str = f"Thursday 25{space}March" + if include_year: + date_str += " 2027" + result = parse_date_string(f" {space}{date_str}{space} ") + assert result == date(2027 if include_year else date.today().year, 3, 25) + def test_parse_date_with_year(self) -> None: """Test parsing date string with year included.""" result = parse_date_string("Monday 25 December 2023") @@ -152,7 +162,7 @@ class TestAdvanceTicketsPageHtml: @pytest.mark.asyncio @patch("httpx.AsyncClient") - async def test_fetch_from_web(self, mock_client: Any) -> None: + async def test_fetch_from_web(self, mock_client: typing.Any) -> None: """Test fetching from web when cache is stale.""" mock_response = AsyncMock() mock_response.text = "fresh content" @@ -176,7 +186,7 @@ class TestAdvanceTicketDate: @pytest.mark.asyncio @patch("agenda.gwr.advance_tickets_page_html") - async def test_advance_ticket_date_success(self, mock_html: Any) -> None: + async def test_advance_ticket_date_success(self, mock_html: typing.Any) -> None: """Test successfully extracting advance ticket date.""" mock_html.return_value = """
No relevant data
" @@ -197,7 +207,7 @@ class TestAdvanceTicketDate: @pytest.mark.asyncio @patch("agenda.gwr.advance_tickets_page_html") - async def test_advance_ticket_date_force_cache(self, mock_html: Any) -> None: + async def test_advance_ticket_date_force_cache(self, mock_html: typing.Any) -> None: """Test advance_ticket_date with force_cache parameter.""" mock_html.return_value = """