Skip to content

fix(CORE-355): handle abbreviated timezone offsets in convert_partial_iso_format_to_full_iso_format#2116

Merged
haritamar merged 3 commits intomasterfrom
core-355-fix-abbreviated-timezone-offset
Feb 18, 2026
Merged

fix(CORE-355): handle abbreviated timezone offsets in convert_partial_iso_format_to_full_iso_format#2116
haritamar merged 3 commits intomasterfrom
core-355-fix-abbreviated-timezone-offset

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 17, 2026

fix(CORE-355): handle abbreviated timezone offsets in time string parsing

Summary

PostgreSQL can emit timestamps with abbreviated timezone offsets like +00 (2-digit, no minutes). Python 3.10's datetime.fromisoformat() rejects this format — it requires +00:00. This causes a high volume of Failed to covert time string errors in Datadog (logger: elementary.utils.time).

The fix adds a normalization step that expands abbreviated offsets (+00+00:00) before parsing. Also fixes a typo in the error log message ("covert" → "convert").

Updates since last revision

  • Tightened regex: Changed from ([+-])(\d{2})$ to (:\d{2}(?:\.\d+)?)([+-])(\d{2})$ — now requires a time component (:SS or :SS.fraction) before the +/- sign, preventing false matches on date-only strings like 2024-01-15 where -15 would have incorrectly matched.
  • Added parametrized unit tests: 7 test cases covering abbreviated offsets (+00, -05), fractional seconds, already-correct full offsets (+00:00, +05:30), no-offset input, and date-only strings.

Review & Testing Checklist for Human

  • Log message string changed: The typo fix changes the error message from "Failed to covert time string" to "Failed to convert time string". If any Datadog monitors or alerts filter on the old misspelled string, they'll need updating.
  • Regex doesn't cover HH:MM+tz (no seconds): The pattern requires :\d{2} before the offset, so a hypothetical input like 15:26+00 (minutes without seconds) would not be normalized. This format is unusual and fromisoformat rejects it regardless, so it's not a regression — just worth noting.
  • Verify in Datadog: After deploying, confirm the "Failed to convert time string" error volume drops to near-zero for the airflow_prod service.

Notes


Open with Devin

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of abbreviated timezone offsets so inputs like "+05" are correctly interpreted and formatted as full ISO offsets (e.g., "+05:00"), ensuring accurate timezone-aware parsing.
    • Fixed a typo in an error message ("covert" → "convert").
  • Tests

    • Added unit tests covering conversion of partial ISO datetime strings to full ISO format.

…_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>
@linear
Copy link

linear bot commented Feb 17, 2026

@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Contributor

👋 @devin-ai-integration[bot]
Thank you for raising your pull request.
Please make sure to add tests and document all user-facing changes.
You can do this by editing the docs files in this pull request.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Normalize abbreviated timezone offsets (e.g., +00, +05) before calling datetime.fromisoformat(), add re import, fix a typo in an exception message, and add unit tests for the conversion function.

Changes

Cohort / File(s) Summary
Timezone parsing utility
elementary/utils/time.py
Adds import re, introduces _ABBREVIATED_TZ_OFFSET_PATTERN and _normalize_timezone_offset() to expand abbreviated timezone offsets (e.g., +05+05:00), applies normalization inside convert_partial_iso_format_to_full_iso_format() before datetime.fromisoformat(), and fixes typo "covert" → "convert".
Unit tests
tests/unit/utils/test_time.py
Adds tests covering convert_partial_iso_format_to_full_iso_format() for various partial ISO inputs and expected full ISO outputs, validating timezone normalization behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nibbled at timestamps, found offsets too brief,
A regex stitch made them whole — no parsing grief.
+05 hops to +05:00, +00 learns to stay,
Now Python and Postgres waltz, perfectly in play. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: handling abbreviated timezone offsets in the function, which is the core fix described in both the PR and the linked issue.
Linked Issues check ✅ Passed The code changes meet all objectives: normalize abbreviated timezone offsets like +00 to +00:00, fix the typo 'covert' to 'convert', and add test coverage for the function.
Out of Scope Changes check ✅ Passed All changes directly address CORE-355: timezone offset normalization, typo fix, and test coverage for convert_partial_iso_format_to_full_iso_format.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch core-355-fix-abbreviated-timezone-offset

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

)


_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"([+-])(\d{2})$")
Copy link
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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
elementary/utils/time.py (1)

100-107: Add regression tests for abbreviated offsets.

