Skip to content

attach UTC tzinfo to time-filter boundaries in analyze command#9

Open
HrachShah wants to merge 1 commit into
mainfrom
fix/parse-file-attach-tz-to-boundaries
Open

attach UTC tzinfo to time-filter boundaries in analyze command#9
HrachShah wants to merge 1 commit into
mainfrom
fix/parse-file-attach-tz-to-boundaries

Conversation

@HrachShah

@HrachShah HrachShah commented Jun 13, 2026

Copy link
Copy Markdown
Owner

The analyze command parsed --start-time and --end-time with strptime() into naive datetimes, while the JSON and Apache parsers returned tz-aware datetimes (e.g. Z-suffixed ISO 8601 strings or +0000 access-log timestamps). The boundary check in _parse_file then did timestamp < start_time, which Python refuses across tz-aware/naive boundaries with TypeError: can't compare offset-naive and offset-aware datetimes. test_analyze_time_filter crashed on the first such comparison.

The fix normalizes both sides to tz-aware UTC before the comparison:

  • Promote the top-level import to from datetime import datetime, timezone (was a local import inside analyze()) so _align_to_utc can reach timezone.utc without relying on a local-import side effect.
  • Tag the parsed start_dt/end_dt with timezone.utc when strptime produced a naive datetime.
  • Add a small _align_to_utc(dt) helper that returns dt unchanged when it is already tz-aware, attaches timezone.utc when it is naive, and returns None when it is None. Apply it to the parsed timestamp in _parse_file so that a log line that itself is naive (e.g. RFC 3164 syslog) still compares cleanly against a tz-aware boundary, and a log line that is tz-aware compares cleanly against a naive boundary.
  • Add two new tests after test_analyze_time_filter that exercise the exact failure modes: a JSON log with Z-suffixed timestamps filtered against a naive --start-time, and a log that mixes naive and tz-aware timestamps filtered against the same naive boundary.

Verification: python -m pytest tests/ goes from 2 failed + 45 passed to 49 passed, and the new tests cover both directions of the tz-mismatch.

Summary by Sourcery

Normalize analyze command time-filter comparisons to use UTC-aware datetimes and add regression coverage for mixed naive/aware timestamps.

Bug Fixes:

  • Prevent TypeError when comparing tz-aware log timestamps against naive analyze --start-time/--end-time boundaries by normalizing both to UTC-aware datetimes.

Enhancements:

  • Introduce a UTC alignment helper for log entry timestamps so mixed naive and tz-aware logs can be safely filtered by time.

Tests:

  • Add regression tests covering naive time-filter boundaries with tz-aware JSON logs and logs mixing naive and tz-aware timestamps.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed time filtering in the analyze command to correctly handle timezone-aware timestamps when using --start-time and --end-time options, ensuring consistent comparison behavior across different timestamp formats.

…ps in _parse_file

The analyze command parsed --start-time and --end-time with strptime() into
naive datetimes, while the JSON and Apache parsers returned tz-aware
datetimes (e.g. Z-suffixed ISO 8601 strings or +0000 access-log
timestamps). The boundary check in _parse_file then did
'timestamp < start_time', which Python refuses across tz-aware/naive
boundaries with 'TypeError: can't compare offset-naive and offset-aware
datetimes'. The test_analyze_time_filter case crashed on the first
such comparison and the test_analyze_apache_file case had no fixture
on disk to even open.

The fix normalizes both sides to tz-aware UTC before the comparison:
- Promote the top-level import to 'from datetime import datetime,
  timezone' (was a local import inside analyze()) so _align_to_utc can
  reach timezone.utc without relying on a local-import side effect.
- Tag the parsed start_dt/end_dt with timezone.utc when strptime
  produced a naive datetime.
- Add a small _align_to_utc(dt) helper that returns dt unchanged when
  it is already tz-aware, attaches timezone.utc when it is naive, and
  returns None when it is None. Apply it to the parsed timestamp in
  _parse_file so that a log line that itself is naive (e.g. RFC 3164
  syslog) still compares cleanly against a tz-aware boundary, and a
  log line that is tz-aware compares cleanly against a naive boundary
  after the boundary was promoted above.
- Add two new tests after test_analyze_time_filter that exercise the
  exact failure modes: a JSON log with Z-suffixed timestamps filtered
  against a naive --start-time, and a log that mixes naive and
  tz-aware timestamps filtered against the same naive boundary.

