Skip to content

filter_lines: drop tzinfo before comparing parsed timestamps to --start-time/--end-time - #29

Open
HrachShah wants to merge 2 commits into
mainfrom
fix/filter-lines-tz-aware-vs-naive-compare
Open

filter_lines: drop tzinfo before comparing parsed timestamps to --start-time/--end-time#29
HrachShah wants to merge 2 commits into
mainfrom
fix/filter-lines-tz-aware-vs-naive-compare

Conversation

@HrachShah

@HrachShah HrachShah commented Jul 8, 2026

Copy link
Copy Markdown
Owner

The previous compare-tz-aware-to-naive path raised TypeError on <=>/< between a tz-aware log timestamp and a naive CLI bound, so any line with an offset (e.g. Apache/JSON logs with +00:00) silently disappeared from filtered output. cli._parse_file already handles this with timestamp.replace(tzinfo=None) when a tz is attached; mirror that here in the underlying filter_lines helper so the helper, the CLI, and any direct library caller all agree on how a tz-aware log entry is compared against a tz-naive bound.

compare_ts = (
    timestamp.replace(tzinfo=None)
    if timestamp.tzinfo
    else timestamp
)

Two new TestFilterLinesTzAware cases pin the behaviour: a 15:00:00+00:00 entry is correctly dropped against a naive 16:00:00 start, and a 17:00:00+00:00 entry is kept. Full suite: 75 passed (was 73 + 2 new).

Summary by Sourcery

Improve timestamp parsing and time-bound filtering to correctly handle timezone-aware log entries against naive start/end times.

New Features:

  • Support parsing Apache common log timestamps that include an optional timezone offset.
  • Support parsing ISO 8601 timestamps with fractional seconds and explicit timezones without dropping microseconds or timezone information.

Bug Fixes:

  • Prevent TypeError and ensure correct inclusion/exclusion when filtering tz-aware log entries against naive --start-time/--end-time bounds in both filter_lines and cli._parse_file.

Tests:

  • Add unit tests for parse_timestamp covering ISO 8601 and Apache formats with timezones and microseconds.
  • Add tests for filter_lines to verify consistent handling of tz-aware entries compared to cli._parse_file when using naive time bounds.

Summary by CodeRabbit

  • Bug Fixes

    • Improved timestamp handling so logs with time zones and fractional seconds are parsed more reliably.
    • Fixed time-range filtering to handle timezone-aware log entries without errors when comparing against start/end bounds.
    • Expanded support for more common timestamp formats, including ISO 8601, Apache-style times, syslog entries, and slash-separated dates.
  • Tests

    • Added coverage for timestamp parsing, log level detection, error-pattern normalization, and time-based filtering behavior.

Zo Bot added 2 commits July 4, 2026 17:31
…rt-time/--end-time

The previous compare-tz-aware-to-naive path raised TypeError on
`<=>/<` between a tz-aware log timestamp and a naive CLI bound
("Cannot compare tz-naive and tz-aware datetime-like objects"),
so any line with an offset (e.g. Apache/JSON logs with +00:00)
silently disappeared from filtered output. cli._parse_file already
handles this with `timestamp.replace(tzinfo=None)` when a tz is
attached; mirror that here in the underlying filter_lines helper
so the helper, the CLI, and any direct library caller all agree on
how a tz-aware log entry is compared against a tz-naive bound.

Two new TestFilterLinesTzAware cases pin the behaviour: a
15:00:00+00:00 entry is correctly dropped against a naive 16:00:00
start, and a 17:00:00+00:00 entry is kept.
@sourcery-ai

sourcery-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Aligns timestamp parsing and filtering between the core utils and CLI by properly handling timezone-aware datetimes (including Apache-style and ISO 8601 with offsets), and normalizes tz-aware timestamps to naive for comparison against naive start/end bounds, with new tests to lock in parsing and filtering behavior.

Sequence diagram for tz-aware timestamp filtering in CLI and utils

sequenceDiagram
    actor User
    participant CLI as cli._parse_file
    participant Utils as utils.filter_lines
    participant Parser as parse_timestamp

    User->>CLI: _parse_file(start_time, end_time)
    loop for each line
        CLI->>Parser: parse_timestamp(line)
        Parser-->>CLI: timestamp
        alt [start_time or end_time]
            alt [timestamp and timestamp.tzinfo]
                CLI->>CLI: timestamp.replace(tzinfo=None) as compare_ts
            else [timestamp is naive or None]
                CLI->>CLI: compare_ts = timestamp
            end
            CLI->>CLI: compare_ts < start_time?
            CLI->>CLI: compare_ts > end_time?
        end
    end

    User->>Utils: filter_lines(lines, start_time, end_time)
    loop for each line
        Utils->>Parser: parse_timestamp(line)
        Parser-->>Utils: timestamp
        alt [start_time]
            alt [timestamp and timestamp.tzinfo]
                Utils->>Utils: timestamp.replace(tzinfo=None) as compare_ts
            else [timestamp is naive or None]
                Utils->>Utils: compare_ts = timestamp
            end
            Utils->>Utils: skip if compare_ts < start_time
        end
        alt [end_time]
            alt [timestamp and timestamp.tzinfo]
                Utils->>Utils: timestamp.replace(tzinfo=None) as compare_ts
            else [timestamp is naive or None]
                Utils->>Utils: compare_ts = timestamp
            end
            Utils->>Utils: skip if compare_ts > end_time
        end
    end
