Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LINQ: Fixes preserve DateTime.Kind when passing value to custom JsonConverter #3224

Merged
merged 13 commits into from
Jun 6, 2022
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit
else if (memberType == typeof(DateTime))
ealsur marked this conversation as resolved.
Show resolved Hide resolved
{
SqlStringLiteral serializedDateTime = (SqlStringLiteral)right.Literal;
value = DateTime.Parse(serializedDateTime.Value);
value = DateTime.Parse(serializedDateTime.Value, provider: null, DateTimeStyles.RoundtripKind);
}

if (value != default(object))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Results>
<Result>
<Input>
<Description><![CDATA[IsoDateTimeConverter LocalTime = filter]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Local)))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (root["IsoDateOnly"] = "2016-09-13")]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsoDateTimeConverter UniversalTime = filter]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.IsoDateOnly == new DateTime(2016, 9, 13, 0, 0, 0, Utc)))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (root["IsoDateOnly"] = "2016-09-13")]]></SqlQuery>
</Output>
</Result>
</Results>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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<Random, DataObject> createDataObj = (random) =>
{
DataObject obj = new() {
IsoDateOnly = LinqTestsCommon.RandomDateTime(random, midDateTime),
Id = Guid.NewGuid().ToString(),
Pk = "Test"
};
return obj;
};
Func<bool, IQueryable<DataObject>> getQuery = LinqTestsCommon.GenerateTestCosmosData(createDataObj, Records, testContainer);

List<LinqTestInput> 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<None Remove="BaselineTest\TestBaseline\EndToEndTraceWriterBaselineTests.ReadManyAsync.xml" />
<None Remove="BaselineTest\TestBaseline\EndToEndTraceWriterBaselineTests.StreamPointOperationsAsync.xml" />
<None Remove="BaselineTest\TestBaseline\EndToEndTraceWriterBaselineTests.TypedPointOperationsAsync.xml" />
<None Remove="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml" />
</ItemGroup>

<ItemGroup>
Expand Down Expand Up @@ -189,6 +190,9 @@
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestClausesOrderVariations.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestSelectTop.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
ccurrens marked this conversation as resolved.
Show resolved Hide resolved
{
// Should work for UTC time
DateTime utcDate = new DateTime(2022, 5, 26, 0, 0, 0, DateTimeKind.Utc);
Expression<Func<TestDocument, bool>> 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);
}
}
}
}
}