Skip to content

Commit 99e531a

Browse files
authored
DateTimeIsParsedAsUTC (#277)
* CustomDateTimeConverter * TargetFrameworks * uap10.0 * Nullable DateTime
1 parent 8708ef8 commit 99e531a

16 files changed

+256
-72
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</PropertyGroup>
55

66
<PropertyGroup>
7-
<VersionPrefix>1.0.18</VersionPrefix>
7+
<VersionPrefix>1.0.19</VersionPrefix>
88
</PropertyGroup>
99

1010
<Choose>
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
3+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UTC/@EntryIndexedValue">UTC</s:String></wpf:ResourceDictionary>

src-console/ConsoleAppEF2.1.1_InMemory/Program.cs

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Newtonsoft.Json;
33
using System;
44
using System.Collections.Generic;
5+
using System.Globalization;
56
using System.Linq;
67
using System.Linq.Dynamic.Core;
78
using System.Linq.Dynamic.Core.CustomTypeProviders;
@@ -74,6 +75,20 @@ private static void StringEscapeTest()
7475

7576
static void Main(string[] args)
7677
{
78+
var d = new[] { DateTime.UtcNow }.AsQueryable();
79+
80+
//DateTime.TryParse("Fri, 10 May 2019 11:03:17 GMT", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime);
81+
//dateTime.ToUniversalTime().Dump();
82+
83+
var r = d.Where(dd => dd > DateTime.Parse("Fri, 10 May 2019 11:03:17 GMT")).ToArray();
84+
85+
var r2 = d.Where("it > \"Fri, 10 May 2019 11:03:17 GMT\"").ToArray();
86+
87+
// DateTime.Now.ToString("R").Dump();
88+
89+
return;
90+
91+
7792
StringEscapeTest();
7893
//var q = new[] { new NestedDto(), new NestedDto { NestedDto2 = new NestedDto2 { NestedDto3 = new NestedDto3 { Id = 42 } } } }.AsQueryable();
7994

src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<PropertyGroup>
33
<Description>Dynamic Linq extensions for EntityFramework which adds Async support</Description>
44
<AssemblyTitle>EntityFramework.DynamicLinq</AssemblyTitle>
5-
<!--<VersionPrefix>1.0.9.1</VersionPrefix>-->
65
<Authors>Stef Heyenrath</Authors>
76
<TargetFrameworks>net45;net46</TargetFrameworks>
87
<DefineConstants>EF</DefineConstants>

src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<PropertyGroup>
33
<Description>Dynamic Linq extensions for Microsoft.EntityFrameworkCore which adds Async support</Description>
44
<AssemblyTitle>Microsoft.EntityFrameworkCore.DynamicLinq</AssemblyTitle>
5-
<!--<VersionPrefix>1.0.9.1</VersionPrefix>-->
65
<Authors>Stef Heyenrath</Authors>
76
<TargetFrameworks>net451;net46;netstandard1.3;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
87
<DefineConstants>$(DefineConstants);EFCORE</DefineConstants>

src/System.Linq.Dynamic.Core/Compatibility/CustomIntrospectionExtensions.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22

3+
// ReSharper disable once CheckNamespace
34
namespace System.Reflection
45
{
56
/// <summary>
@@ -36,4 +37,4 @@ public static Type[] GetGenericTypeArguments(this TypeInfo typeInfo)
3637
}
3738
#endif
3839
}
39-
}
40+
}

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

+12-19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq.Dynamic.Core.Parser.SupportedMethods;
88
using System.Linq.Dynamic.Core.Parser.SupportedOperands;
99
using System.Linq.Dynamic.Core.Tokenizer;
10+
using System.Linq.Dynamic.Core.TypeConverters;
1011
using System.Linq.Dynamic.Core.Validation;
1112
using System.Linq.Expressions;
1213
using System.Reflection;
@@ -29,6 +30,7 @@ public class ExpressionParser
2930
private readonly TextParser _textParser;
3031
private readonly IExpressionHelper _expressionHelper;
3132
private readonly ITypeFinder _typeFinder;
33+
private readonly ITypeConverterFactory _typeConverterFactory;
3234
private readonly Dictionary<string, object> _internals;
3335
private readonly Dictionary<string, object> _symbols;
3436

@@ -75,6 +77,7 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull]
7577
_methodFinder = new MethodFinder(_parsingConfig);
7678
_expressionHelper = new ExpressionHelper(_parsingConfig);
7779
_typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper);
80+
_typeConverterFactory = new TypeConverterFactory(_parsingConfig);
7881
}
7982

