diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs index 201afbf6..39e7a429 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs @@ -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; } @@ -189,6 +206,24 @@ public void Wrap(ref Expression expression) { expression = Wrap(Expression.Lambda>(newExpression).Compile()()); } +#if NET6_0_OR_GREATER + else if (newExpression.Type == typeof(DateOnly)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(DateOnly?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(TimeOnly)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(TimeOnly?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } +#endif } } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index 5900e037..75dd8752 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -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)) { diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index cb840f6a..22089efe 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -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(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null) + else if (_expressionHelper.TryUnwrapConstantExpression(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(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null) + else if (_expressionHelper.TryUnwrapConstantExpression(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type)) { left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type); } @@ -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) { @@ -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; } @@ -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(); @@ -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); diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs index d42377a8..3bfe8bb6 100644 --- a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -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() diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs new file mode 100644 index 00000000..a329e961 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs @@ -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 +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs deleted file mode 100644 index b8b78ae1..00000000 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace System.Linq.Dynamic.Core.Parser.SupportedOperands -{ - internal interface IAddSignatures : IArithmeticSignatures - { - void F(DateTime x, TimeSpan y); - void F(TimeSpan x, TimeSpan y); - void F(DateTime? x, TimeSpan? y); - void F(TimeSpan? x, TimeSpan? y); - } -} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs index 602face9..1021c660 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs @@ -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); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs index b91855ed..8c1c9c1b 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs @@ -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 +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs deleted file mode 100644 index 19eec926..00000000 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace System.Linq.Dynamic.Core.Parser.SupportedOperands -{ - internal interface ISubtractSignatures : IAddSignatures - { - void F(DateTime x, DateTime y); - void F(DateTime? x, DateTime? y); - } -} diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index c648bc1f..8ef4bc98 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -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) { diff --git a/src/System.Linq.Dynamic.Core/TypeConverters/CustomDateTimeConverter.cs b/src/System.Linq.Dynamic.Core/TypeConverters/CustomDateTimeConverter.cs index a0b4e954..5a47c55f 100644 --- a/src/System.Linq.Dynamic.Core/TypeConverters/CustomDateTimeConverter.cs +++ b/src/System.Linq.Dynamic.Core/TypeConverters/CustomDateTimeConverter.cs @@ -13,7 +13,11 @@ internal class CustomDateTimeConverter : DateTimeOffsetConverter /// The object to be converted. /// A that represents the specified object. /// The conversion cannot be performed. +#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?; diff --git a/src/System.Linq.Dynamic.Core/TypeConverters/DateOnlyConverter.cs b/src/System.Linq.Dynamic.Core/TypeConverters/DateOnlyConverter.cs new file mode 100644 index 00000000..460c7e35 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/TypeConverters/DateOnlyConverter.cs @@ -0,0 +1,47 @@ +#if NET6_0 +using System.ComponentModel; +using System.Globalization; + +namespace System.Linq.Dynamic.Core.TypeConverters; + +/// +/// Based on https://github.com/dotnet/runtime/issues/68743 +/// +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 \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/TypeConverters/TimeOnlyConverter.cs b/src/System.Linq.Dynamic.Core/TypeConverters/TimeOnlyConverter.cs new file mode 100644 index 00000000..1cd8df1b --- /dev/null +++ b/src/System.Linq.Dynamic.Core/TypeConverters/TimeOnlyConverter.cs @@ -0,0 +1,47 @@ +#if NET6_0 +using System.ComponentModel; +using System.Globalization; + +namespace System.Linq.Dynamic.Core.TypeConverters; + +/// +/// Based on https://github.com/dotnet/runtime/issues/68743 +/// +internal class TimeOnlyConverter : 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 TimeOnly.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 ((TimeOnly?)value)?.ToString(culture); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool IsValid(ITypeDescriptorContext? context, object? value) + { + return value is TimeOnly || base.IsValid(context, value); + } +} +#endif \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs b/src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs index d6a4bc92..4115669c 100644 --- a/src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs +++ b/src/System.Linq.Dynamic.Core/TypeConverters/TypeConverterFactory.cs @@ -2,49 +2,57 @@ using System.Linq.Dynamic.Core.Parser; using System.Linq.Dynamic.Core.Validation; -namespace System.Linq.Dynamic.Core.TypeConverters +namespace System.Linq.Dynamic.Core.TypeConverters; + +internal class TypeConverterFactory : ITypeConverterFactory { - internal class TypeConverterFactory : ITypeConverterFactory + private readonly ParsingConfig _config; + +#if NET6_0 + static TypeConverterFactory() { - private readonly ParsingConfig _config; + TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyConverter))); + TypeDescriptor.AddAttributes(typeof(TimeOnly), new TypeConverterAttribute(typeof(TimeOnlyConverter))); + } +#endif + + public TypeConverterFactory(ParsingConfig config) + { + _config = Check.NotNull(config); + } - public TypeConverterFactory(ParsingConfig config) + /// + public TypeConverter GetConverter(Type type) + { + Check.NotNull(type); + + if (_config.DateTimeIsParsedAsUTC && (type == typeof(DateTime) || type == typeof(DateTime?))) { - _config = Check.NotNull(config); + return new CustomDateTimeConverter(); } - /// - public TypeConverter GetConverter(Type type) + var typeToCheck = TypeHelper.IsNullableType(type) ? TypeHelper.GetNonNullableType(type) : type; + if (_config.TypeConverters != null && _config.TypeConverters.TryGetValue(typeToCheck, out var typeConverter)) { - Check.NotNull(type); - - if (_config.DateTimeIsParsedAsUTC && (type == typeof(DateTime) || type == typeof(DateTime?))) - { - return new CustomDateTimeConverter(); - } - - var typeToCheck = TypeHelper.IsNullableType(type) ? TypeHelper.GetNonNullableType(type) : type; - if (_config.TypeConverters != null && _config.TypeConverters.TryGetValue(typeToCheck, out var typeConverter)) - { - return typeConverter; - } + return typeConverter; + } #if !SILVERLIGHT - return TypeDescriptor.GetConverter(type); + var c = TypeDescriptor.GetConverter(type); + return c; #else - var attributes = type.GetCustomAttributes(typeof(TypeConverterAttribute), false); + var attributes = type.GetCustomAttributes(typeof(TypeConverterAttribute), false); - if (attributes.Length != 1) - return new TypeConverter(); + if (attributes.Length != 1) + return new TypeConverter(); - var converterAttribute = (TypeConverterAttribute)attributes[0]; - var converterType = Type.GetType(converterAttribute.ConverterTypeName); + var converterAttribute = (TypeConverterAttribute)attributes[0]; + var converterType = Type.GetType(converterAttribute.ConverterTypeName); - if (converterType == null) - return new TypeConverter(); + if (converterType == null) + return new TypeConverter(); - return Activator.CreateInstance(converterType) as TypeConverter; + return Activator.CreateInstance(converterType) as TypeConverter; #endif - } } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index b9b8341a..f951d6e3 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -623,6 +623,53 @@ public void ExpressionTests_DateTimeString() Assert.Equal(lst[0], result2.Single()); } +#if NET6_0_OR_GREATER + [Fact] + public void ExpressionTests_DateOnlyString() + { + // Arrange + var now = new DateTime(2023, 2, 10, 12, 13, 14); + var lst = new List + { + DateOnly.FromDateTime(now), + DateOnly.FromDateTime(now.AddDays(1)), + DateOnly.FromDateTime(now.AddDays(2)) + }; + var qry = lst.AsQueryable(); + + // Act + var testValue = lst[0].ToString(CultureInfo.InvariantCulture); + var result1 = qry.Where("it = @0", testValue); + var result2 = qry.Where("@0 = it", testValue); + + // Assert + Assert.Equal(lst[0], result1.Single()); + Assert.Equal(lst[0], result2.Single()); + } + + [Fact] + public void ExpressionTests_TimeOnlyString() + { + // Arrange + var now = new DateTime(2023, 2, 10, 12, 13, 14); + var lst = new List + { + TimeOnly.FromDateTime(now), + TimeOnly.FromDateTime(now.AddSeconds(1)), + TimeOnly.FromDateTime(now.AddSeconds(2)) + }; + var qry = lst.AsQueryable(); + + // Act + var testValue = "12:13:14"; + var result1 = qry.Where("it = @0", testValue); + var result2 = qry.Where("@0 = it", testValue); + + // Assert + Assert.Equal(lst[0], result1.Single()); + Assert.Equal(lst[0], result2.Single()); + } +#endif [Fact] public void ExpressionTests_DecimalQualifiers() { diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs index d79ca134..0129e36e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs @@ -84,6 +84,100 @@ public void ParseTypeAccess_Via_Constructor_Any_To_DateTime_Invalid(object any) a.Should().Throw(); } +#if NET6_0_OR_GREATER + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_DateOnly_Valid() + { + // Arrange + string str = "\"2020-10-31\""; + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({str})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_Arguments_To_DateOnly_Valid() + { + // Arrange + var arguments = "2022, 10, 31"; + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({arguments})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(DateOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("\"abc\"")] + public void ParseTypeAccess_Via_Constructor_Any_To_DateOnly_Invalid(object any) + { + // Arrange + var parameter = Expression.Parameter(typeof(DateOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"DateOnly({any})", new object[] { }, ParsingConfig.Default); + Action a = () => parser.Parse(typeof(DateOnly)); + + // Assert + a.Should().Throw(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_String_To_TimeOnly_Valid() + { + // Arrange + string str = "\"09:15:11\""; + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({str})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(TimeOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Fact] + public void ParseTypeAccess_Via_Constructor_Arguments_To_TimeOnly_Valid() + { + // Arrange + var arguments = "9, 15, 11"; + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({arguments})", new object[] { }, ParsingConfig.Default); + var expression = parser.Parse(typeof(TimeOnly)); + + // Assert + expression.ToString().Should().NotBeEmpty(); + } + + [Theory] + [InlineData(null)] + [InlineData("\"abc\"")] + public void ParseTypeAccess_Via_Constructor_Any_To_TimeOnly_Invalid(object any) + { + // Arrange + var parameter = Expression.Parameter(typeof(TimeOnly)); + + // Act + var parser = new ExpressionParser(new[] { parameter }, $"TimeOnly({any})", new object[] { }, ParsingConfig.Default); + Action a = () => parser.Parse(typeof(TimeOnly)); + + // Assert + a.Should().Throw(); + } +#endif + [Fact] public void ParseTypeAccess_Via_Constructor_String_To_Uri() {