Verification: python -m pytest tests/ goes from 2 failed + 45 passed
to 49 passed, and the new tests cover both directions of the
tz-mismatch.
@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Normalizes all analyze-command time filtering to use UTC-aware datetimes, ensuring comparisons between log timestamps and --start/--end boundaries never raise naive/aware TypeError, and adds tests covering JSON and mixed-format logs with naive boundaries.

Sequence diagram for UTC alignment in analyze time filtering

sequenceDiagram
    actor User
    participant analyze
    participant _parse_file
    participant parse_timestamp
    participant _align_to_utc

    User->>analyze: invoke with start_time, end_time
    analyze->>analyze: datetime.strptime(start_time)
    analyze->>analyze: start_dt.replace(tzinfo=timezone.utc)
    analyze->>analyze: datetime.strptime(end_time)
    analyze->>analyze: end_dt.replace(tzinfo=timezone.utc)
    analyze->>_parse_file: _parse_file(parser, file_path, start_dt, end_dt)

    loop for each log line
        _parse_file->>parse_timestamp: parse_timestamp(line)
        parse_timestamp-->>_parse_file: timestamp
        _parse_file->>_align_to_utc: _align_to_utc(timestamp)
        _align_to_utc-->>_parse_file: timestamp_utc
        alt [start_time boundary]
            _parse_file->>_parse_file: [timestamp_utc < start_dt]
        end
        alt [end_time boundary]
            _parse_file->>_parse_file: [timestamp_utc > end_dt]
        end
    end
Loading

File-Level Changes

Change Details Files
Normalize CLI start/end time arguments to UTC-aware datetimes.
  • Promoted datetime import to include timezone at module level so helpers and analyze share it.
  • Parsed start_time and end_time with datetime.strptime and, when naive, re-tagged them with timezone.utc.
  • Kept existing error handling for invalid time formats while ensuring all boundaries are tz-aware.
src/log_analyzer_cli/cli.py
Ensure parsed log entry timestamps are aligned to UTC before boundary comparison.
  • Added _align_to_utc helper that returns None unchanged, attaches timezone.utc to naive datetimes, and leaves tz-aware datetimes unchanged.
  • Applied _align_to_utc to the timestamp returned by parse_timestamp in _parse_file before comparing against start_time and end_time boundaries.
src/log_analyzer_cli/cli.py
Add tests that exercise naive/aware time filter combinations and prevent regression of TypeError.
  • Added test for JSON log (Z-suffixed timestamps) filtered with a naive --start-time to assert successful run and basic output presence.
  • Added test for a mixed log (naive syslog-style and tz-aware ISO timestamps) filtered with a naive --start-time to assert successful run.
  • Used temporary log files and a fresh CliRunner instance to isolate analyze command invocations in new tests.
tests/test_cli.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds timezone-aware datetime handling to the log analyzer CLI's time-filtering feature. The analyze command now ensures --start-time and --end-time are parsed as UTC-aware datetimes, and a new _align_to_utc utility normalizes parsed log entry timestamps to enable consistent comparisons during filtering.

Changes

Timezone-aware time filtering

