Skip to content

Commit

Permalink
LINQ: Fixes preserve DateTime.Kind when passing value to custom JsonC…
Browse files Browse the repository at this point in the history
…onverter (#3224)

* LINQ: preserve DateTime.Kind when passing to custom JsonConverter

* Add named parameter

* Add unit test

* Update test

* Remove unused namespaces

* Fix nit

* Add emulator test

* Update test to not use timezones

Co-authored-by: j82w <j82w@users.noreply.github.com>
Co-authored-by: Matias Quaranta <ealsur@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 6, 2022
1 parent 35c177b commit dfa0b7c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
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))
{
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()
{
// 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);
}
}
}
}
}

0 comments on commit dfa0b7c

Please sign in to comment.