Skip to content

fix(filter): read a date in a list with the invariant culture - #173

Merged
pdevito3 merged 1 commit into
v2from
fix/invariant-culture-list-dates
Oct 2, 2026
Merged

pdevito3 merged 1 commit into
v2from
fix/invariant-culture-list-dates

Conversation

@pdevito3

@pdevito3 pdevito3 commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

A list value (^^ [...]) for DateOnly, TimeOnly, and DateTimeOffset now reads with the invariant culture. A single comparison already did this. Before, the list used the culture of the thread.

 TypeConversionFunctions
-  DateTimeOffset => DateTimeOffset.Parse(value).ToUniversalTime()
-  DateOnly       => DateOnly.Parse(value)
-  TimeOnly       => TimeOnly.Parse(value)
+  DateTimeOffset => DateTimeOffset.Parse(value, CultureInfo.InvariantCulture).ToUniversalTime()
+  DateOnly       => DateOnly.Parse(value, CultureInfo.InvariantCulture)
+  TimeOnly       => TimeOnly.Parse(value, CultureInfo.InvariantCulture)
   TimeSpan       => TimeSpan.Parse(value)      # unchanged

The cultures with a real bug use a calendar that is not Gregorian: ar-SA (Um Al-Qura), th-TH (Thai Buddhist), and fa-IR, prs-AF, ps-AF (Persian). The ISO form with T is not affected. A form without T, for example 2024-01-15 or 2024-01-15Z, is affected:

Culture Date ^^ [2024-01-15] before
ar-SA FormatException
th-TH reads the year as 1481, 0 rows
fa-IR reads the year as 2645, 0 rows

TimeOnly showed no difference in any culture. Its change is for consistency only.

DateTimeOffset keeps the UTC conversion from #143, which is already on v2. This PR changes only the culture.

#116 changes the same DateTimeOffset line and must rebase on this PR.

Call sites not changed

  • TimeSpan.Parse(value) in the list: the invariant culture rejects a comma fraction such as 01:02:03,5, which works under de-DE today. No culture showed a bug for this type, so a change is a break with no gain.
  • bool, Guid, char, Enum, and the int.Parse of the fraction digits: these do not depend on the culture.
  • decimal.TryParse(value, out _) in the literal check of IsPropertyPath: this is the number grammar of fix(parser)!: parse numbers with the invariant culture only #121. It is a breaking change and stays out of this PR.

Evidence

Proof from the verify-querykit harness, labels dateonly-list-{culture} and dateonly-list-{culture}-fixed. Memory and Postgres gave the same result.

  • Before: under th-TH and fa-IR, Date ^^ [2024-01-15] returned 0 rows. Under ar-SA, it threw FormatException.
    After: under each culture, the filter returned the row with the date 2024-01-15.

Tests, written first (TDD):

QueryKit.UnitTests/ListDateCultureTests.cs               (en-US, ar-SA, th-TH, fa-IR)
  date_only_in_a_list_matches_in_every_culture
  date_time_offset_in_a_list_matches_in_every_culture
  time_only_in_a_list_matches_in_every_culture
QueryKit.IntegrationTests/Tests/ListDateCultureTests.cs  (Postgres, th-TH, fa-IR, ar-SA)
  date_only_in_a_list_matches_in_a_culture_with_another_calendar
  date_time_offset_in_a_list_matches_in_a_culture_with_another_calendar
  • Before: 6 of 12 unit tests and 6 of 6 integration tests failed.
    After: dotnet test: 482 unit tests and 303 integration tests pass, 0 failures.

Merge Danger

Door: two-way

Blast Radius: small

The change affects only a list value of these three types under a culture with a calendar that is not Gregorian. Before, those filters returned wrong rows or threw. In every other culture, the result is the same.

A list value for DateOnly, TimeOnly, and DateTimeOffset used the current
culture. Under ar-SA the list threw FormatException. Under th-TH and fa-IR
the list read 2024-01-15 with another calendar and matched no row. A single
comparison already used the invariant culture.
@pdevito3
pdevito3 force-pushed the fix/invariant-culture-list-dates branch from dec4194 to 39837d7 Compare October 2, 2026 20:13
@pdevito3
pdevito3 merged commit aa3751d into v2 Oct 2, 2026
2 checks passed
@pdevito3
pdevito3 deleted the fix/invariant-culture-list-dates branch October 2, 2026 20:14
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