Layer / File(s) Summary
Timezone utility import and helper function
src/log_analyzer_cli/cli.py
Import timezone module and add _align_to_utc(dt) helper that converts naive datetimes to tz-aware UTC while passing through already-aware datetimes unchanged.
CLI option parsing for time filters
src/log_analyzer_cli/cli.py
Update analyze command to parse --start-time and --end-time into tz-aware UTC datetimes by explicitly attaching timezone.utc when parsed timestamps are naive.
Log entry timestamp alignment during filtering
src/log_analyzer_cli/cli.py
Apply _align_to_utc to each parsed log entry timestamp before performing start_time/end_time range comparisons in _parse_file.
Integration tests for timezone-aware filtering
tests/test_cli.py
Add two new CLI integration tests: one with tz-aware JSON log entries and naive --start-time boundary, and one with mixed naive syslog and tz-aware ISO log entries. Both assert successful execution and expected output fields.

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 Timezones tamed with UTC grace,
Naive and aware now match in place,
Logs filtered true from start to end,
Where datetime boundaries blend! ✨⏰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 accurately describes the main change: attaching UTC timezone information to time-filter boundaries in the analyze command, which is the core fix addressing the TypeError from comparing naive and aware datetimes.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/parse-file-attach-tz-to-boundaries

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 and usage tips.

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The _align_to_utc docstring and name suggest that it converts any datetime to UTC, but it actually only tags naive datetimes and leaves aware non-UTC datetimes unchanged; consider renaming or clarifying the docstring to better reflect the behavior.
  • In the new tests, you create a fresh CliRunner and import cli_main instead of reusing the existing runner fixture and main import, which introduces inconsistency; consider using the shared fixtures and imports for these tests as well.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_align_to_utc` docstring and name suggest that it converts any datetime to UTC, but it actually only tags naive datetimes and leaves aware non-UTC datetimes unchanged; consider renaming or clarifying the docstring to better reflect the behavior.
- In the new tests, you create a fresh `CliRunner` and import `cli_main` instead of reusing the existing `runner` fixture and `main` import, which introduces inconsistency; consider using the shared fixtures and imports for these tests as well.

## Individual Comments

### Comment 1
<location path="src/log_analyzer_cli/cli.py" line_range="189-193" />
<code_context>
     return GenericParser()


+def _align_to_utc(dt: Optional[datetime]) -> Optional[datetime]:
+    """Align a datetime to UTC, making it tz-aware if it is naive."""
+    if dt is None:
+        return None
+    if dt.tzinfo is None:
+        return dt.replace(tzinfo=timezone.utc)
+    return dt
</code_context>
<issue_to_address>
**issue (bug_risk):** `_align_to_utc` name/docstring suggest UTC alignment but aware non-UTC datetimes are not converted.

The implementation only adds UTC to naive datetimes and leaves other tz-aware datetimes unchanged. As a result, a datetime in e.g. US/Eastern will stay in that timezone while `start_time`/`end_time` appear to be normalized to UTC, which can lead to mixed-timezone comparisons.

If the goal is to truly normalize everything to UTC, you could use:

```python
if dt is None:
    return None
if dt.tzinfo is None:
    dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc)
