Skip to content

Commit 5cf8357

Browse files
authored
Add support for DateOnly and TimeOnly (#671)
* DateOnly * TimeOnly * fix
1 parent f5676b0 commit 5cf8357

16 files changed

+437
-93
lines changed

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

+36-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,24 @@ public void Wrap(ref Expression expression)
151151
{
152152
expression = Wrap((TimeSpan?)constantExpression.Value);
153153
}
154-
154+
#if NET6_0_OR_GREATER
155+
else if (constantExpression.Type == typeof(DateOnly))
156+
{
157+
expression = Wrap((DateOnly)constantExpression.Value);
158+
}
159+
else if (constantExpression.Type == typeof(DateOnly?))
160+
{
161+
expression = Wrap((DateOnly?)constantExpression.Value);
162+
}
163+
else if (constantExpression.Type == typeof(TimeOnly))
164+
{
165+
expression = Wrap((TimeOnly)constantExpression.Value);
166+
}
167+
else if (constantExpression.Type == typeof(TimeOnly?))
168+
{
169+
expression = Wrap((TimeOnly?)constantExpression.Value);
170+
}
171+
#endif
155172
return;
156173
}
157174

@@ -189,6 +206,24 @@ public void Wrap(ref Expression expression)
189206
{
190207
expression = Wrap(Expression.Lambda<Func<TimeSpan?>>(newExpression).Compile()());
191208
}
209+
#if NET6_0_OR_GREATER
210+
else if (newExpression.Type == typeof(DateOnly))
211+
{
212+
expression = Wrap(Expression.Lambda<Func<DateOnly>>(newExpression).Compile()());
213+
}
214+
else if (newExpression.Type == typeof(DateOnly?))
215+
{
216+
expression = Wrap(Expression.Lambda<Func<DateOnly?>>(newExpression).Compile()());
217+
}
218+
else if (newExpression.Type == typeof(TimeOnly))
219+
{
220+
expression = Wrap(Expression.Lambda<Func<TimeOnly>>(newExpression).Compile()());
221+
}
222+
else if (newExpression.Type == typeof(TimeOnly?))
223+
{
224+
expression = Wrap(Expression.Lambda<Func<TimeOnly?>>(newExpression).Compile()());
225+
}
226+
#endif
192227
}
193228
}
194229

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,23 @@ public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression ri
207207

208208
public Expression? OptimizeStringForEqualityIfPossible(string? text, Type type)
209209
{
210-
if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime))
210+
if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime))
211211
{
212212
return Expression.Constant(dateTime, typeof(DateTime));
213213
}
214+
215+
#if NET6_0_OR_GREATER
216+
if (type == typeof(DateOnly) && DateOnly.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnly))
217+
{
218+
return Expression.Constant(dateOnly, typeof(DateOnly));
219+
}
220+
221+
if (type == typeof(TimeOnly) && TimeOnly.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var timeOnly))
222+
{
223+
return Expression.Constant(timeOnly, typeof(TimeOnly));
224+
}
225+
#endif
226+
214227
#if !NET35
215228
if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid))
216229
{

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

+12-9
Original file line numberDiff line numberDiff line change
@@ -490,19 +490,19 @@ private Expression ParseComparisonOperator()
490490
}
491491
}
492492
}
493-
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null)
493+
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
494494
{
495495
right = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueR), left.Type);
496496
}
497-
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null)
497+
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
498498
{
499499
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
500500
}
501-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null)
501+
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
502502
{
503503
right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type);
504504
}
505-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null)
505+
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
506506
{
507507
left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type);
508508
}
@@ -654,10 +654,11 @@ private Expression ParseShiftOperator()
654654
private Expression ParseAdditive()
655655
{
656656
Expression left = ParseMultiplicative();
657-
while (_textParser.CurrentToken.Id == TokenId.Plus || _textParser.CurrentToken.Id == TokenId.Minus)
657+
while (_textParser.CurrentToken.Id is TokenId.Plus or TokenId.Minus)
658658
{
659659
Token op = _textParser.CurrentToken;
660660
_textParser.NextToken();
661+
661662
Expression right = ParseMultiplicative();
662663
switch (op.Id)
663664
{
@@ -668,12 +669,13 @@ private Expression ParseAdditive()
668669
}
669670
else
670671
{
671-
CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
672+
CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
672673
left = _expressionHelper.GenerateAdd(left, right);
673674
}
674675
break;
676+
675677
case TokenId.Minus:
676-
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
678+
CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
677679
left = _expressionHelper.GenerateSubtract(left, right);
678680
break;
679681
}
@@ -685,8 +687,7 @@ private Expression ParseAdditive()
685687
private Expression ParseMultiplicative()
686688
{
687689
Expression left = ParseUnary();
688-
while (_textParser.CurrentToken.Id == TokenId.Asterisk || _textParser.CurrentToken.Id == TokenId.Slash ||
689-
_textParser.CurrentToken.Id == TokenId.Percent || TokenIdentifierIs("mod"))
690+
while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIdentifierIs("mod"))
690691
{
691692
Token op = _textParser.CurrentToken;
692693
_textParser.NextToken();
@@ -697,9 +698,11 @@ private Expression ParseMultiplicative()
697698
case TokenId.Asterisk:
698699
left = Expression.Multiply(left, right);
699700
break;
701+
700702
case TokenId.Slash:
701703
left = Expression.Divide(left, right);
702704
break;
705+
703706
case TokenId.Percent:
704707
case TokenId.Identifier:
705708
left = Expression.Modulo(left, right);

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ internal static class PredefinedTypesHelper
4444
{ typeof(Guid), 0 },
4545
{ typeof(Math), 0 },
4646
{ typeof(Convert), 0 },
47-
{ typeof(Uri), 0 }
47+
{ typeof(Uri), 0 },
48+
#if NET6_0_OR_GREATER
49+
{ typeof(DateOnly), 0 },
50+
{ typeof(TimeOnly), 0 }
51+
#endif
4852
});
4953