8083
void ProcessParameters(ParameterExpression[] parameters)
@@ -477,13 +480,13 @@ Expression ParseComparisonOperator()
477480
}
478481
}
479482
}
480-
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string && (typeConverter = TypeConverterFactory.GetConverter(left.Type)) != null)
483+
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null)
481484
{
482-
right = Expression.Constant(typeConverter.ConvertFromInvariantString((string)constantExpr.Value), left.Type);
485+
right = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueR), left.Type);
483486
}
484-
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string && (typeConverter = TypeConverterFactory.GetConverter(right.Type)) != null)
487+
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null)
485488
{
486-
left = Expression.Constant(typeConverter.ConvertFromInvariantString((string)constantExpr.Value), right.Type);
489+
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
487490
}
488491
else
489492
{
@@ -1526,7 +1529,7 @@ Expression ParseTypeAccess(Type type)
15261529
return ParseMemberAccess(type, null);
15271530
}
15281531

1529-
static Expression GenerateConversion(Expression expr, Type type, int errorPos)
1532+
private Expression GenerateConversion(Expression expr, Type type, int errorPos)
15301533
{
15311534
Type exprType = expr.Type;
15321535
if (exprType == type)
@@ -1557,21 +1560,11 @@ static Expression GenerateConversion(Expression expr, Type type, int errorPos)
15571560
{
15581561
string text = (string)((ConstantExpression)expr).Value;
15591562

1560-
// DateTime is parsed as UTC time.
1561-
if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime))
1563+
var typeConvertor = _typeConverterFactory.GetConverter(type);
1564+
if (typeConvertor != null)
15621565
{
1563-
return Expression.Constant(dateTime, type);
1564-
}
1565-
1566-
object[] arguments = { text, null };
1567-
#if NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD
1568-
MethodInfo method = type.GetMethod("TryParse", new[] { typeof(string), type.MakeByRefType() });
1569-
#else
1570-
MethodInfo method = type.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), type.MakeByRefType() }, null);
1571-
#endif
1572-
if (method != null && (bool)method.Invoke(null, arguments))
1573-
{
1574-
return Expression.Constant(arguments[1], type);
1566+
var value = typeConvertor.ConvertFromInvariantString(text);
1567+
return Expression.Constant(value, type);
15751568
}
15761569
}
15771570

src/System.Linq.Dynamic.Core/ParsingConfig.cs

+31-8
Original file line numberDiff line numberDiff line change
@@ -92,58 +92,81 @@ public IQueryableAnalyzer QueryableAnalyzer
9292
/// <summary>
9393
/// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression.
9494
/// Does not affect the usability of the equivalent context symbols ($, ^ and ~).
95+
///
9596
/// Default value is true.
9697
/// </summary>
9798
public bool AreContextKeywordsEnabled { get; set; } = true;
9899

99100
/// <summary>
100-
/// Gets or sets a value indicating whether to use dynamic object class for anonymous types. Default value is false.
101+
/// Gets or sets a value indicating whether to use dynamic object class for anonymous types.
102+
///
103+
/// Default value is false.
101104
/// </summary>
102105
public bool UseDynamicObjectClassForAnonymousTypes { get; set; } = false;
103106

104107
/// <summary>
105-
/// Gets or sets a value indicating whether the EntityFramework version supports evaluating GroupBy at database level. Default value is false.
108+
/// Gets or sets a value indicating whether the EntityFramework version supports evaluating GroupBy at database level.
106109
/// See https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.1#linq-groupby-translation
107110
///
108111
/// Remark: when this setting is set to 'true', make sure to supply this ParsingConfig as first parameter on the extension methods.
112+
///
113+
/// Default value is false.
109114
/// </summary>
110115
public bool EvaluateGroupByAtDatabase { get; set; } = false;
111116

112117
/// <summary>
113-
/// Use Parameterized Names in generated dynamic SQL query. Default set to false.
118+
/// Use Parameterized Names in generated dynamic SQL query.
114119
/// See https://github.com/graeme-hill/gblog/blob/master/source_content/articles/2014.139_entity-framework-dynamic-queries-and-parameterization.mkd
120+
///
121+
/// Default value is false.
115122
/// </summary>
116123
public bool UseParameterizedNamesInDynamicQuery { get; set; } = false;
117124

118125
/// <summary>
119-
/// Allows the New() keyword to evaluate any available Type. Default value is false.
126+
/// Allows the New() keyword to evaluate any available Type.
127+
///
128+
/// Default value is false.
120129
/// </summary>
121130
public bool AllowNewToEvaluateAnyType { get; set; } = false;
122131

123132
/// <summary>
124-
/// Renames the (Typed)ParameterExpression empty Name to a the correct supplied name from `it`. Default value is false.
133+
/// Renames the (Typed)ParameterExpression empty Name to a the correct supplied name from `it`.
134+
///
135+
/// Default value is false.
125136
/// </summary>
126137
public bool RenameParameterExpression { get; set; } = false;
127138

128139
/// <summary>
129140
/// By default when a member is not found in a type and the type has a string based index accessor it will be parsed as an index accessor. Use
130141
/// this flag to disable this behaviour and have parsing fail when parsing an expression
131-
/// where a member access on a non existing member happens. Default value is false.
142+
/// where a member access on a non existing member happens.
143+
///
144+
/// Default value is false.
132145
/// </summary>
133146
public bool DisableMemberAccessToIndexAccessorFallback { get; set; } = false;
134147