Loading

File-Level Changes

Change Details Files
Enhance timestamp parsing to correctly handle ISO 8601 and Apache log formats with timezones and microseconds without losing precision or tzinfo.
  • Extend the regex used by parse_timestamp to recognize Apache common log timestamps with optional numeric timezone offsets, with or without colon.
  • Reorder and expand datetime format strings in _try_parse_datetime to prefer the most specific formats, adding explicit patterns for ISO 8601 timestamps with microseconds and timezones, and Apache/syslog variants with optional offsets.
  • Document the rationale for the ordered list of formats to avoid silently dropping fractional seconds or timezone information.
src/log_analyzer_cli/utils.py
Normalize timezone-aware timestamps to naive before comparing against naive start_time/end_time bounds in filtering helpers and CLI code.
  • In filter_lines, introduce a compare_ts variable that strips tzinfo from aware timestamps before comparing against start_time and end_time, preventing TypeError and ensuring consistent behavior for aware vs naive entries.
  • In cli._parse_file, mirror the same compare_ts normalization logic when applying --start-time/--end-time bounds so CLI and helper behavior are aligned.
  • Guard comparisons so that they only occur when both the bound and the (possibly normalized) timestamp are present.
src/log_analyzer_cli/utils.py
src/log_analyzer_cli/cli.py
Add comprehensive tests covering timestamp parsing (including tz-aware/naive cases) and tz-aware filtering behavior.
  • Introduce TestParseTimestamp to validate parsing of ISO 8601 timestamps with/without microseconds, with Z and numeric offsets, Apache common log timestamps with optional offsets, syslog-like formats, and behavior when no timestamp is present.
  • Add tests ensuring fractional seconds and timezone offsets are preserved and not silently truncated or stripped.
  • Add TestFilterLinesTzAware to pin behavior when comparing tz-aware log entries against naive start_time bounds (entries before the bound are dropped, after the bound are kept).
  • Add regression/coverage tests for detect_log_level and normalize_error_pattern to ensure they continue working as expected.
tests/test_utils.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 Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Timestamp parsing in utils.py was extended to support optional timezone offsets and additional datetime formats. Filtering logic in filter_lines (utils.py) and _parse_file (cli.py) now normalizes tz-aware timestamps to tz-naive before comparing against start_time/end_time bounds. New tests validate parsing and filtering behavior.

Changes

Timezone-aware timestamp handling

Layer / File(s) Summary
Timestamp parsing format support
src/log_analyzer_cli/utils.py
Apache log regex now allows optional timezone offsets; _try_parse_datetime format list expanded/reordered for ISO 8601, Apache, syslog, and slash-separated formats with timezone and fractional-second preservation.
Timezone-aware filtering in filter_lines and CLI
src/log_analyzer_cli/utils.py, src/log_analyzer_cli/cli.py
filter_lines and _parse_file normalize tz-aware parsed timestamps to tz-naive before comparing against start_time/end_time bounds.
Tests for timestamp parsing, filtering, and log utilities
tests/test_utils.py
New test suite covers parse_timestamp variants, detect_log_level, normalize_error_pattern, and filter_lines tz-aware/naive comparison behavior.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: normalizing tz-aware timestamps before start/end-time filtering.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/filter-lines-tz-aware-vs-naive-compare

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.

@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 left some high level feedback:

  • The tz-normalization logic for timestamp comparisons is now duplicated between filter_lines and cli._parse_file; consider extracting a small helper (e.g., normalize_timestamp_for_bounds(timestamp)) to keep the behavior consistent and reduce repetition.
  • In filter_lines, compare_ts is recomputed separately for the start and end checks; you could compute it once when timestamp and either bound are present to avoid redundant work and simplify the conditional flow.
  • The updated _try_parse_datetime formats and timestamp_patterns comment on ordering and specificity are helpful; you might also add a brief note in the regex for ISO timestamps clarifying that both Z and numeric offsets are supported, mirroring the detail given for Apache common log.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The tz-normalization logic for timestamp comparisons is now duplicated between `filter_lines` and `cli._parse_file`; consider extracting a small helper (e.g., `normalize_timestamp_for_bounds(timestamp)`) to keep the behavior consistent and reduce repetition.