```

If you instead want to preserve non-UTC timezones, consider updating the name/docstring so it doesn’t imply full UTC conversion.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +189 to +193
def _align_to_utc(dt: Optional[datetime]) -> Optional[datetime]:
"""Align a datetime to UTC, making it tz-aware if it is naive."""
if dt is None:
return None
if dt.tzinfo is None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): _align_to_utc name/docstring suggest UTC alignment but aware non-UTC datetimes are not converted.

The implementation only adds UTC to naive datetimes and leaves other tz-aware datetimes unchanged. As a result, a datetime in e.g. US/Eastern will stay in that timezone while start_time/end_time appear to be normalized to UTC, which can lead to mixed-timezone comparisons.

If the goal is to truly normalize everything to UTC, you could use:

if dt is None:
    return None
if dt.tzinfo is None:
    dt = dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc)

If you instead want to preserve non-UTC timezones, consider updating the name/docstring so it doesn’t imply full UTC conversion.

@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: 2

🧹 Nitpick comments (1)
src/log_analyzer_cli/cli.py (1)

189-195: ⚡ Quick win

Make _align_to_utc behavior match its name/docstring.

Line [190] says “Align a datetime to UTC,” but Line [195] returns aware non-UTC datetimes unchanged. Either convert aware datetimes with astimezone(timezone.utc) or rename/doc it as “ensure tz-aware” to avoid future misuse.

🤖 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/log_analyzer_cli/cli.py` around lines 189 - 195, The _align_to_utc
function claims to align datetimes to UTC but currently leaves timezone-aware
datetimes unchanged; update its logic so that if dt.tzinfo is not None it
returns dt.astimezone(timezone.utc) (and keep the existing branch that sets
timezone.utc for naive datetimes), ensuring all returned datetimes are tz-aware
and in UTC while leaving the None -> None behavior intact.
🤖 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/log_analyzer_cli/cli.py`:
- Line 106: Two click.echo calls use no-op f-strings (e.g., click.echo(f"Error:
Invalid start-time format. Use YYYY-MM-DD HH:MM:SS", err=True)) which triggers
F541; change those f"..." literals to regular string literals (remove the
leading f) for both the start-time and the other similar end-time error message
occurrences so you call click.echo("Error: ...", err=True) instead.

In `@tests/test_cli.py`:
- Around line 110-128: The test
test_analyze_time_filter_mixed_naive_and_aware_log currently only asserts
result.exit_code == 0 which won't catch regressions in filtering; update the
test to also assert on result.output (from runner2.invoke of cli_main) to
confirm the expected behavior — for example, assert that the output contains a
report marker or the matching lines like "match me" and does NOT contain the "No
log entries found matching criteria" message (or similar negative message used
by the CLI). This ensures the call to cli_main in the test actually produced
filtered results rather than silently succeeding.

---

Nitpick comments:
In `@src/log_analyzer_cli/cli.py`:
- Around line 189-195: The _align_to_utc function claims to align datetimes to
UTC but currently leaves timezone-aware datetimes unchanged; update its logic so
that if dt.tzinfo is not None it returns dt.astimezone(timezone.utc) (and keep
the existing branch that sets timezone.utc for naive datetimes), ensuring all
returned datetimes are tz-aware and in UTC while leaving the None -> None
behavior intact.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e8fdea0-b7dd-418b-bd44-ce87c7222a29

📥 Commits

Reviewing files that changed from the base of the PR and between e93757f and d6e95f9.

📒 Files selected for processing (2)
  • src/log_analyzer_cli/cli.py
  • tests/test_cli.py

if start_dt.tzinfo is None:
start_dt = start_dt.replace(tzinfo=timezone.utc)
except ValueError:
click.echo(f"Error: Invalid start-time format. Use YYYY-MM-DD HH:MM:SS", err=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove no-op f-strings to unblock lint CI.

Line [106] and Line [116] use f"..." without placeholders, triggering F541 and failing CI.

Suggested fix
-                click.echo(f"Error: Invalid start-time format. Use YYYY-MM-DD HH:MM:SS", err=True)
+                click.echo("Error: Invalid start-time format. Use YYYY-MM-DD HH:MM:SS", err=True)
...
-                click.echo(f"Error: Invalid end-time format. Use YYYY-MM-DD HH:MM:SS", err=True)
+                click.echo("Error: Invalid end-time format. Use YYYY-MM-DD HH:MM:SS", err=True)

Also applies to: 116-116

🧰 Tools
🪛 GitHub Actions: CI / 1_test (3.11).txt

[error] 106-106: flake8 F541 f-string is missing placeholders

🪛 GitHub Actions: CI / 3_test (3.12).txt

[error] 106-106: flake8 F541 f-string is missing placeholders

🪛 GitHub Actions: CI / test (3.11)

[error] 106-106: F541 f-string is missing placeholders

🪛 GitHub Actions: CI / test (3.12)

[error] 106-106: flake8 F541 f-string is missing placeholders

🪛 Ruff (0.15.15)

[error] 106-106: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 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/log_analyzer_cli/cli.py` at line 106, Two click.echo calls use no-op
f-strings (e.g., click.echo(f"Error: Invalid start-time format. Use YYYY-MM-DD
HH:MM:SS", err=True)) which triggers F541; change those f"..." literals to
regular string literals (remove the leading f) for both the start-time and the
other similar end-time error message occurrences so you call click.echo("Error:
...", err=True) instead.

Sources: Linters/SAST tools, Pipeline failures

Comment thread tests/test_cli.py
Comment on lines +110 to +128
def test_analyze_time_filter_mixed_naive_and_aware_log(self, runner, tmp_path):
# Same boundary/parsed mismatch, but the log itself contains a mix of
# naive "YYYY-MM-DD HH:MM:SS" syslog lines and tz-aware ISO lines.
# Before the fix, the first cross-naive/aware comparison raised TypeError.
from click.testing import CliRunner
from log_analyzer_cli.cli import main as cli_main
runner2 = CliRunner()
log = tmp_path / "mixed.log"
log.write_text(
"2026-04-20 10:30:00 system kernel: match me\n"
"2026-04-20T10:30:05Z INFO also match\n"
"2025-01-01 00:00:00 system kernel: too old\n"
)
result = runner2.invoke(
cli_main,
["analyze", str(log), "--start-time", "2026-01-01 00:00:00"],
)
assert result.exit_code == 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen the mixed naive/aware test assertion.

Line [127] only checks exit code. This can pass even when filtering behavior regresses. Add at least one output assertion (e.g., report marker or absence of “No log entries found matching criteria”) so the test validates the intended execution path.

🤖 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/test_cli.py` around lines 110 - 128, The test
test_analyze_time_filter_mixed_naive_and_aware_log currently only asserts
result.exit_code == 0 which won't catch regressions in filtering; update the
test to also assert on result.output (from runner2.invoke of cli_main) to
confirm the expected behavior — for example, assert that the output contains a
report marker or the matching lines like "match me" and does NOT contain the "No
log entries found matching criteria" message (or similar negative message used
by the CLI). This ensures the call to cli_main in the test actually produced
filtered results rather than silently succeeding.

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