135148
/// <summary>
136-
/// By default finding types by a simple name is not suported.
149+
/// By default finding types by a simple name is not supported.
137150
/// Use this flag to use the CustomTypeProvider to resolve types by a simple name like "Employee" instead of "MyDatabase.Entities.Employee".
138151
/// Note that a first matching type is returned and this functionality needs to scan all types from all assemblies, so use with caution.
152+
///
139153
/// Default value is false.
140154
/// </summary>
141155
public bool ResolveTypesBySimpleName { get; set; } = false;
142156

143157
/// <summary>
144-
/// Support enumeration-types from the System namespace in mscorelib. An example could be "StringComparison".
158+
/// Support enumeration-types from the System namespace in mscorlib. An example could be "StringComparison".
159+
///
145160
/// Default value is true.
146161
/// </summary>
147162
public bool SupportEnumerationsFromSystemNamespace { get; set; } = true;
163+
164+
/// <summary>
165+
/// By default DateTime (like 'Fri, 10 May 2019 11:03:17 GMT') is parsed as local time.
166+
/// Use this flag to parse all DateTime strings as UTC.
167+
///
168+
/// Default value is false.
169+
/// </summary>
170+
public bool DateTimeIsParsedAsUTC { get; set; } = false;
148171
}
149172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.ComponentModel;
2+
using System.Globalization;
3+
4+
namespace System.Linq.Dynamic.Core.TypeConverters
5+
{
6+
internal class CustomDateTimeConverter : DateTimeOffsetConverter
7+
{
8+
/// <summary>
9+
/// Converts the specified object to a <see cref="DateTime"></see>.
10+
/// </summary>
11+
/// <param name="context">The date format context.</param>
12+
/// <param name="culture">The date culture.</param>
13+
/// <param name="value">The object to be converted.</param>
14+
/// <returns>A <see cref="Nullable{DateTime}"></see> that represents the specified object.</returns>
15+
/// <exception cref="NotSupportedException">The conversion cannot be performed.</exception>
16+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
17+
{
18+
var dateTimeOffset = base.ConvertFrom(context, culture, value) as DateTimeOffset?;
19+
20+
return dateTimeOffset?.UtcDateTime;
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using JetBrains.Annotations;
2+
using System.ComponentModel;
3+
4+
namespace System.Linq.Dynamic.Core.TypeConverters
5+
{
6+
interface ITypeConverterFactory
7+
{
8+
/// <summary>
9+
/// Returns a type converter for the specified type.
10+
/// </summary>
11+
/// <param name="type">The System.Type of the target component.</param>
12+
/// <returns>A System.ComponentModel.TypeConverter for the specified type.</returns>
13+
TypeConverter GetConverter([NotNull] Type type);
14+
}
15+
}

src/System.Linq.Dynamic.Core/TypeConverterFactory.cs src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs

+19-9
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,29 @@
22
using System.ComponentModel;
33
using System.Linq.Dynamic.Core.Validation;
44

5-
namespace System.Linq.Dynamic.Core
5+
namespace System.Linq.Dynamic.Core.TypeConverters
66
{
7-
internal static class TypeConverterFactory
7+
internal class TypeConverterFactory : ITypeConverterFactory
88
{
9-
/// <summary>
10-
/// Returns a type converter for the specified type.
11-
/// </summary>
12-
/// <param name="type">The System.Type of the target component.</param>
13-
/// <returns>A System.ComponentModel.TypeConverter for the specified type.</returns>
14-
public static TypeConverter GetConverter([NotNull] Type type)
9+
private readonly ParsingConfig _config;
10+
11+
public TypeConverterFactory([NotNull] ParsingConfig config)
12+
{
13+
Check.NotNull(config, nameof(config));
14+
15+
_config = config;
16+
}
17+
18+
/// <see cref="ITypeConverterFactory.GetConverter"/>
19+
public TypeConverter GetConverter(Type type)
1520
{
1621
Check.NotNull(type, nameof(type));
1722

23+
if (_config.DateTimeIsParsedAsUTC && (type == typeof(DateTime) || type == typeof(DateTime?)))
24+
{
25+
return new CustomDateTimeConverter();
26+
}
27+
1828
#if !SILVERLIGHT
1929
return TypeDescriptor.GetConverter(type);
2030
#else
@@ -33,4 +43,4 @@ public static TypeConverter GetConverter([NotNull] Type type)
3343
#endif
3444
}
3545
}
36-
}
46+
}

test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public partial class QueryableTests
1919
public class Example
2020
{
2121
public DateTime Time { get; set; }
22+
public DateTime? TimeNull { get; set; }
2223
public DayOfWeek? DOWNull { get; set; }
2324
public DayOfWeek DOW { get; set; }
2425
public int Sec { get; set; }

0 commit comments

Comments
 (0)