5054
static PredefinedTypesHelper()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;
2+
3+
internal interface IAddAndSubtractSignatures : IArithmeticSignatures
4+
{
5+
void F(TimeSpan x, TimeSpan y);
6+
7+
void F(TimeSpan x, TimeSpan? y);
8+
9+
void F(TimeSpan? x, TimeSpan y);
10+
11+
void F(TimeSpan? x, TimeSpan? y);
12+
13+
void F(DateTime x, DateTime y);
14+
15+
void F(DateTime x, DateTime? y);
16+
17+
void F(DateTime? x, DateTime y);
18+
19+
void F(DateTime? x, DateTime? y);
20+
21+
#if NET6_0_OR_GREATER
22+
void F(DateOnly x, DateOnly y);
23+
24+
void F(DateOnly x, DateOnly? y);
25+
26+
void F(DateOnly? x, DateOnly y);
27+
28+
void F(DateOnly? x, DateOnly? y);
29+
30+
void F(TimeOnly x, TimeOnly y);
31+
32+
void F(TimeOnly x, TimeOnly? y);
33+
34+
void F(TimeOnly? x, TimeOnly y);
35+
36+
void F(TimeOnly? x, TimeOnly? y);
37+
#endif
38+
}

src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs

-10
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands
1+
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;
2+
3+
internal interface IArithmeticSignatures
24
{
3-
internal interface IArithmeticSignatures
4-
{
5-
void F(int x, int y);
6-
void F(uint x, uint y);
7-
void F(long x, long y);
8-
void F(ulong x, ulong y);
9-
void F(float x, float y);
10-
void F(double x, double y);
11-
void F(decimal x, decimal y);
12-
void F(int? x, int? y);
13-
void F(uint? x, uint? y);
14-
void F(long? x, long? y);
15-
void F(ulong? x, ulong? y);
16-
void F(float? x, float? y);
17-
void F(double? x, double? y);
18-
void F(decimal? x, decimal? y);
19-
}
20-
}
5+
void F(int x, int y);
6+
void F(uint x, uint y);
7+
void F(long x, long y);
8+
void F(ulong x, ulong y);
9+
void F(float x, float y);
10+
void F(double x, double y);
11+
void F(decimal x, decimal y);
12+
void F(int? x, int? y);
13+
void F(uint? x, uint? y);
14+
void F(long? x, long? y);
15+
void F(ulong? x, ulong? y);
16+
void F(float? x, float? y);
17+
void F(double? x, double? y);
18+
void F(decimal? x, decimal? y);
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands
1+
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;
2+
3+
internal interface IRelationalSignatures : IArithmeticSignatures
24
{
3-
internal interface IRelationalSignatures : IArithmeticSignatures
4-
{
5-
void F(string x, string y);
6-
void F(char x, char y);
7-
void F(DateTime x, DateTime y);
8-
void F(DateTimeOffset x, DateTimeOffset y);
9-
void F(TimeSpan x, TimeSpan y);
10-
void F(char? x, char? y);
11-
void F(DateTime? x, DateTime? y);
12-
void F(DateTimeOffset? x, DateTimeOffset? y);
13-
void F(TimeSpan? x, TimeSpan? y);
14-
}
15-
}
5+
void F(string x, string y);
6+
7+
void F(char x, char y);
8+
9+
void F(DateTime x, DateTime y);
10+
11+
void F(DateTimeOffset x, DateTimeOffset y);
12+
13+
void F(TimeSpan x, TimeSpan y);
14+
15+
void F(char? x, char? y);
16+
17+
void F(DateTime? x, DateTime? y);
18+
19+
void F(DateTimeOffset? x, DateTimeOffset? y);
20+
21+
void F(TimeSpan? x, TimeSpan? y);
22+
23+
#if NET6_0_OR_GREATER
24+
void F(DateOnly x, DateOnly y);
25+
26+
void F(DateOnly? x, DateOnly? y);
27+
28+
void F(TimeOnly x, TimeOnly y);
29+
30+
void F(TimeOnly? x, TimeOnly? y);
31+
#endif
32+
}

