Normalize absolute date-time values in filters#166
Conversation
Relative dates (e.g. `now-30d`) are already normalized to the stored "YYYY-MM-DD HH:mm:ss" UTC format at parse time, but absolute ISO values from a filter (e.g. `published_at:>'2025-02-27T19:03:00.000-05:00'`) were passed through untouched. On SQLite datetimes are stored as text and compared lexically, so the "T" sorts after the stored space separator and the comparison returns the wrong rows; on MySQL the timezone offset was dropped rather than applied. Normalize absolute date-time values to the same stored format in the `VALUE` grammar rule, next to the existing relative-date handling, so date comparisons behave identically across SQLite and MySQL. Only full ISO-8601 date-times (a date with a time component) are rewritten — bare dates and other plain strings are left untouched, since nql-lang has no column-type information and must not corrupt a legitimate non-date value. Fixes TryGhost/Ghost#23441
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
WalkthroughThis PR adds absolute date-time normalization to nql-lang. A new Estimated code review effort: 2 (Simple) | ~12 minutes Sequence Diagram(s)sequenceDiagram
participant nql_y as nql.y VALUE rule
participant scope_js as scope.normalizeAbsoluteDate
nql_y->>scope_js: LITERAL/STRING value
scope_js-->>nql_y: normalized UTC date or original value
Related issues: Suggested labels: bug, nql-lang Suggested reviewers: kevinansfield, daniellockyer 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #166 +/- ##
==========================================
+ Coverage 84.18% 84.53% +0.34%
==========================================
Files 9 9
Lines 2074 2127 +53
Branches 428 437 +9
==========================================
+ Hits 1746 1798 +52
Misses 322 322
- Partials 6 7 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Reject calendar-invalid dates (e.g. Feb 30) instead of letting new Date roll them over to a different day - Enforce hour/minute/second ranges in the ISO regex so out-of-range times (T24:00, 19:60) pass through instead of rolling over - Require the T separator: space-separated values are either already in the stored format or arbitrary text, and only the ISO T form exhibits the comparison bug being fixed - Skip normalization when preserveRelativeDates is set, so the lossless parse mode also preserves absolute date-times
Problem
Filtering by an absolute date returns the wrong rows on SQLite. Using
filter="published_at:>'...'"in a theme's{{#get}}, or the same filter via the Content/Admin API, includes posts from the wrong side of the boundary. Fixes TryGhost/Ghost#23441.Root cause
Dates are stored as
YYYY-MM-DD HH:MM:SS(UTC). Relative dates (now-30d) are already normalized to that format at parse time (scope.js: relDateToAbsolute→formatDateForSQL), but absolute ISO values from a filter (e.g.published_at:>'2025-02-27T19:03:00.000-05:00') were passed through untouched:Tsorts after the stored space separator (T>) and the comparison returns the wrong rows.Fix
Normalize absolute date-time values to the stored
YYYY-MM-DD HH:mm:ssUTC format in theVALUEgrammar rule — right next to the existing relative-date handling — so the value is canonicalized once at parse time and SQLite and MySQL receive identical input. No per-dialect logic.Only full ISO-8601 date-times (a date with a time component, optionally with fractional seconds and a
Z/±HH:MMzone) are rewritten. Bare dates (2025-02-27), plain strings, and non-date values are left untouched, because nql-lang has no column-type information and must not corrupt a legitimate non-date value (e.g. a date-like slug). A zone-less date-time is interpreted as UTC (dates are stored in UTC); anything that fails to parse is returned unchanged.Notes for reviewers
'2025-02-27'to2025-02-27 00:00:00while SQLite compares it as text, so there is a pre-existing behavior difference for date-only boundaries. Normalizing bare dates universally would risk rewriting legitimate non-date string filters, so it's deliberately out of scope here — happy to discuss if we'd rather align that too.dist/parser.js) is checked in and rebuilt viayarn build.Tests
Added unit tests for
normalizeAbsoluteDate(offset/Zulu/no-seconds/fractional/zone-less/idempotent/bare-date/non-date/unparseable/non-string) and parser-level tests covering>/</equality/$in/logical-group normalization plus the bare-date and non-date pass-through. Full suite passes and lint is clean.