Skip to content

fix: use datetime.fromisoformat() to handle timezone offsets in event… - #1634

Open
mkanoor wants to merge 1 commit into
ansible:mainfrom
mkanoor:time_parsing_error
Open

fix: use datetime.fromisoformat() to handle timezone offsets in event…#1634
mkanoor wants to merge 1 commit into
ansible:mainfrom
mkanoor:time_parsing_error

Conversation

@mkanoor

@mkanoor mkanoor commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Failure on parsing date time in AnsibleEvents which contain time zone offset

strptime with "%Y-%m-%dT%H:%M:%S.%f" fails on timestamps containing timezone offsets (e.g. +00:00) with:

ValueError: unconverted data remains: +00:00
File "consumers.py", line 240, in insert_event_related_data
created = datetime.strptime(created, "%Y-%m-%dT%H:%M:%S.%f")

fromisoformat() handles all ISO 8601 variants including naive, Z-suffixed, and offset timestamps.

Summary by CodeRabbit

  • Bug Fixes
    • Improved event processing to accept ISO 8601 timestamps in multiple formats, including UTC and timezone-offset values.
    • Ensured events with timezone-aware timestamps are stored and processed correctly.

@mkanoor
mkanoor requested a review from a team as a code owner July 24, 2026 21:33
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mkanoor, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 852c80c5-b981-4e40-938a-0f67d6c76583

📥 Commits

Reviewing files that changed from the base of the PR and between c26782d and d5422c7.

📒 Files selected for processing (2)
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/wsapi/test_consumer.py
📝 Walkthrough

Walkthrough

The websocket consumer now parses created timestamps with datetime.fromisoformat, and integration tests cover naive, UTC, and offset-based ISO-8601 values. A heartbeat assignment was reformatted without changing its stored payload.

Changes

Websocket timestamp handling

Layer / File(s) Summary
Timestamp parsing and integration validation
src/aap_eda/wsapi/consumers.py, tests/integration/wsapi/test_consumer.py
Event creation uses datetime.fromisoformat and tests verify processing for multiple timezone-aware and naive timestamp formats. The heartbeat statistics assignment is reformatted without changing behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the bug and fix, but it does not follow the required template sections or include testing and impact details. Add the template sections: what changed, why it is needed, how it addresses the issue, any dependencies or breaking changes, and how to test it.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: switching to fromisoformat() to support timezone-offset event timestamps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@mkanoor
mkanoor force-pushed the time_parsing_error branch from c26782d to 825aa65 Compare July 24, 2026 21:35
@mkanoor
mkanoor requested review from AlexSCorey and wfealdel July 24, 2026 21:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/integration/wsapi/test_consumer.py (1)

306-306: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the persisted timestamp, not only the row count.

The test passes even if created is ignored or converted to the wrong instant. Retrieve the inserted JobInstanceEvent and compare created_at with the expected timestamp after applying the project’s timezone normalization.

As per path instructions: “Focus on major issues impacting performance, readability, maintainability and security; avoid nitpicks and verbosity.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/wsapi/test_consumer.py` at line 306, Update the test around
get_job_instance_event_count to retrieve the inserted JobInstanceEvent and
assert its created_at matches the expected timestamp after applying the
project’s timezone normalization. Retain the existing row-count assertion, but
ensure the test validates persisted timestamp correctness rather than only
insertion count.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aap_eda/wsapi/consumers.py`:
- Line 240: Update the JobInstanceEvent.created_at model/serialization path so
the parsed WebSocket timestamp from created is preserved when
JobInstanceEvent.objects.create(...) runs. Make created_at explicitly writable
(including any serializer or model configuration needed) and avoid auto_now_add
overriding the supplied value; retain the parsed timestamp as the stored event
time.

---

Nitpick comments:
In `@tests/integration/wsapi/test_consumer.py`:
- Line 306: Update the test around get_job_instance_event_count to retrieve the
inserted JobInstanceEvent and assert its created_at matches the expected
timestamp after applying the project’s timezone normalization. Retain the
existing row-count assertion, but ensure the test validates persisted timestamp
correctness rather than only insertion count.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c1b6665f-b544-4b7f-9a66-53e38948b727

📥 Commits

