diff --git a/elementary/utils/time.py b/elementary/utils/time.py index 96f0cfce3..7b861aa1d 100644 --- a/elementary/utils/time.py +++ b/elementary/utils/time.py @@ -1,3 +1,4 @@ +import re from datetime import datetime, timedelta, timezone from typing import Optional @@ -89,17 +90,24 @@ def datetime_strftime(datetime: datetime, include_timezone: bool = False) -> str ) +_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"(:\d{2}(?:\.\d+)?)([+-])(\d{2})$") + + +def _normalize_timezone_offset(time_string: str) -> str: + return _ABBREVIATED_TZ_OFFSET_PATTERN.sub(r"\1\2\3:00", time_string) + + def convert_partial_iso_format_to_full_iso_format(partial_iso_format_time: str) -> str: try: - date = datetime.fromisoformat(partial_iso_format_time) - # Get the given date timezone + normalized = _normalize_timezone_offset(partial_iso_format_time) + date = datetime.fromisoformat(normalized) time_zone_name = date.strftime("%Z") time_zone = tz.gettz(time_zone_name) if time_zone_name else tz.UTC date_with_timezone = date.replace(tzinfo=time_zone, microsecond=0) return date_with_timezone.isoformat() except ValueError: logger.exception( - f'Failed to covert time string: "{partial_iso_format_time}" to ISO format' + f'Failed to convert time string: "{partial_iso_format_time}" to ISO format' ) return partial_iso_format_time diff --git a/tests/unit/utils/test_time.py b/tests/unit/utils/test_time.py index f2e010803..37499b357 100644 --- a/tests/unit/utils/test_time.py +++ b/tests/unit/utils/test_time.py @@ -1,8 +1,10 @@ from datetime import datetime +import pytest from dateutil import tz from elementary.utils.time import ( + convert_partial_iso_format_to_full_iso_format, convert_time_to_timezone, datetime_strftime, get_formatted_timedelta, @@ -67,3 +69,49 @@ def test_convert_time_to_timezone(): assert date.hour == 1 assert date_with_timezone.hour == 2 assert (date - date_with_timezone).total_seconds() == 0 + + +@pytest.mark.parametrize( + "input_time, expected_output", + [ + pytest.param( + "2024-01-01T12:00:00+00", + "2024-01-01T12:00:00+00:00", + id="abbreviated_utc_offset", + ), + pytest.param( + "2024-01-01T12:00:00-05", + "2024-01-01T12:00:00-05:00", + id="abbreviated_negative_offset", + ), + pytest.param( + "2024-01-01 12:00:00.123456+00", + "2024-01-01T12:00:00+00:00", + id="abbreviated_offset_with_fractional_seconds", + ), + pytest.param( + "2024-01-01T12:00:00+00:00", + "2024-01-01T12:00:00+00:00", + id="full_utc_offset", + ), + pytest.param( + "2024-01-01T12:00:00+05:30", + "2024-01-01T12:00:00+05:30", + id="full_non_utc_offset", + ), + pytest.param( + "2024-01-01T12:00:00", + "2024-01-01T12:00:00+00:00", + id="no_offset_defaults_to_utc", + ), + pytest.param( + "2024-01-15", + "2024-01-15T00:00:00+00:00", + id="date_only_not_corrupted", + ), + ], +) +def test_convert_partial_iso_format_to_full_iso_format( + input_time: str, expected_output: str +) -> None: + assert convert_partial_iso_format_to_full_iso_format(input_time) == expected_output