This change is parsing-critical. Please add tests covering at least: +00+00:00, negative offsets (e.g., -05), already-normalized offsets (+05:30), and no-offset inputs to prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@elementary/utils/time.py` around lines 100 - 107, Add unit tests for
convert_partial_iso_format_to_full_iso_format to cover abbreviated and edge
timezone offsets: test inputs like "2023-01-01T12:00:00+00" expecting "+00:00",
"2023-01-01T12:00:00-05" expecting "-05:00", an already-normalized offset like
"2023-01-01T12:00:00+05:30" expecting unchanged "+05:30", and a no-offset input
like "2023-01-01T12:00:00" (expecting UTC or no offset behavior as produced by
the function); for each invocation assert the returned string is a valid ISO
timestamp with microseconds stripped (microsecond=0) and correct
colon-normalized offset; add these tests alongside other time utilities and
reference convert_partial_iso_format_to_full_iso_format (and
_normalize_timezone_offset if needed) so future changes cannot regress these
cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@elementary/utils/time.py`:
- Around line 100-107: Add unit tests for
convert_partial_iso_format_to_full_iso_format to cover abbreviated and edge
timezone offsets: test inputs like "2023-01-01T12:00:00+00" expecting "+00:00",
"2023-01-01T12:00:00-05" expecting "-05:00", an already-normalized offset like
"2023-01-01T12:00:00+05:30" expecting unchanged "+05:30", and a no-offset input
like "2023-01-01T12:00:00" (expecting UTC or no offset behavior as produced by
the function); for each invocation assert the returned string is a valid ISO
timestamp with microseconds stripped (microsecond=0) and correct
colon-normalized offset; add these tests alongside other time utilities and
reference convert_partial_iso_format_to_full_iso_format (and
_normalize_timezone_offset if needed) so future changes cannot regress these
cases.

…fset

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>
)


_ABBREVIATED_TZ_OFFSET_PATTERN = re.compile(r"([+-])(\d{2})$")
Copy link
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
elementary/utils/time.py (2)

93-107: Add unit coverage for timezone normalization edge cases.

Please add tests for convert_partial_iso_format_to_full_iso_format covering +HH, -HH, fractional seconds, and date-only inputs to ensure the regex doesn’t regress parsing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@elementary/utils/time.py` around lines 93 - 107, Add unit tests for
convert_partial_iso_format_to_full_iso_format that exercise the
_ABBREVIATED_TZ_OFFSET_PATTERN normalization: include inputs with "+HH" (e.g.,
"2023-01-01T12:00:00+02"), "-HH" (e.g., "2023-01-01T12:00:00-05"), inputs with
fractional seconds (e.g., "2023-01-01T12:00:00.123+03"), and date-only strings
(e.g., "2023-01-01") to ensure _normalize_timezone_offset and
convert_partial_iso_format_to_full_iso_format produce valid full ISO strings and
do not break parsing; assert expected isoformat outputs and that no exceptions
are raised.

109-110: Update monitors/alerts that match the old log string.

The log message typo is fixed; ensure any Datadog monitors or alert rules keyed on the old string are updated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@elementary/utils/time.py` around lines 109 - 110, The log string passed to
logger.exception that referenced partial_iso_format_time was corrected, so
update any downstream Datadog monitors/alert rules that match the old message
exactly to use the new message text; search for usages of logger.exception (and
the variable partial_iso_format_time) in elementary/utils/time.py to find the
new exact string and update monitors/alert query expressions or saved alerts to
match it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@elementary/utils/time.py`:
- Around line 93-107: Add unit tests for
convert_partial_iso_format_to_full_iso_format that exercise the
_ABBREVIATED_TZ_OFFSET_PATTERN normalization: include inputs with "+HH" (e.g.,
"2023-01-01T12:00:00+02"), "-HH" (e.g., "2023-01-01T12:00:00-05"), inputs with
fractional seconds (e.g., "2023-01-01T12:00:00.123+03"), and date-only strings
(e.g., "2023-01-01") to ensure _normalize_timezone_offset and
convert_partial_iso_format_to_full_iso_format produce valid full ISO strings and
do not break parsing; assert expected isoformat outputs and that no exceptions
are raised.
- Around line 109-110: The log string passed to logger.exception that referenced
partial_iso_format_time was corrected, so update any downstream Datadog
monitors/alert rules that match the old message exactly to use the new message
text; search for usages of logger.exception (and the variable
partial_iso_format_time) in elementary/utils/time.py to find the new exact
string and update monitors/alert query expressions or saved alerts to match it.

…so_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>
@haritamar haritamar merged commit 866d7ae into master Feb 18, 2026
16 checks passed
@haritamar haritamar deleted the core-355-fix-abbreviated-timezone-offset branch February 18, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments