diff --git a/src/ParquetViewer.Tests/HelperTests.cs b/src/ParquetViewer.Tests/HelperTests.cs index 6113804..cd73e44 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 66bd266..257c9bd 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