Reviewing files that changed from the base of the PR and between 3e648f1 and c26782d.

📒 Files selected for processing (2)
  • src/aap_eda/wsapi/consumers.py
  • tests/integration/wsapi/test_consumer.py

created = event_data.get("created")
if created:
created = datetime.strptime(created, "%Y-%m-%dT%H:%M:%S.%f")
created = datetime.fromisoformat(created)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

rg -n -C 3 'JobInstanceEvent|created_at\s*=' src/aap_eda/core/models/job.py

Repository: ansible/eda-server

Length of output: 757


Persist the WebSocket event timestamp in JobInstanceEvent.created_at.

JobInstanceEvent.created_at is defined with auto_now_add=True, so the parsed created value passed into JobInstanceEvent.objects.create(...) is overwritten by insertion time. Either make the field editable / add it to the serializer or omit it from the model if only the runtime event time should be kept.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aap_eda/wsapi/consumers.py` at line 240, Update the
JobInstanceEvent.created_at model/serialization path so the parsed WebSocket
timestamp from created is preserved when JobInstanceEvent.objects.create(...)
runs. Make created_at explicitly writable (including any serializer or model
configuration needed) and avoid auto_now_add overriding the supplied value;
retain the parsed timestamp as the stored event time.

Source: Path instructions

@codecov-commenter

codecov-commenter commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.37%. Comparing base (85eb48e) to head (d5422c7).

@@           Coverage Diff           @@
##             main    #1634   +/-   ##
=======================================
  Coverage   93.36%   93.37%           
=======================================
  Files         244      244           
  Lines       11412    11412           
=======================================
+ Hits        10655    10656    +1     
+ Misses        757      756    -1     
Flag Coverage Δ
unit-int-tests-3.11 93.37% <100.00%> (+<0.01%) ⬆️
unit-int-tests-3.12 93.37% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/aap_eda/wsapi/consumers.py 93.71% <100.00%> (+0.28%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread tests/integration/wsapi/test_consumer.py
Comment on lines +286 to +306
async def test_handle_events_with_timezone_in_created(
ws_communicator: WebsocketCommunicator,
created_timestamp: str,
):
job_instance = await _prepare_job_instance()

initial_count = await get_job_instance_event_count()
payload = {
"type": "AnsibleEvent",
"event": {
"event": "verbose",
"job_id": job_instance.uuid,
"counter": 1,
"stdout": "the playbook is completed",
"created": created_timestamp,
},
}
await ws_communicator.send_json_to(payload)
await ws_communicator.wait()

assert (await get_job_instance_event_count()) == initial_count + 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AFAICS, this test only checks that the event is processed, however nothing actually seems to check that the wanted created_timestamp string is parsed properly including the timezone. Am I missing anything?

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.

@ptoscano This was a parsing error that I was trying to fix, there is another issue which relates to created_at should be set differently that requires DB migration. I think that should be done in a different PR. This was limited to fixing the parsing error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

there is another issue which relates to created_at should be set differently that requires DB migration. I think that should be done in a different PR. This was limited to fixing the parsing error.

That is a different problem, yes.

What I'm saying is different: this test does not actually test that the result of the created parsing is what is expected. If datetime.fromisoformat() would suddenly ignore bits (like the previous datetime.strptime() way) without an exception, then this test is not checking that.

@mkanoor
mkanoor force-pushed the time_parsing_error branch from 825aa65 to e737b9e Compare July 27, 2026 21:19
… timestamps

strptime with "%Y-%m-%dT%H:%M:%S.%f" fails on timestamps containing
timezone offsets (e.g. +00:00) with:

  ValueError: unconverted data remains: +00:00
    File "consumers.py", line 240, in insert_event_related_data
      created = datetime.strptime(created, "%Y-%m-%dT%H:%M:%S.%f")

fromisoformat() handles all ISO 8601 variants including naive,
Z-suffixed, and offset timestamps.

This was happening on receipt of AnsibleEvents on the WebSocket channel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mkanoor
mkanoor force-pushed the time_parsing_error branch from e737b9e to d5422c7 Compare July 27, 2026 21:19
@sonarqubecloud

Copy link
Copy Markdown

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.

4 participants