Skip to content

Fix date math rounding to respect inclusive/exclusive bracket boundaries#129

Merged
niemyjski merged 3 commits intomainfrom
fix/inclusive-exclusive-bracket-rounding
Feb 16, 2026
Merged

Fix date math rounding to respect inclusive/exclusive bracket boundaries#129
niemyjski merged 3 commits intomainfrom
fix/inclusive-exclusive-bracket-rounding

Conversation

@niemyjski
Copy link
Member

Summary

  • Fix bracket-aware rounding in TwoPartFormatParser: Previously, rounding (/d, /M, /h, etc.) always rounded down for the start side and up for the end side regardless of bracket type. Now follows Elasticsearch conventions where inclusive brackets ([, ]) maximize the matched range and exclusive brackets ({, }) minimize it.
  • Support mixed bracket pairs: [start TO end} and {start TO end] are now valid, matching Elasticsearch Lucene query syntax. Previously these were rejected as mismatched.
  • Performance: Replaced regex pre-scan with a simple backwards character scan to detect the closing bracket. Wildcard parsers still receive positional isUpperLimit since they use it for min/max semantics, not rounding.
  • Documentation: Added a "Rounding with Inclusive/Exclusive Ranges" section to README with a reference table and common date range patterns.

Rounding Rules

Boundary Bracket Rounding Elasticsearch equivalent
Min (start) [ inclusive Round down (start of period) gte
Min (start) { exclusive Round up (end of period) gt
Max (end) ] inclusive Round up (end of period) lte
Max (end) } exclusive Round down (start of period) lt

Examples

  • [now/d TO now/d] — entire current day (start-of-day to end-of-day)
  • [now/d TO now/d} — start-of-day to start-of-day (empty/collapsed)
  • {now/d TO now/d] — end-of-day to end-of-day (empty/collapsed)
  • [now-1d/d TO now/d} — start of yesterday to start of today (lt semantics on end)

Ref: FoundatioFx/Foundatio.Lucene@a8426ab

Test plan

  • All 684 existing tests pass (no regressions)
  • New TwoPartFormatParserTests: exclusive brackets ({jan TO feb}, {2012 TO 2013}), mixed brackets ([2012 TO 2013}, {2012 TO 2013]), wildcard with exclusive brackets ({2012 TO *})
  • New DateTimeRangeTests: all four bracket combos with /d rounding, /M rounding, /h rounding, mixed brackets with date math operations
  • Build succeeds with 0 warnings, 0 errors
  • dotnet format passes with no changes needed

Previously, TwoPartFormatParser always passed isUpperLimit=false for the
start side and isUpperLimit=true for the end side of a range, regardless
of bracket type. This is only correct for inclusive [...] brackets.

Per Elasticsearch conventions, rounding behavior must change based on
whether each boundary is inclusive or exclusive:

  - Inclusive min ([): rounds down (start of period)  — gte semantics
  - Exclusive min ({): rounds up (end of period)      — gt semantics
  - Inclusive max (]): rounds up (end of period)       — lte semantics
  - Exclusive max (}): rounds down (start of period)   — lt semantics

This means [now/d TO now/d] correctly produces the entire day (start to
end), while {now/d TO now/d} collapses, and mixed brackets like
[now/d TO now/d} produce start-of-day to start-of-day.

Changes:
- Pre-scan closing bracket before parsing parts so isUpperLimit is known
  upfront (uses a simple backwards char scan, no extra regex)
- Pass bracket-aware isUpperLimit to part parsers; wildcard parsers
  still receive positional isUpperLimit (false=min, true=max) since they
  use it for position semantics rather than rounding
- Allow mixed bracket pairs ([..}, {..]) per Elasticsearch Lucene syntax
- Add comprehensive tests for all four bracket combinations with /d, /M,
  /h rounding and mixed date math operations
- Document rounding behavior in README with reference table and common
  date range patterns

Ref: FoundatioFx/Foundatio.Lucene@a8426ab
Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adjusts date range parsing to make date-math rounding bracket-aware (inclusive/exclusive) and align behavior with Elasticsearch range-query conventions, while also allowing mixed bracket pairs.

Changes:

  • Updates TwoPartFormatParser to determine rounding direction based on inclusive ([ ]) vs exclusive ({ }) boundaries, and allows mixed bracket pairs.
  • Adds/updates tests covering exclusive + mixed bracket combinations and /d, /M, /h rounding behaviors.
  • Expands README documentation with a bracket-aware rounding reference section and common patterns.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/Exceptionless.DateTimeExtensions.Tests/FormatParsers/TwoPartFormatParserTests.cs Adds coverage for exclusive and mixed bracket pairs (and updates wildcard expectations).
tests/Exceptionless.DateTimeExtensions.Tests/DateTimeRangeTests.cs Adds end-to-end parsing tests for bracket-aware rounding across units and mixed brackets.
src/Exceptionless.DateTimeExtensions/FormatParsers/FormatParsers/TwoPartFormatParser.cs Implements bracket-aware rounding direction + mixed bracket acceptance; changes closing-bracket detection.
README.md Documents inclusive/exclusive rounding rules and provides example patterns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Simplify boolean ternary expressions per CodeQL: replace
  `A ? false : B` with `A is not X && B` and `A ? true : B` with
  `A is X || B` for the wildcard type checks
- Collapse inverted ranges from exclusive brackets instead of letting
  DateTimeRange reorder bounds and unintentionally expand the range
- Update test comment to reflect the explicit collapse behavior

Co-authored-by: Cursor <cursoragent@cursor.com>
ejsmith
ejsmith previously approved these changes Feb 16, 2026
Corrects the logic for determining inclusive and exclusive date ranges based on brackets.

Improves the bracket parsing logic to handle null or empty content and bracket characters, ensuring accurate date range calculations.
Also fixes the IsValidBracketPair function to improve bracket validation.
@niemyjski niemyjski merged commit dc9b7c0 into main Feb 16, 2026
3 of 4 checks passed
@niemyjski niemyjski deleted the fix/inclusive-exclusive-bracket-rounding branch February 16, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants