Skip to content

Commit 866d7ae

Browse files
fix(CORE-355): handle abbreviated timezone offsets in convert_partial_iso_format_to_full_iso_format (#2116)
* fix(CORE-355): handle abbreviated timezone offsets in convert_partial_iso_format_to_full_iso_format - Add _normalize_timezone_offset to expand +HH to +HH:00 before parsing - Python 3.10 datetime.fromisoformat() rejects +00 format from PostgreSQL - Also fix typo: 'covert' -> 'convert' in error log message Co-Authored-By: Itamar Hartstein <haritamar@gmail.com> * fix: tighten regex to require time component before abbreviated tz offset Anchors the pattern to :\d{2} (seconds) or .\d+ (fractional seconds) before the +/- sign, preventing false matches on date-only strings like '2024-01-15' where '-15' would incorrectly match. Co-Authored-By: Itamar Hartstein <haritamar@gmail.com> * test: add parametrized tests for convert_partial_iso_format_to_full_iso_format Covers abbreviated offsets (+00, -05), fractional seconds, full offsets, no-offset input, and date-only strings to prevent regressions. Co-Authored-By: Itamar Hartstein <haritamar@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Itamar Hartstein <haritamar@gmail.com>
1 parent 814f0ae commit 866d7ae

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

elementary/utils/time.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from datetime import datetime, timedelta, timezone
23
from typing import Optional
34

@@ -89,17 +90,24 @@ def datetime_strftime(datetime: datetime, include_timezone: bool = False) -> str
8990
)
9091

9192

93+
_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"(:\d{2}(?:\.\d+)?)([+-])(\d{2})$")
94+
95+
96+
def _normalize_timezone_offset(time_string: str) -> str:
97+
return _ABBREVIATED_TZ_OFFSET_PATTERN.sub(r"\1\2\3:00", time_string)
98+
99+
92100
def convert_partial_iso_format_to_full_iso_format(partial_iso_format_time: str) -> str:
93101
try:
94-
date = datetime.fromisoformat(partial_iso_format_time)
95-
# Get the given date timezone
102+
normalized = _normalize_timezone_offset(partial_iso_format_time)
103+
date = datetime.fromisoformat(normalized)
96104
time_zone_name = date.strftime("%Z")
97105
time_zone = tz.gettz(time_zone_name) if time_zone_name else tz.UTC
98106
date_with_timezone = date.replace(tzinfo=time_zone, microsecond=0)
99107
return date_with_timezone.isoformat()
100108
except ValueError:
101109
logger.exception(
102-
f'Failed to covert time string: "{partial_iso_format_time}" to ISO format'
110+
f'Failed to convert time string: "{partial_iso_format_time}" to ISO format'
103111
)
104112
return partial_iso_format_time
105113

tests/unit/utils/test_time.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import datetime
22

3+
import pytest
34
from dateutil import tz
45

56
from elementary.utils.time import (
7+
convert_partial_iso_format_to_full_iso_format,
68
convert_time_to_timezone,
79
datetime_strftime,
810
get_formatted_timedelta,
@@ -67,3 +69,49 @@ def test_convert_time_to_timezone():
6769
assert date.hour == 1
6870
assert date_with_timezone.hour == 2
6971
assert (date - date_with_timezone).total_seconds() == 0
72+
73+
74+
@pytest.mark.parametrize(
75+
"input_time, expected_output",
76+
[
77+
pytest.param(
78+
"2024-01-01T12:00:00+00",
79+
"2024-01-01T12:00:00+00:00",
80+
id="abbreviated_utc_offset",
81+
),
82+
pytest.param(
83+
"2024-01-01T12:00:00-05",
84+
"2024-01-01T12:00:00-05:00",
85+
id="abbreviated_negative_offset",
86+
),
87+
pytest.param(
88+
"2024-01-01 12:00:00.123456+00",
89+
"2024-01-01T12:00:00+00:00",
90+
id="abbreviated_offset_with_fractional_seconds",
91+
),
92+
pytest.param(
93+
"2024-01-01T12:00:00+00:00",
94+
"2024-01-01T12:00:00+00:00",
95+
id="full_utc_offset",
96+
),
97+
pytest.param(
98+
"2024-01-01T12:00:00+05:30",
99+
"2024-01-01T12:00:00+05:30",
100+
id="full_non_utc_offset",
101+
),
102+
pytest.param(
103+
"2024-01-01T12:00:00",
104+
"2024-01-01T12:00:00+00:00",
105+
id="no_offset_defaults_to_utc",
106+
),
107+
pytest.param(
108+
"2024-01-15",
109+
"2024-01-15T00:00:00+00:00",
110+
id="date_only_not_corrupted",
111+
),
112+
],
113+
)
114+
def test_convert_partial_iso_format_to_full_iso_format(
115+
input_time: str, expected_output: str
116+
) -> None:
117+
assert convert_partial_iso_format_to_full_iso_format(input_time) == expected_output

0 commit comments

Comments
 (0)