Skip to content

Commit

Permalink
Add support for DateOnly and TimeOnly (#671)
Browse files Browse the repository at this point in the history
* DateOnly

* TimeOnly

* fix
  • Loading branch information
StefH committed Feb 11, 2023
1 parent f5676b0 commit 5cf8357
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 93 deletions.
37 changes: 36 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,24 @@ public void Wrap(ref Expression expression)
{
expression = Wrap((TimeSpan?)constantExpression.Value);
}

#if NET6_0_OR_GREATER
else if (constantExpression.Type == typeof(DateOnly))
{
expression = Wrap((DateOnly)constantExpression.Value);
}
else if (constantExpression.Type == typeof(DateOnly?))
{
expression = Wrap((DateOnly?)constantExpression.Value);
}
else if (constantExpression.Type == typeof(TimeOnly))
{
expression = Wrap((TimeOnly)constantExpression.Value);
}
else if (constantExpression.Type == typeof(TimeOnly?))
{
expression = Wrap((TimeOnly?)constantExpression.Value);
}
#endif
return;
}

Expand Down Expand Up @@ -189,6 +206,24 @@ public void Wrap(ref Expression expression)
{
expression = Wrap(Expression.Lambda<Func<TimeSpan?>>(newExpression).Compile()());
}
#if NET6_0_OR_GREATER
else if (newExpression.Type == typeof(DateOnly))
{
expression = Wrap(Expression.Lambda<Func<DateOnly>>(newExpression).Compile()());
}
else if (newExpression.Type == typeof(DateOnly?))
{
expression = Wrap(Expression.Lambda<Func<DateOnly?>>(newExpression).Compile()());
}
else if (newExpression.Type == typeof(TimeOnly))
{
expression = Wrap(Expression.Lambda<Func<TimeOnly>>(newExpression).Compile()());
}
else if (newExpression.Type == typeof(TimeOnly?))
{
expression = Wrap(Expression.Lambda<Func<TimeOnly?>>(newExpression).Compile()());
}
#endif
}
}

Expand Down
15 changes: 14 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,23 @@ public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression ri

public Expression? OptimizeStringForEqualityIfPossible(string? text, Type type)
{
if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime))
if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime))
{
return Expression.Constant(dateTime, typeof(DateTime));
}

#if NET6_0_OR_GREATER
if (type == typeof(DateOnly) && DateOnly.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnly))
{
return Expression.Constant(dateOnly, typeof(DateOnly));
}

if (type == typeof(TimeOnly) && TimeOnly.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var timeOnly))
{
return Expression.Constant(timeOnly, typeof(TimeOnly));
}
#endif

#if !NET35
if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid))
{
Expand Down
21 changes: 12 additions & 9 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,19 +490,19 @@ private Expression ParseComparisonOperator()
}
}
}
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null)
else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
{
right = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueR), left.Type);
}
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null)
else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
{
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
}
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null)
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
{
right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type);
}
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null)
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
{
left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type);
}
Expand Down Expand Up @@ -654,10 +654,11 @@ private Expression ParseShiftOperator()
private Expression ParseAdditive()
{
Expression left = ParseMultiplicative();
while (_textParser.CurrentToken.Id == TokenId.Plus || _textParser.CurrentToken.Id == TokenId.Minus)
while (_textParser.CurrentToken.Id is TokenId.Plus or TokenId.Minus)
{
Token op = _textParser.CurrentToken;
_textParser.NextToken();

Expression right = ParseMultiplicative();
switch (op.Id)
{
Expand All @@ -668,12 +669,13 @@ private Expression ParseAdditive()
}
else
{
CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = _expressionHelper.GenerateAdd(left, right);
}
break;

case TokenId.Minus:
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = _expressionHelper.GenerateSubtract(left, right);
break;
}
Expand All @@ -685,8 +687,7 @@ private Expression ParseAdditive()
private Expression ParseMultiplicative()
{
Expression left = ParseUnary();
while (_textParser.CurrentToken.Id == TokenId.Asterisk || _textParser.CurrentToken.Id == TokenId.Slash ||
_textParser.CurrentToken.Id == TokenId.Percent || TokenIdentifierIs("mod"))
while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIdentifierIs("mod"))
{
Token op = _textParser.CurrentToken;
_textParser.NextToken();
Expand All @@ -697,9 +698,11 @@ private Expression ParseMultiplicative()
case TokenId.Asterisk:
left = Expression.Multiply(left, right);
break;

case TokenId.Slash:
left = Expression.Divide(left, right);
break;

case TokenId.Percent:
case TokenId.Identifier:
left = Expression.Modulo(left, right);
Expand Down
6 changes: 5 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ internal static class PredefinedTypesHelper
{ typeof(Guid), 0 },
{ typeof(Math), 0 },
{ typeof(Convert), 0 },
{ typeof(Uri), 0 }
{ typeof(Uri), 0 },
#if NET6_0_OR_GREATER
{ typeof(DateOnly), 0 },
{ typeof(TimeOnly), 0 }
#endif
});

static PredefinedTypesHelper()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;