src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs

-8
This file was deleted.

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,13 @@ public static bool IsStruct(Type type)
279279
{
280280
if (!nonNullableType.GetTypeInfo().IsPrimitive)
281281
{
282-
if (nonNullableType != typeof(decimal) && nonNullableType != typeof(DateTime) && nonNullableType != typeof(Guid))
282+
if
283+
(
284+
#if NET6_0_OR_GREATER
285+
nonNullableType != typeof(DateOnly) && nonNullableType != typeof(TimeOnly) &&
286+
#endif
287+
nonNullableType != typeof(decimal) && nonNullableType != typeof(DateTime) && nonNullableType != typeof(Guid)
288+
)
283289
{
284290
if (!nonNullableType.GetTypeInfo().IsEnum)
285291
{

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

+4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ internal class CustomDateTimeConverter : DateTimeOffsetConverter
1313
/// <param name="value">The object to be converted.</param>
1414
/// <returns>A <see cref="Nullable{DateTime}"></see> that represents the specified object.</returns>
1515
/// <exception cref="NotSupportedException">The conversion cannot be performed.</exception>
16+
#if NET6_0_OR_GREATER
17+
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
18+
#else
1619
public override object? ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
20+
#endif
1721
{
1822
var dateTimeOffset = base.ConvertFrom(context, culture, value) as DateTimeOffset?;
1923

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#if NET6_0
2+
using System.ComponentModel;
3+
using System.Globalization;
4+
5+
namespace System.Linq.Dynamic.Core.TypeConverters;
6+
7+
/// <summary>
8+
/// Based on https://github.com/dotnet/runtime/issues/68743
9+
/// </summary>
10+
internal class DateOnlyConverter : TypeConverter
11+
{
12+
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
13+
{
14+
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
15+
}
16+
17+
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
18+
{
19+
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
20+
}
21+
22+
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
23+
{
24+
if (value is string s)
25+
{
26+
return DateOnly.Parse(s, culture);
27+
}
28+
29+
return base.ConvertFrom(context, culture, value);
30+
}
31+
32+
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
33+
{
34+
if (destinationType == typeof(string))
35+
{
36+
return ((DateOnly?)value)?.ToString(culture);
37+
}
38+
39+
return base.ConvertTo(context, culture, value, destinationType);
40+
}
41+
42+
public override bool IsValid(ITypeDescriptorContext? context, object? value)
43+
{
44+
return value is DateOnly || base.IsValid(context, value);
45+
}
46+
}
47+
#endif

0 commit comments

Comments
 (0)