diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 3bbf936bce..4e3174895f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -551,7 +551,7 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit else if (memberType == typeof(DateTime)) { SqlStringLiteral serializedDateTime = (SqlStringLiteral)right.Literal; - value = DateTime.Parse(serializedDateTime.Value); + value = DateTime.Parse(serializedDateTime.Value, provider: null, DateTimeStyles.RoundtripKind); } if (value != default(object)) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml new file mode 100644 index 0000000000..6c3ceb9716 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml @@ -0,0 +1,26 @@ + + + + + (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Local)))]]> + + + + + + + + + (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Utc)))]]> + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index a3b62a1ff1..d25f213868 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using Newtonsoft.Json; using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using Newtonsoft.Json.Converters; using BaselineTest; @@ -134,6 +135,9 @@ internal class DataObject : LinqTestObject [JsonConverter(typeof(IsoDateTimeConverter))] public DateTime IsoTime; + [JsonConverter(typeof(DateJsonConverter))] + public DateTime IsoDateOnly; + // This field should serialize as ISO Date // as this is the default DateTimeConverter // used by Newtonsoft @@ -145,6 +149,21 @@ internal class DataObject : LinqTestObject public string Pk; } + class DateJsonConverter : IsoDateTimeConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is DateTime dateTime) + { + writer.WriteValue(dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + else + { + base.WriteJson(writer, value, serializer); + } + } + } + internal class AmbientContextObject { public double FieldAccess; @@ -350,6 +369,30 @@ public void TestDateTimeJsonConverter() this.ExecuteTestSuite(inputs); } + [TestMethod] + public void TestDateTimeJsonConverterTimezones() + { + const int Records = 10; + DateTime midDateTime = new (2016, 9, 13, 0, 0, 0); + Func createDataObj = (random) => + { + DataObject obj = new() { + IsoDateOnly = LinqTestsCommon.RandomDateTime(random, midDateTime), + Id = Guid.NewGuid().ToString(), + Pk = "Test" + }; + return obj; + }; + Func> getQuery = LinqTestsCommon.GenerateTestCosmosData(createDataObj, Records, testContainer); + + List inputs = new() + { + new LinqTestInput("IsoDateTimeConverter LocalTime = filter", b => getQuery(b).Where(doc => doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Local))), + new LinqTestInput("IsoDateTimeConverter UniversalTime = filter", b => getQuery(b).Where(doc => doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, DateTimeKind.Utc))), + }; + this.ExecuteTestSuite(inputs); + } + [TestMethod] public void TestNullableFields() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 5e69fbb1e0..24f2bc44bc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -34,6 +34,7 @@ + @@ -189,6 +190,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs new file mode 100644 index 0000000000..e4f90f1957 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -0,0 +1,54 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Globalization; + using System.Linq.Expressions; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + [TestClass] + public class CosmosLinqJsonConverterTests + { + [TestMethod] + public void DateTimeKindIsPreservedTest() + { + // Should work for UTC time + DateTime utcDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Utc); + Expression> expr = a => a.StartDate <= utcDate; + string sql = SqlTranslator.TranslateExpression(expr.Body); + Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); + + // Should work for local time + DateTime localDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Local); + expr = a => a.StartDate <= localDate; + sql = SqlTranslator.TranslateExpression(expr.Body); + Assert.AreEqual("(a[\"StartDate\"] <= \"2022-05-26\")", sql); + } + + class TestDocument + { + [JsonConverter(typeof(DateJsonConverter))] + public DateTime StartDate { get; set; } + } + + class DateJsonConverter : IsoDateTimeConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is DateTime dateTime) + { + writer.WriteValue(dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + else + { + base.WriteJson(writer, value, serializer); + } + } + } + } +}