Skip to content

fix: parse compact ISO 8601 date/datetime string formats#289

Merged
DecisionNerd merged 2 commits intomainfrom
fix/273-compact-iso8601-formats
Feb 26, 2026
Merged

fix: parse compact ISO 8601 date/datetime string formats#289
DecisionNerd merged 2 commits intomainfrom
fix/273-compact-iso8601-formats

Conversation

@DecisionNerd
Copy link
Owner

@DecisionNerd DecisionNerd commented Feb 26, 2026

Closes #273

Summary

  • Add _parse_iso_date_part(s) helper handling all compact ISO 8601 date forms: ordinal (YYYY-DDD, YYYYDDD), week date (YYYY-Www[-D], YYYYWww[D]), year-month (YYYY-MM, YYYYMM), and year-only (YYYY)
  • Add _normalize_compact_time(t) helper expanding compact time strings (HH, HHMM, HHMMSS) to HH:MM:SS form (with optional fractional seconds and timezone passthrough)
  • Update CypherDate.__init__ to use _parse_iso_date_part instead of dateutil directly
  • Update CypherDateTime.__init__ to split on T, normalise date and time parts independently, then re-assemble before passing to dateutil

Fixes

14 TCK failures in should_parse_date_from_string and should_parse_local_date_time_from_string.

Test plan

  • 9 integration tests for compact date formats (TestCompactISO8601Date)
  • 5 integration tests for compact datetime formats (TestCompactISO8601LocalDateTime)
  • All 18 TCK date/datetime string scenarios now pass (was 4/18)
  • 3609 total tests pass, 88.67% coverage
  • make pre-push green

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced date and datetime parsing to support more ISO 8601 format variants, including compact representations (year-only, year-month, ordinal, and week-based formats).
  • Tests

    • Added integration tests validating date and datetime parsing across various ISO 8601 formats.

Adds _parse_iso_date_part() and _normalize_compact_time() helpers in
types/values.py to handle compact ISO 8601 variants not supported by
dateutil: ordinal (YYYY-DDD, YYYYDDD), week (YYYY-Www[-D], YYYYWww[D]),
year-month (YYYY-MM, YYYYMM), and year-only (YYYY).

CypherDate now uses _parse_iso_date_part() instead of dateutil directly.
CypherDateTime splits on 'T', normalises each part, then re-assembles
before passing to dateutil, handling compact date and time components.

Fixes 14 TCK failures in should_parse_date_from_string and
should_parse_local_date_time_from_string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Warning

Rate limit exceeded

@DecisionNerd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b8e92c7 and 2424893.

📒 Files selected for processing (2)
  • src/graphforge/types/values.py
  • tests/integration/test_compact_iso8601.py

Walkthrough

This PR adds support for compact ISO 8601 date/datetime string formats (ordinal, week-based, year-month, year-only, compact time) by introducing helper functions _parse_iso_date_part() and _normalize_compact_time() to normalize compact formats, and updating CypherDate and CypherDateTime classes to use them. Comprehensive integration tests validate the new parsing capabilities.

Changes

Cohort / File(s) Summary
ISO 8601 Compact Format Parser
src/graphforge/types/values.py
Adds _parse_iso_date_part() to parse compact/extended ISO 8601 date variants (ordinal, week-based, year-month, year-only) with regex preprocessing, and _normalize_compact_time() to expand compact time strings (HH, HHMM, HHMMSS, with optional fractional seconds and timezone). Updates CypherDate and CypherDateTime to use these helpers instead of relying solely on dateutil.parse.
Integration Tests for Compact ISO 8601
tests/integration/test_compact_iso8601.py
Adds TestCompactISO8601Date and TestCompactISO8601LocalDateTime test classes validating parsing of compact/extended formats via GraphForge queries, including ordinal (YYYYDDD), week-based (YYYYWwwD), year-month (YYYYMM), year-only, and combined datetime variants with compact time representations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for compact ISO 8601 date/datetime string format parsing.
Description check ✅ Passed The description includes a clear summary of changes, specific helper functions added, issue references, and comprehensive test plan with results.
Linked Issues check ✅ Passed The PR fully addresses issue #273 objectives: implements _parse_iso_date_part and _normalize_compact_time to handle all compact ISO 8601 variants (ordinal, week, year-month, year-only, and compact datetime formats) and resolves 14 TCK test failures.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving issue #273: two new helper functions in values.py and integration tests for compact ISO 8601 parsing, with no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/273-compact-iso8601-formats

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.

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/integration/test_compact_iso8601.py (1)

12-99: Parametrize repeated cases to reduce duplication.

Line 12 onward repeats the same test flow with only input/expected values changing. Converting both classes to @pytest.mark.parametrize would keep coverage while reducing maintenance overhead.

♻️ Example refactor pattern
+@pytest.mark.parametrize(
+    ("expr", "alias", "expected"),
+    [
+        ("date('2015')", "d", "2015-01-01"),
+        ("date('2015-07')", "d", "2015-07-01"),
+        ("date('201507')", "d", "2015-07-01"),
+        ("date('2015-202')", "d", "2015-07-21"),
+        ("date('2015202')", "d", "2015-07-21"),
+        ("date('2015-W30-2')", "d", "2015-07-21"),
+        ("date('2015W302')", "d", "2015-07-21"),
+        ("date('2015-W30')", "d", "2015-07-20"),
+        ("date('2015W30')", "d", "2015-07-20"),
+    ],
+)
+def test_compact_iso8601_date_cases(expr, alias, expected):
+    gf = GraphForge()
+    r = gf.execute(f"RETURN {expr} AS {alias}")
+    assert r[0][alias].value.isoformat() == expected

