From 4773e8343fda3eec274c5c30626fe5b8eca441f7 Mon Sep 17 00:00:00 2001 From: PerikiyoXD Date: Sat, 1 Aug 2026 20:20:03 +0200 Subject: [PATCH] Format filter query values with invariant culture GenerateFilterQuery builds a DataView.RowFilter expression, whose syntax is culture invariant: it always expects '.' as the decimal separator and a Gregorian calendar. Both the numeric and DateTime paths were formatting with the current culture instead. On a comma-decimal locale a float rendered as "1,5". Inside an IN (...) clause the comma also separates values, so the filter silently matched the wrong rows rather than failing outright. On a locale with a non-Gregorian default calendar, such as th-TH, the DateTime path emitted year 2567 for 2024. Note that a custom format string does not avoid this: it still resolves the calendar from the current culture. This was already covered by FloatScientificNotation_IsWrappedInQuotes, which failed on any comma-decimal machine but passed in CI under en-US. The new test pins the culture explicitly so the regression is caught regardless of where the suite runs. --- src/ParquetViewer.Tests/HelperTests.cs | 40 +++++++++++++++++++ src/ParquetViewer/Controls/ParquetGridView.cs | 13 ++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/ParquetViewer.Tests/HelperTests.cs b/src/ParquetViewer.Tests/HelperTests.cs index 6113804f..cd73e444 100644 --- a/src/ParquetViewer.Tests/HelperTests.cs +++ b/src/ParquetViewer.Tests/HelperTests.cs @@ -285,6 +285,46 @@ public void FloatScientificNotation_IsWrappedInQuotes() Assert.AreEqual("Value = '1.23E+20'", query); } + [TestMethod] + [DataRow("de-DE")] //comma decimal separator + [DataRow("fr-FR")] //comma decimal separator + narrow no-break group separator + [DataRow("th-TH")] //non-Gregorian calendar by default + public void NumericAndDateFilters_AreCultureInvariant(string cultureName) + { + //DataView.RowFilter always expects '.' as the decimal separator and a Gregorian date, + //regardless of the user's locale. Without invariant formatting, a culture that uses ',' + //would corrupt the filter since ',' also separates values inside an `IN (...)` clause. + var originalCulture = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo(cultureName); + + Assert.AreEqual("Value = 1.5", ParquetGridView.GenerateFilterQuery(new() + { + ("Value", typeof(double), new object[] { 1.5d }) + })); + + Assert.AreEqual("Value = '1.23E+20'", ParquetGridView.GenerateFilterQuery(new() + { + ("Value", typeof(double), new object[] { 1.23e20 }) + })); + + Assert.AreEqual("Value IN (1.5,2.5)", ParquetGridView.GenerateFilterQuery(new() + { + ("Value", typeof(double), new object[] { 1.5d, 2.5d }) + })); + + Assert.AreEqual("Value = #2024-01-31 13:45:30#", ParquetGridView.GenerateFilterQuery(new() + { + ("Value", typeof(DateTime), new object[] { new DateTime(2024, 1, 31, 13, 45, 30) }) + })); + } + finally + { + CultureInfo.CurrentCulture = originalCulture; + } + } + [TestMethod] public void ByteArrayValue_IsCorrectlyTruncated() { diff --git a/src/ParquetViewer/Controls/ParquetGridView.cs b/src/ParquetViewer/Controls/ParquetGridView.cs index 66bd2666..257c9bd4 100644 --- a/src/ParquetViewer/Controls/ParquetGridView.cs +++ b/src/ParquetViewer/Controls/ParquetGridView.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Data; using System.Drawing; +using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -1050,12 +1051,18 @@ public static string GenerateFilterQuery(List<(string ColumnName, Type ValueType { if (valueType == typeof(DateTime)) { - //Use a standard date format so the query is always syntactically correct - queryBuilder.AppendFormat(dateValueEscapeFormat, ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF")); + //Use a standard date format so the query is always syntactically correct. + //Invariant culture is required: custom format strings still resolve the calendar from + //the current culture, so locales like th-TH would emit a non-Gregorian year. + queryBuilder.AppendFormat(CultureInfo.InvariantCulture, dateValueEscapeFormat, + ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF", CultureInfo.InvariantCulture)); } else if (valueType.IsNumber()) { - var stringValue = value.ToString(); + //DataView.RowFilter syntax is culture invariant: it always expects '.' as the decimal + //separator. Formatting with the current culture would emit ',' in locales like de-DE, + //which silently corrupts the filter since ',' separates values inside an `IN (...)` clause. + var stringValue = Convert.ToString(value, CultureInfo.InvariantCulture); if ((valueType == typeof(float) || valueType == typeof(double)) && stringValue?.Contains('E', StringComparison.OrdinalIgnoreCase) == true) stringValue = $"'{stringValue}'"; //scientific notation values need to be wrapped in single quotes