- In `filter_lines`, `compare_ts` is recomputed separately for the start and end checks; you could compute it once when `timestamp` and either bound are present to avoid redundant work and simplify the conditional flow.
- The updated `_try_parse_datetime` formats and `timestamp_patterns` comment on ordering and specificity are helpful; you might also add a brief note in the regex for ISO timestamps clarifying that both `Z` and numeric offsets are supported, mirroring the detail given for Apache common log.

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.

@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 (2)
tests/test_utils.py (1)

144-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add end_time test coverage for tz-aware entries.

The suite validates start_time filtering with tz-aware entries but doesn't test end_time. The normalization code path for end_time (lines 220-227 in utils.py) is symmetric but untested.

💚 Suggested additional test
     def test_filter_lines_tz_aware_entry_after_naive_start(self) -> None:
         lines = ["2025-10-10T17:00:00+00:00 INFO kept"]
         start = datetime(2025, 10, 10, 16, 0, 0)
         results = list(filter_lines(iter(lines), start_time=start))
         assert len(results) == 1
         assert results[0][1] == "2025-10-10T17:00:00+00:00 INFO kept"
+
+    def test_filter_lines_tz_aware_entry_after_naive_end(self) -> None:
+        """A tz-aware entry at 17:00:00+00:00 must be dropped when
+        compared against a naive end_time of 16:00:00."""
+        lines = ["2025-10-10T17:00:00+00:00 INFO hello"]
+        end = datetime(2025, 10, 10, 16, 0, 0)
+        results = list(filter_lines(iter(lines), end_time=end))
+        assert results == []
+
+    def test_filter_lines_tz_aware_entry_before_naive_end(self) -> None:
+        """A tz-aware entry at 15:00:00+00:00 must be kept when
+        compared against a naive end_time of 16:00:00."""
+        lines = ["2025-10-10T15:00:00+00:00 INFO kept"]
+        end = datetime(2025, 10, 10, 16, 0, 0)
+        results = list(filter_lines(iter(lines), end_time=end))
+        assert len(results) == 1
+        assert results[0][1] == "2025-10-10T15:00:00+00:00 INFO 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 `@tests/test_utils.py` around lines 144 - 166, Add test coverage for
timezone-aware entries against end_time in TestFilterLinesTzAware, mirroring the
existing start_time cases so the same normalization path is exercised in
filter_lines. Create one test where a tz-aware log entry before a naive end_time
is kept and one where a tz-aware entry after the naive end_time is dropped,
asserting the behavior matches the start_time handling and does not raise a
timezone comparison error.
src/log_analyzer_cli/utils.py (1)

208-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared tz-aware normalization helper to eliminate duplication.

The tz-aware-to-naive normalization logic is duplicated between filter_lines here and _parse_file in cli.py (lines 217-225). Extracting a small helper would keep the two paths consistent and prevent future drift.

♻️ Proposed helper extraction
+def _normalize_to_naive(timestamp: Optional[datetime]) -> Optional[datetime]:
+    """Strip tzinfo from a tz-aware timestamp for naive bound comparison."""
+    if timestamp and timestamp.tzinfo:
+        return timestamp.replace(tzinfo=None)
+    return timestamp
+
 def filter_lines(
     lines: Generator[str, None, None],
     include_levels: Optional[list[str]] = None,
     exclude_levels: Optional[list[str]] = None,
     search_pattern: Optional[str] = None,
     start_time: Optional[datetime] = None,
     end_time: Optional[datetime] = None,
 ) -> Generator[tuple[int, str, Optional[datetime], str], None]:
     """Filter log lines based on various criteria.
     ...
     """
     compiled_pattern = re.compile(search_pattern) if search_pattern else None

     for line_num, line in enumerate(lines, 1):
         line = line.rstrip("\n\r")
         if not line:
             continue

         level = detect_log_level(line)

         if include_levels and level not in include_levels:
             continue

         if exclude_levels and level in exclude_levels:
             continue

         if compiled_pattern and not compiled_pattern.search(line):
             continue

         timestamp = parse_timestamp(line)

-        if start_time and timestamp:
-            # Normalize a tz-aware timestamp to naive so a naive --start-time
-            # bound can be compared against log entries with a UTC offset,
-            # while preserving the existing behavior for tz-naive entries.
-            compare_ts = (
-                timestamp.replace(tzinfo=None)
-                if timestamp.tzinfo
-                else timestamp
-            )
-            if compare_ts < start_time:
-                continue
-
-        if end_time and timestamp:
-            compare_ts = (
-                timestamp.replace(tzinfo=None)
-                if timestamp.tzinfo
-                else timestamp
-            )
-            if compare_ts > end_time:
-                continue
+        if start_time and timestamp:
+            compare_ts = _normalize_to_naive(timestamp)
+            if compare_ts < start_time:
+                continue
+
+        if end_time and timestamp:
+            compare_ts = _normalize_to_naive(timestamp)
+            if compare_ts > end_time:
+                continue

         yield line_num, line, timestamp, level