internal interface IAddAndSubtractSignatures : IArithmeticSignatures
{
void F(TimeSpan x, TimeSpan y);

void F(TimeSpan x, TimeSpan? y);

void F(TimeSpan? x, TimeSpan y);

void F(TimeSpan? x, TimeSpan? y);

void F(DateTime x, DateTime y);

void F(DateTime x, DateTime? y);

void F(DateTime? x, DateTime y);

void F(DateTime? x, DateTime? y);

#if NET6_0_OR_GREATER
void F(DateOnly x, DateOnly y);

void F(DateOnly x, DateOnly? y);

void F(DateOnly? x, DateOnly y);

void F(DateOnly? x, DateOnly? y);

void F(TimeOnly x, TimeOnly y);

void F(TimeOnly x, TimeOnly? y);

void F(TimeOnly? x, TimeOnly y);

void F(TimeOnly? x, TimeOnly? y);
#endif
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;

internal interface IArithmeticSignatures
{
internal interface IArithmeticSignatures
{
void F(int x, int y);
void F(uint x, uint y);
void F(long x, long y);
void F(ulong x, ulong y);
void F(float x, float y);
void F(double x, double y);
void F(decimal x, decimal y);
void F(int? x, int? y);
void F(uint? x, uint? y);
void F(long? x, long? y);
void F(ulong? x, ulong? y);
void F(float? x, float? y);
void F(double? x, double? y);
void F(decimal? x, decimal? y);
}
}
void F(int x, int y);
void F(uint x, uint y);
void F(long x, long y);
void F(ulong x, ulong y);
void F(float x, float y);
void F(double x, double y);
void F(decimal x, decimal y);
void F(int? x, int? y);
void F(uint? x, uint? y);
void F(long? x, long? y);
void F(ulong? x, ulong? y);
void F(float? x, float? y);
void F(double? x, double? y);
void F(decimal? x, decimal? y);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands
namespace System.Linq.Dynamic.Core.Parser.SupportedOperands;

internal interface IRelationalSignatures : IArithmeticSignatures
{
internal interface IRelationalSignatures : IArithmeticSignatures
{
void F(string x, string y);
void F(char x, char y);
void F(DateTime x, DateTime y);
void F(DateTimeOffset x, DateTimeOffset y);
void F(TimeSpan x, TimeSpan y);
void F(char? x, char? y);
void F(DateTime? x, DateTime? y);
void F(DateTimeOffset? x, DateTimeOffset? y);
void F(TimeSpan? x, TimeSpan? y);
}
}
void F(string x, string y);

void F(char x, char y);

void F(DateTime x, DateTime y);

void F(DateTimeOffset x, DateTimeOffset y);

void F(TimeSpan x, TimeSpan y);

void F(char? x, char? y);

void F(DateTime? x, DateTime? y);

void F(DateTimeOffset? x, DateTimeOffset? y);

void F(TimeSpan? x, TimeSpan? y);

#if NET6_0_OR_GREATER
void F(DateOnly x, DateOnly y);

void F(DateOnly? x, DateOnly? y);

void F(TimeOnly x, TimeOnly y);

void F(TimeOnly? x, TimeOnly? y);
#endif
}

This file was deleted.

8 changes: 7 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,13 @@ public static bool IsStruct(Type type)
{
if (!nonNullableType.GetTypeInfo().IsPrimitive)
{
if (nonNullableType != typeof(decimal) && nonNullableType != typeof(DateTime) && nonNullableType != typeof(Guid))
if
(
#if NET6_0_OR_GREATER
nonNullableType != typeof(DateOnly) && nonNullableType != typeof(TimeOnly) &&
#endif
nonNullableType != typeof(decimal) && nonNullableType != typeof(DateTime) && nonNullableType != typeof(Guid)
)
{
if (!nonNullableType.GetTypeInfo().IsEnum)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ internal class CustomDateTimeConverter : DateTimeOffsetConverter
/// <param name="value">The object to be converted.</param>
/// <returns>A <see cref="Nullable{DateTime}"></see> that represents the specified object.</returns>
/// <exception cref="NotSupportedException">The conversion cannot be performed.</exception>
#if NET6_0_OR_GREATER
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
#else
public override object? ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
#endif
{
var dateTimeOffset = base.ConvertFrom(context, culture, value) as DateTimeOffset?;

Expand Down
47 changes: 47 additions & 0 deletions src/System.Linq.Dynamic.Core/TypeConverters/DateOnlyConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#if NET6_0
using System.ComponentModel;
using System.Globalization;

namespace System.Linq.Dynamic.Core.TypeConverters;

/// <summary>
/// Based on https://github.com/dotnet/runtime/issues/68743
/// </summary>
internal class DateOnlyConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}

public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string s)
{
return DateOnly.Parse(s, culture);
}

return base.ConvertFrom(context, culture, value);
}

public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((DateOnly?)value)?.ToString(culture);
}

return base.ConvertTo(context, culture, value, destinationType);
}

public override bool IsValid(ITypeDescriptorContext? context, object? value)
{
return value is DateOnly || base.IsValid(context, value);
}
}
#endif

0 comments on commit 5cf8357

Please sign in to comment.