As per coding guidelines: "Use @pytest.mark.parametrize for testing the same logic with different inputs to avoid code duplication".

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

In `@tests/integration/test_compact_iso8601.py` around lines 12 - 99, Multiple
test methods in TestCompactISO8601 and TestCompactISO8601LocalDateTime duplicate
the same pattern; replace them with parametrized tests using
`@pytest.mark.parametrize` to supply (input_string, expected_iso) pairs and a
single test function that creates GraphForge, calls gf.execute("RETURN
date(...)"/"RETURN localdatetime(...)") and asserts r[0][...].value.isoformat()
== expected_iso; target the classes TestCompactISO8601 and
TestCompactISO8601LocalDateTime and consolidate the current test_* methods
(e.g., test_year_only, test_year_month_separator, test_year_month_compact,
test_ordinal_separator, test_ordinal_compact, test_week_date_with_day_separator,
test_week_date_with_day_compact, test_week_date_no_day_separator,
test_week_date_no_day_compact and the localdatetime variants) into two
parametrized test functions while keeping `@pytest.mark.integration` on the
classes or tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/graphforge/types/values.py`:
- Around line 332-339: The code allows unsupported compact-time lengths to
silently pass through by returning t + frac + tz; instead, detect any unexpected
length of the compact time string (variable t) and raise a clear ValueError
describing the offending input and its length so malformed inputs fail fast.
Replace the final fallback return with raising ValueError(f"unsupported compact
time length {len(t)} for input: {t}") (and keep the existing branches for
lengths 2, 4, 6 intact); update any callers/tests to expect the exception where
appropriate.
- Around line 276-285: The ordinal-date parsing blocks that match
r"^(\d{4})-(\d{3})$" and r"^(\d{4})(\d{3})$" must validate the extracted ordinal
before adding days; after extracting year = int(m.group(1)) and ordinal =
int(m.group(2)), compute the year's max ordinal (use 365/366 via
calendar.isleap(year) or equivalent) and if ordinal < 1 or ordinal > max_ordinal
raise a ValueError (or return an error) instead of blindly doing
datetime.date(year,1,1) + datetime.timedelta(...); only perform the date
arithmetic when the ordinal is in-range.

---

Nitpick comments:
In `@tests/integration/test_compact_iso8601.py`:
- Around line 12-99: Multiple test methods in TestCompactISO8601 and
TestCompactISO8601LocalDateTime duplicate the same pattern; replace them with
parametrized tests using `@pytest.mark.parametrize` to supply (input_string,
expected_iso) pairs and a single test function that creates GraphForge, calls
gf.execute("RETURN date(...)"/"RETURN localdatetime(...)") and asserts
r[0][...].value.isoformat() == expected_iso; target the classes
TestCompactISO8601 and TestCompactISO8601LocalDateTime and consolidate the
current test_* methods (e.g., test_year_only, test_year_month_separator,
test_year_month_compact, test_ordinal_separator, test_ordinal_compact,
test_week_date_with_day_separator, test_week_date_with_day_compact,
test_week_date_no_day_separator, test_week_date_no_day_compact and the
localdatetime variants) into two parametrized test functions while keeping
`@pytest.mark.integration` on the classes or tests.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7b0e7 and b8e92c7.

📒 Files selected for processing (2)
  • src/graphforge/types/values.py
  • tests/integration/test_compact_iso8601.py

@codecov
Copy link

codecov bot commented Feb 26, 2026

Codecov Report

❌ Patch coverage is 93.50649% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.04%. Comparing base (8e7b0e7) to head (2424893).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #289      +/-   ##
==========================================
+ Coverage   90.00%   90.04%   +0.03%     
==========================================
  Files          38       38              
  Lines       10379    10495     +116     
  Branches     2130     2149      +19     
==========================================
+ Hits         9342     9450     +108     
  Misses        728      728              
- Partials      309      317       +8     
Flag Coverage Δ
full-coverage 90.04% <93.50%> (+0.03%) ⬆️

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

Components Coverage Δ
parser 96.42% <ø> (ø)
planner 94.88% <ø> (ø)
executor 86.06% <ø> (ø)
storage 93.31% <ø> (ø)
ast 96.18% <ø> (ø)
types 96.13% <93.50%> (-0.88%) ⬇️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e7b0e7...2424893. Read the comment docs.

- _parse_iso_date_part: validate ordinal day is in range [1, 365/366]
  before constructing date; raises ValueError for out-of-range values
  instead of silently rolling into adjacent years
- _normalize_compact_time: raise ValueError for unexpected compact time
  digit lengths (not 2, 4, or 6) instead of passing through to dateutil
- Add 4 validation tests covering both error paths and leap year edge case

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DecisionNerd DecisionNerd merged commit c2f38c7 into main Feb 26, 2026
23 checks passed
@DecisionNerd DecisionNerd deleted the fix/273-compact-iso8601-formats branch February 26, 2026 05:10
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.

fix: compact ISO 8601 date/datetime string formats not parsed (ordinal, week, year-month, year-only)

1 participant