Then in cli.py:_parse_file, replace the inline normalization with the same helper:

-        compare_ts = timestamp.replace(tzinfo=None) if (timestamp and timestamp.tzinfo) else timestamp
+        compare_ts = _normalize_to_naive(timestamp)
🤖 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/utils.py` around lines 208 - 227, The tz-aware-to-naive
timestamp normalization is duplicated in filter_lines and _parse_file, so
extract that shared logic into a small helper and use it in both places. Create
a single normalization function near the existing timestamp handling in
utils.py, then update the comparisons in filter_lines and the corresponding code
in cli.py to call that helper so both paths stay consistent and avoid future
drift.
🤖 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 `@tests/test_utils.py`:
- Around line 7-14: Remove the unused imports in the test_utils module to
satisfy flake8 F401. In the import block that pulls in pytest and symbols from
log_analyzer_cli.utils, delete pytest and read_log_file if they are not
referenced anywhere else in the file. Keep only the imports actually used by the
test helpers/functions in this module.

---

Nitpick comments:
In `@src/log_analyzer_cli/utils.py`:
- Around line 208-227: The tz-aware-to-naive timestamp normalization is
duplicated in filter_lines and _parse_file, so extract that shared logic into a
small helper and use it in both places. Create a single normalization function
near the existing timestamp handling in utils.py, then update the comparisons in
filter_lines and the corresponding code in cli.py to call that helper so both
paths stay consistent and avoid future drift.

In `@tests/test_utils.py`:
- Around line 144-166: Add test coverage for timezone-aware entries against
end_time in TestFilterLinesTzAware, mirroring the existing start_time cases so
the same normalization path is exercised in filter_lines. Create one test where
a tz-aware log entry before a naive end_time is kept and one where a tz-aware
entry after the naive end_time is dropped, asserting the behavior matches the
start_time handling and does not raise a timezone comparison error.
🪄 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: 74a9462a-01e3-4e6a-8399-8d6d2cda89df

📥 Commits

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

📒 Files selected for processing (3)
  • src/log_analyzer_cli/cli.py
  • src/log_analyzer_cli/utils.py
  • tests/test_utils.py

Comment thread tests/test_utils.py
Comment on lines +7 to +14
import pytest

from log_analyzer_cli.utils import (
detect_log_level,
filter_lines,
normalize_error_pattern,
parse_timestamp,
read_log_file,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔴 Critical | ⚡ Quick win

Remove unused imports causing CI failures.

pytest and read_log_file are imported but never used. flake8 F401 is failing CI on both Python 3.10 and 3.11.

🛡️ Proposed fix
 from datetime import datetime, timezone, timedelta
 
-import pytest
-
 from log_analyzer_cli.utils import (
     detect_log_level,
     filter_lines,
     normalize_error_pattern,
     parse_timestamp,
-    read_log_file,
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
from log_analyzer_cli.utils import (
detect_log_level,
filter_lines,
normalize_error_pattern,
parse_timestamp,
read_log_file,
import pytest
from log_analyzer_cli.utils import (
detect_log_level,
filter_lines,
normalize_error_pattern,
parse_timestamp,
)
🧰 Tools
🪛 GitHub Actions: CI / 1_test (3.10).txt

[error] 7-7: F401 'pytest' imported but unused


[error] 9-9: F401 'log_analyzer_cli.utils.read_log_file' imported but unused

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

[error] 7-7: flake8 (F401) 'pytest' imported but unused


[error] 9-9: flake8 (F401) 'log_analyzer_cli.utils.read_log_file' imported but unused

🪛 GitHub Actions: CI / test (3.10)

[error] 7-7: flake8: F401 'pytest' imported but unused


[error] 9-9: flake8: F401 'log_analyzer_cli.utils.read_log_file' imported but unused

🪛 GitHub Actions: CI / test (3.11)

[error] 7-7: flake8 F401 'pytest' imported but unused


[error] 9-9: flake8 F401 'log_analyzer_cli.utils.read_log_file' imported but unused

🤖 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_utils.py` around lines 7 - 14, Remove the unused imports in the
test_utils module to satisfy flake8 F401. In the import block that pulls in
pytest and symbols from log_analyzer_cli.utils, delete pytest and read_log_file
if they are not referenced anywhere else in the file. Keep only the imports
actually used by the test helpers/functions in this module.

Source: Pipeline failures

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