Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions elementary/utils/time.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from datetime import datetime, timedelta, timezone
from typing import Optional

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


_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"([+-])(\d{2})$")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Regex falsely matches date components (e.g., -15 in 2024-01-15) as abbreviated timezone offsets

The regex ([+-])(\d{2})$ is too broad and matches trailing -DD in date-only ISO strings, corrupting them before parsing.

Root Cause and Impact

The pattern _ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"([+-])(\d{2})$") matches any string ending with +XX or -XX. This means a date-only string like "2024-01-15" matches on "-15" and gets rewritten to "2024-01-15:00".

Verified with the actual code:

"2024-01-15" → "2024-01-15:00"  (corrupted!)
"2024-12"    → "2024-12:00"     (corrupted!)

In the current Python 3.10 environment, datetime.fromisoformat("2024-01-15:00") happens to parse identically to datetime.fromisoformat("2024-01-15") (both yield 2024-01-15 00:00:00) because fromisoformat accepts any single character as a date-time separator. So the output is currently identical by coincidence, but this is fragile — it relies on a quirk of fromisoformat's separator handling.

A safer pattern would anchor to a time component before the offset, e.g.:

re.compile(r"(\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?)([+-])(\d{2})$")

or at minimum require a T or digit before the +/-:

re.compile(r"(?<=\d)([+-])(\d{2})$")

Impact: Currently minimal because the coincidental parse behavior produces the same result, but the function silently corrupts its input for date-only strings. This could become a real issue with future Python version changes to fromisoformat parsing behavior, or with other partial ISO format inputs (like "2024-12").

Suggested change
_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"([+-])(\d{2})$")
_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"(?<=\d{2})([+-])(\d{2})$")
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the original ([+-])(\d{2})$ would indeed match -15 in date-only strings like 2024-01-15.

I've tightened the pattern to (:\d{2}(?:\.\d+)?)([+-])(\d{2})$ which requires a time component (:SS or :SS.fraction) before the +/-, so it only fires on actual timezone offsets after timestamps. Pushed in b38a4b5.



def _normalize_timezone_offset(time_string: str) -> str:
return _ABBREVIATED_TZ_OFFSET_PATTERN.sub(r"\1\2:00", time_string)


def convert_partial_iso_format_to_full_iso_format(partial_iso_format_time: str) -> str:
Comment thread
haritamar marked this conversation as resolved.
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

Expand Down
Loading