Skip to content

Conversation

niemyjski
Copy link
Member

Extends the date range parsing functionality to support wildcard characters, allowing for open-ended date ranges.

Implements a new `WildcardPartParser` to handle "*" characters, representing either the minimum or maximum DateTimeOffset value based on the context.

Updates the `TwoPartFormatParser` to correctly handle bracketed date ranges and wildcard characters.
Adds support for "TO" delimiter and bracket syntax to the two-part date range parser.
This allows for more flexible date range input formats,
including those used by Elasticsearch.
@niemyjski niemyjski self-assigned this Sep 22, 2025
@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 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

This PR adds Elasticsearch DateMath support to the DateTime extensions library, specifically implementing bracket syntax ([ and {), TO delimiter support, and wildcard (*) functionality for date range queries. The implementation follows Elasticsearch's query-dsl-query-string-query range syntax patterns.

  • Adds wildcard part parser for handling * in date ranges (maps to DateTime.MinValue/MaxValue)
  • Extends TwoPartFormatParser to support Elasticsearch bracket syntax and TO delimiter
  • Comprehensive test coverage for all new syntax patterns including edge cases

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/Exceptionless.DateTimeExtensions/FormatParsers/FormatParsers/TwoPartFormatParser.cs Updated regex patterns to support Elasticsearch bracket syntax ([ ] { })
src/Exceptionless.DateTimeExtensions/FormatParsers/FormatParsers/PartParsers/WildcardPartParser.cs New parser implementation for wildcard (*) support in date ranges
tests/Exceptionless.DateTimeExtensions.Tests/FormatParsers/TwoPartFormatParserTests.cs Added test cases for new syntax patterns including TO delimiter, brackets, and wildcards
tests/Exceptionless.DateTimeExtensions.Tests/FormatParsers/PartParsers/WildcardPartParserTests.cs Comprehensive test suite for wildcard parser functionality

Adds a new entry to .gitignore to prevent tracking of specific IDE configuration files related to the project.

This ensures a cleaner repository by excluding auto-generated files and preventing accidental commits of personal IDE settings.
ejsmith
ejsmith previously approved these changes Sep 22, 2025
Adds `RegexOptions.Compiled` to regex definitions in the
`TwoPartFormatParser` to improve performance.
Introduces tests for parsing date ranges using various delimiters
including dashes, "TO", and Elasticsearch bracket syntax.
Also, adds tests for wildcard support in date range parsing.
Addresses and updates existing wildcard parser tests.
Implements parsing of Elasticsearch-style date math expressions within date ranges, offering flexible and precise date calculations.

This includes support for:
- Anchors (now, explicit dates)
- Operations (+, -, /)
- Units (y, M, w, d, h, m, s)
- Timezone handling

Updates the DateTimeRange parsing to support bracket notation and wildcards.
Copy link
Member

@ejsmith ejsmith left a comment

Choose a reason for hiding this comment

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

Looks good, but really wish you had kept the DateMath class separate so it could be used by itself.

Refactors the DateMathPartParser to enhance performance and accuracy in parsing Elasticsearch date math expressions.

- Optimizes explicit date parsing by using length-based format selection and pre-compiled regex.
- Improves timezone handling and format ordering for better parsing.
- Adds comprehensive tests to ensure correctness.
@niemyjski niemyjski marked this pull request as ready for review September 22, 2025 03:19
Copy link

@Copilot 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

Copilot reviewed 8 out of 9 changed files in this pull request and generated 9 comments.

public class TwoPartFormatParser : IFormatParser
{
private static readonly Regex _beginRegex = new(@"^\s*", RegexOptions.Compiled);
private static readonly Regex _beginRegex = new(@"^\s*(?:[\[\{])?\s*", RegexOptions.Compiled);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The updated regexes consume opening and closing brackets/braces but do not capture which type was used; without capturing, the parser cannot distinguish inclusive [] from exclusive {} semantics (Elasticsearch treats {} as exclusive). This results in silently treating exclusive bounds as inclusive. Either (a) remove brace support until exclusivity is implemented, or (b) capture bracket type and adjust start/end boundaries accordingly (e.g., shift by smallest representable unit). Example: modify begin/end patterns to capture the delimiter type and pass it through parsing logic.

Copilot uses AI. Check for mistakes.

private static readonly Regex _beginRegex = new(@"^\s*(?:[\[\{])?\s*", RegexOptions.Compiled);
private static readonly Regex _delimiterRegex = new(@"\G(?:\s*-\s*|\s+TO\s+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex _endRegex = new(@"\G\s*$", RegexOptions.Compiled);
private static readonly Regex _endRegex = new(@"\G\s*(?:[\]\}])?\s*$", RegexOptions.Compiled);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The updated regexes consume opening and closing brackets/braces but do not capture which type was used; without capturing, the parser cannot distinguish inclusive [] from exclusive {} semantics (Elasticsearch treats {} as exclusive). This results in silently treating exclusive bounds as inclusive. Either (a) remove brace support until exclusivity is implemented, or (b) capture bracket type and adjust start/end boundaries accordingly (e.g., shift by smallest representable unit). Example: modify begin/end patterns to capture the delimiter type and pass it through parsing logic.

Copilot uses AI. Check for mistakes.

public class TwoPartFormatParser : IFormatParser
{
private static readonly Regex _beginRegex = new(@"^\s*", RegexOptions.Compiled);
private static readonly Regex _beginRegex = new(@"^\s*(?:[\[\{])?\s*", RegexOptions.Compiled);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

These patterns allow mismatches like an opening '{' with a closing ']' or a closing bracket without any opener, which can mask malformed input. Consider validating for a balanced, matching pair (and rejecting lone or mismatched delimiters) by deferring closing delimiter consumption until after successful two-part parsing and explicitly checking the original characters.

Copilot uses AI. Check for mistakes.

private static readonly Regex _beginRegex = new(@"^\s*(?:[\[\{])?\s*", RegexOptions.Compiled);
private static readonly Regex _delimiterRegex = new(@"\G(?:\s*-\s*|\s+TO\s+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex _endRegex = new(@"\G\s*$", RegexOptions.Compiled);
private static readonly Regex _endRegex = new(@"\G\s*(?:[\]\}])?\s*$", RegexOptions.Compiled);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

These patterns allow mismatches like an opening '{' with a closing ']' or a closing bracket without any opener, which can mask malformed input. Consider validating for a balanced, matching pair (and rejecting lone or mismatched delimiters) by deferring closing delimiter consumption until after successful two-part parsing and explicitly checking the original characters.

Copilot uses AI. Check for mistakes.

Comment on lines +50 to +51
// Bracket notation support [start TO end]
var bracketRange = DateTimeRange.Parse("[2023-01-01 TO 2023-12-31]", DateTime.Now);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

Documentation shows only square bracket usage but the tests and parser also accept curly braces; the README does not clarify (a) whether {} are supported, or (b) inclusive vs exclusive semantics. Either document that {} are currently treated the same as [] (temporary limitation) or explain exclusivity once implemented to avoid developer confusion.

Suggested change
// Bracket notation support [start TO end]
var bracketRange = DateTimeRange.Parse("[2023-01-01 TO 2023-12-31]", DateTime.Now);
// Bracket notation support [start TO end] or {start TO end}
// Both square brackets [ ] and curly braces { } are accepted for range expressions.
// Currently, both are treated the same (inclusive bounds). Exclusive semantics for { } may be added in the future.
var bracketRange = DateTimeRange.Parse("[2023-01-01 TO 2023-12-31]", DateTime.Now);
var curlyBracketRange = DateTimeRange.Parse("{2023-01-01 TO 2023-12-31}", DateTime.Now); // Currently same as above

Copilot uses AI. Check for mistakes.

Comment on lines 121 to 122
case 4: // Built-in: year (yyyy)
return TryParseWithFormat(dateStr, "yyyy", offset, false, out result);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The parser supports year-only and year-month anchors, but there are no corresponding tests exercising these formats (e.g., "2023||" or "2023-06||"). Add tests to ensure these branches remain correct and to catch regressions in length-based parsing.

Copilot generated this review using guidance from repository custom instructions.

Comment on lines 124 to 126
case 7: // Built-in: year_month (yyyy-MM)
if (dateStr[4] == '-')
return TryParseWithFormat(dateStr, "yyyy-MM", offset, false, out result);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The parser supports year-only and year-month anchors, but there are no corresponding tests exercising these formats (e.g., "2023||" or "2023-06||"). Add tests to ensure these branches remain correct and to catch regressions in length-based parsing.

Copilot generated this review using guidance from repository custom instructions.

Comment on lines 25 to 28
private static readonly Regex _parser = new(
@"^(?<anchor>now|(?<date>\d{4}[-.]?\d{2}[-.]?\d{2}(?:[T\s](?:\d{1,2}(?::?\d{2}(?::?\d{2})?)?(?:\.\d{1,3})?)?(?:[+-]\d{2}:?\d{2}|Z)?)?)\|\|)" +
@"(?<operations>(?:[+\-/]\d*[yMwdhHms])*)$",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The regex permits multiple rounding operations or additional arithmetic after a rounding (e.g., "now/d+1h"), but tests do not cover invalid sequencing; Elasticsearch requires rounding (/unit) be the final operation. Add negative tests (expecting null) for sequences like "now/d+1h" and "/d/d" to ensure spec-aligned enforcement (or clarify in docs if divergence is intentional).

Copilot generated this review using guidance from repository custom instructions.

Comment on lines 25 to 26
private static readonly Regex _parser = new(
@"^(?<anchor>now|(?<date>\d{4}[-.]?\d{2}[-.]?\d{2}(?:[T\s](?:\d{1,2}(?::?\d{2}(?::?\d{2})?)?(?:\.\d{1,3})?)?(?:[+-]\d{2}:?\d{2}|Z)?)?)\|\|)" +
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

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

The date portion allows a dotted format (e.g., 2001.02.01) via the [-.]? separators, but there are no tests covering dotted date inputs. Add tests like "2001.02.01||" (with and without operations) to validate this branch and prevent future regressions.

Copilot generated this review using guidance from repository custom instructions.

Adds a `DateMath` utility class for parsing Elasticsearch date math expressions, offering standalone date math functionality without range capabilities.

This utility supports parsing expressions with `now`, explicit dates, operations, and time units, providing a simpler API for direct parsing operations.

Includes comprehensive unit tests for various parsing scenarios, edge cases, and timezone handling.
- Improves date parsing by removing support for dotted date formats and enforcing hyphenated formats for consistency.
- Adds validation to ensure rounding operations in date math expressions are only used as the final operation, aligning with specification requirements.
- Enhances two-part format parser to validate matching brackets, preventing parsing errors due to unbalanced brackets.
Adds validation to ensure that brackets and braces are properly matched in the format parser.

This prevents incorrect parsing when there are mismatched brackets/braces.
Extends the DateMath utility to support TimeZoneInfo, enabling accurate date parsing and calculations within specific timezones.

This enhancement allows for parsing expressions using a specified timezone, ensuring that "now" calculations and dates without explicit timezone information are correctly interpreted.
Dates with explicit timezone information are preserved, regardless of the TimeZoneInfo parameter.
Updates test method names to clearly indicate that they specifically test timezone parsing functionality.
This improves clarity and maintainability of the test suite.
@niemyjski niemyjski merged commit 3555cfe into main Sep 22, 2025
2 of 3 checks passed
@niemyjski niemyjski deleted the feature/datemath branch September 22, 2025 04:08
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.

3 participants