diff --git a/README.md b/README.md index cb2fec2..62009fc 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ Authors.First() ### Extension Methods ### -Driver provides extension methods for converting string to `T?`. `CultureInfo.InvariantCulture` is used by default. +Driver provides extension methods for converting `string` (and `ReadOnlySpan` for .NET Core/.NET) to `T?`. `CultureInfo.InvariantCulture` is used by default. ```csharp // Bool. @@ -393,11 +393,38 @@ DateTime? ToDateTime( DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null); +// .NET Core/.NET only. +DateTime? ToDateTime( + ReadOnlySpan format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + DateTime? ToDateTime( string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null); +// DateTimeOffset. +DateTimeOffset? ToDateTimeOffset( + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +DateTimeOffset? ToDateTimeOffset( + string format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +// .NET Core/.NET only. +DateTimeOffset? ToDateTimeOffset( + ReadOnlySpan format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +DateTimeOffset? ToDateTimeOffset( + string[] formats, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + // TimeSpan. TimeSpan? ToTimeSpan(CultureInfo? cultureInfo = null); @@ -406,10 +433,58 @@ TimeSpan? ToTimeSpan( TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null); +// .NET Core/.NET only. +TimeSpan? ToTimeSpan( + ReadOnlySpan format, + TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, + CultureInfo? cultureInfo = null); + TimeSpan? ToTimeSpan( string[] formats, TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null); + +// .NET 6+: DateOnly. +DateOnly? ToDateOnly( + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +DateOnly? ToDateOnly( + string format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +// .NET Core/.NET only. +DateOnly? ToDateOnly( + ReadOnlySpan format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +DateOnly? ToDateOnly( + string[] formats, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +// .NET 6+: TimeOnly. +TimeOnly? ToTimeOnly( + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +TimeOnly? ToTimeOnly( + string format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +// .NET Core/.NET only. +TimeOnly? ToTimeOnly( + ReadOnlySpan format, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); + +TimeOnly? ToTimeOnly( + string[] formats, + DateTimeStyles dateTimeStyles = DateTimeStyles.None, + CultureInfo? cultureInfo = null); ``` ## Known Issues ## diff --git a/Src/CsvLINQPadDriver/Directory.Build.props b/Src/CsvLINQPadDriver/Directory.Build.props index 1fb63ad..793d1d3 100644 --- a/Src/CsvLINQPadDriver/Directory.Build.props +++ b/Src/CsvLINQPadDriver/Directory.Build.props @@ -1,7 +1,7 @@  7.3.4 - Update Humanizer.Core. + Added DateTimeOffset, DateOnly/TimeOnly (.NET6+) types conversion support. diff --git a/Src/CsvLINQPadDriver/Extensions/StringExtensions.cs b/Src/CsvLINQPadDriver/Extensions/StringExtensions.cs index 6e8f28b..e3b49b6 100644 --- a/Src/CsvLINQPadDriver/Extensions/StringExtensions.cs +++ b/Src/CsvLINQPadDriver/Extensions/StringExtensions.cs @@ -15,48 +15,191 @@ public static class StringExtensions public static int? ToInt(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(int.TryParse(str, NumberStyles.Integer, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static int? ToInt(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(int.TryParse(str, NumberStyles.Integer, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static long? ToLong(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(long.TryParse(str, NumberStyles.Integer, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static long? ToLong(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(long.TryParse(str, NumberStyles.Integer, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static float? ToFloat(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(float.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static float? ToFloat(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(float.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static double? ToDouble(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(double.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static double? ToDouble(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(double.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static decimal? ToDecimal(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(decimal.TryParse(str, NumberStyles.Number, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static decimal? ToDecimal(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(decimal.TryParse(str, NumberStyles.Number, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static DateTime? ToDateTime(this string? str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => GetValueOrNull(DateTime.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#if NETCOREAPP + public static DateTime? ToDateTime(this ReadOnlySpan str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTime.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + public static DateTime? ToDateTime(this string? str, string format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => GetValueOrNull(DateTime.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#if NETCOREAPP + public static DateTime? ToDateTime(this ReadOnlySpan str, ReadOnlySpan format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTime.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + public static DateTime? ToDateTime(this string? str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => GetValueOrNull(DateTime.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#if NETCOREAPP + public static DateTime? ToDateTime(this ReadOnlySpan str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTime.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + + public static DateTimeOffset? ToDateTimeOffset(this string? str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + +#if NETCOREAPP + public static DateTimeOffset? ToDateTimeOffset(this ReadOnlySpan str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + + public static DateTimeOffset? ToDateTimeOffset(this string? str, string format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + +#if NETCOREAPP + public static DateTimeOffset? ToDateTimeOffset(this ReadOnlySpan str, ReadOnlySpan format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + + public static DateTimeOffset? ToDateTimeOffset(this string? str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + +#if NETCOREAPP + public static DateTimeOffset? ToDateTimeOffset(this ReadOnlySpan str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateTimeOffset.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + public static TimeSpan? ToTimeSpan(this string? str, CultureInfo? cultureInfo = null) => GetValueOrNull(TimeSpan.TryParse(str, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#if NETCOREAPP + public static TimeSpan? ToTimeSpan(this ReadOnlySpan str, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeSpan.TryParse(str, SelectCulture(cultureInfo), out var parsedValue), parsedValue); +#endif + public static TimeSpan? ToTimeSpan(this string? str, string format, TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null) => GetValueOrNull(TimeSpan.TryParseExact(str, format, SelectCulture(cultureInfo), timeSpanStyles, out var parsedValue), parsedValue); +#if NETCOREAPP + public static TimeSpan? ToTimeSpan(this ReadOnlySpan str, ReadOnlySpan format, TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeSpan.TryParseExact(str, format, SelectCulture(cultureInfo), timeSpanStyles, out var parsedValue), parsedValue); +#endif + public static TimeSpan? ToTimeSpan(this string? str, string[] formats, TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null) => GetValueOrNull(TimeSpan.TryParseExact(str, formats, SelectCulture(cultureInfo), timeSpanStyles, out var parsedValue), parsedValue); +#if NETCOREAPP + public static TimeSpan? ToTimeSpan(this ReadOnlySpan str, string[] formats, TimeSpanStyles timeSpanStyles = TimeSpanStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeSpan.TryParseExact(str, formats, SelectCulture(cultureInfo), timeSpanStyles, out var parsedValue), parsedValue); +#endif + +#if NET6_0_OR_GREATER + public static DateOnly? ToDateOnly(this string? str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static DateOnly? ToDateOnly(this ReadOnlySpan str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static DateOnly? ToDateOnly(this string? str, string format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static DateOnly? ToDateOnly(this ReadOnlySpan str, ReadOnlySpan format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static DateOnly? ToDateOnly(this string? str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static DateOnly? ToDateOnly(this ReadOnlySpan str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(DateOnly.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this string? str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this ReadOnlySpan str, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParse(str, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this string? str, string format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this ReadOnlySpan str, ReadOnlySpan format, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParseExact(str, format, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this string? str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); + + public static TimeOnly? ToTimeOnly(this ReadOnlySpan str, string[] formats, DateTimeStyles dateTimeStyles = DateTimeStyles.None, CultureInfo? cultureInfo = null) => + GetValueOrNull(TimeOnly.TryParseExact(str, formats, SelectCulture(cultureInfo), dateTimeStyles, out var parsedValue), parsedValue); +#endif + public static Guid? ToGuid(this string? str) => GetValueOrNull(Guid.TryParse(str, out var parsedValue), parsedValue); +#if NETCOREAPP + public static Guid? ToGuid(this ReadOnlySpan str) => + GetValueOrNull(Guid.TryParse(str, out var parsedValue), parsedValue); +#endif + public static Guid? ToGuid(this string? str, string format) => GetValueOrNull(Guid.TryParseExact(str, format, out var parsedValue), parsedValue); +#if NETCOREAPP + public static Guid? ToGuid(this ReadOnlySpan str, ReadOnlySpan format) => + GetValueOrNull(Guid.TryParseExact(str, format, out var parsedValue), parsedValue); +#endif + // ReSharper disable once ParameterTypeCanBeEnumerable.Global public static Guid? ToGuid(this string? str, string[] formats) => formats .Select(format => GetValueOrNull(Guid.TryParseExact(str, format, out var parsedValue), parsedValue)) .FirstOrDefault(static guid => guid is not null); +#if NETCOREAPP + public static Guid? ToGuid(this ReadOnlySpan str, string[] formats) + { + foreach (var format in formats) + { + var guid = GetValueOrNull(Guid.TryParseExact(str, format, out var parsedValue), parsedValue); + if (guid is not null) + { + return guid; + } + } + + return null; + } +#endif + public static bool? ToBool(this string? str, CultureInfo? cultureInfo = null) { var longValue = str.ToLong(cultureInfo); @@ -66,6 +209,17 @@ public static class StringExtensions : GetValueOrNull(bool.TryParse(str, out var parsedValue), parsedValue); } +#if NETCOREAPP + public static bool? ToBool(this ReadOnlySpan str, CultureInfo? cultureInfo = null) + { + var longValue = str.ToLong(cultureInfo); + + return longValue.HasValue + ? longValue.Value != 0 + : GetValueOrNull(bool.TryParse(str, out var parsedValue), parsedValue); + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static CultureInfo SelectCulture(CultureInfo? cultureInfo) => cultureInfo ?? DefaultCultureInfo; diff --git a/Tests/CsvLINQPadDriverTest/LPRun/Templates/Extensions.linq b/Tests/CsvLINQPadDriverTest/LPRun/Templates/Extensions.linq index 445b8ac..022a2ad 100644 --- a/Tests/CsvLINQPadDriverTest/LPRun/Templates/Extensions.linq +++ b/Tests/CsvLINQPadDriverTest/LPRun/Templates/Extensions.linq @@ -2,6 +2,7 @@ var invalidString = "This is invalid string"; string nullString = null; // Bool. +((ReadOnlySpan)"0").ToBool().Should().BeFalse(Reason()); "0".ToBool().Should().BeFalse(Reason()); "false".ToBool().Should().BeFalse(Reason()); @@ -13,26 +14,31 @@ invalidString.ToBool().Should().BeNull(Reason()); nullString.ToBool().Should().BeNull(Reason()); // Int. +((ReadOnlySpan)"1").ToInt().Should().Be(1, Reason()); "1".ToInt().Should().Be(1, Reason()); invalidString.ToInt().Should().BeNull(Reason()); nullString.ToInt().Should().BeNull(Reason()); // Long. +((ReadOnlySpan)"1").ToLong().Should().Be(1L, Reason()); "1".ToLong().Should().Be(1L, Reason()); invalidString.ToLong().Should().BeNull(Reason()); nullString.ToLong().Should().BeNull(Reason()); // Float. +((ReadOnlySpan)"1.23").ToFloat().Should().Be(1.23f, Reason()); "1.23".ToFloat().Should().Be(1.23f, Reason()); invalidString.ToFloat().Should().BeNull(Reason()); nullString.ToFloat().Should().BeNull(Reason()); // Double. +((ReadOnlySpan)"1.23").ToDouble().Should().Be(1.23, Reason()); "1.23".ToDouble().Should().Be(1.23, Reason()); invalidString.ToDouble().Should().BeNull(Reason()); nullString.ToDouble().Should().BeNull(Reason()); // Decimal. +((ReadOnlySpan)"1").ToDecimal().Should().Be(1m, Reason()); "1".ToDecimal().Should().Be(1m, Reason()); invalidString.ToDecimal().Should().BeNull(Reason()); nullString.ToDecimal().Should().BeNull(Reason()); @@ -43,9 +49,12 @@ var guidFormat = "D"; var guidFormats = new [] { "X", guidFormat }; var guidString = expectedGuid.ToString(guidFormat); +((ReadOnlySpan)expectedGuid.ToString()).ToGuid().Should().Be(expectedGuid, Reason()); expectedGuid.ToString().ToGuid().Should().Be(expectedGuid, Reason()); +((ReadOnlySpan)guidString).ToGuid((ReadOnlySpan)guidFormat).Should().Be(expectedGuid, Reason()); guidString.ToGuid(guidFormat).Should().Be(expectedGuid, Reason()); +((ReadOnlySpan)guidString).ToGuid(guidFormats).Should().Be(expectedGuid, Reason()); guidString.ToGuid(guidFormats).Should().Be(expectedGuid, Reason()); invalidString.ToGuid().Should().BeNull(Reason()); @@ -62,28 +71,49 @@ var dateTimeFormat = @"yyyy-MM-dd hh\:mm\:ss"; var dateTimeFormats = new[] { dateTimeFormat.Replace(@"\:ss", ""), dateTimeFormat }; var dateTimeString = expectedDateTime.ToString(dateTimeFormat); +((ReadOnlySpan)expectedDateTime.ToString()).ToDateTime().Should().Be(expectedDateTime, Reason()); expectedDateTime.ToString().ToDateTime().Should().Be(expectedDateTime, Reason()); +((ReadOnlySpan)dateTimeString).ToDateTime((ReadOnlySpan)dateTimeFormat).Should().Be(expectedDateTime, Reason()); dateTimeString.ToDateTime(dateTimeFormat).Should().Be(expectedDateTime, Reason()); +((ReadOnlySpan)dateTimeString).ToDateTime(dateTimeFormats).Should().Be(expectedDateTime, Reason()); dateTimeString.ToDateTime(dateTimeFormats).Should().Be(expectedDateTime, Reason()); invalidString.ToDateTime().Should().BeNull(Reason()); -invalidString.ToDateTime(dateTimeFormat).Should().BeNull(Reason()); invalidString.ToDateTime(dateTimeFormats).Should().BeNull(Reason()); nullString.ToDateTime().Should().BeNull(Reason()); nullString.ToDateTime(dateTimeFormat).Should().BeNull(Reason()); nullString.ToDateTime(dateTimeFormats).Should().BeNull(Reason()); +// DateTimeOffset. +((ReadOnlySpan)expectedDateTime.ToString()).ToDateTimeOffset().Should().Be(expectedDateTime, Reason()); +expectedDateTime.ToString().ToDateTimeOffset().Should().Be(expectedDateTime, Reason()); + +((ReadOnlySpan)dateTimeString).ToDateTimeOffset((ReadOnlySpan)dateTimeFormat).Should().Be(expectedDateTime, Reason()); +dateTimeString.ToDateTimeOffset(dateTimeFormat).Should().Be(expectedDateTime, Reason()); +((ReadOnlySpan)dateTimeString).ToDateTimeOffset(dateTimeFormats).Should().Be(expectedDateTime, Reason()); +dateTimeString.ToDateTimeOffset(dateTimeFormats).Should().Be(expectedDateTime, Reason()); + +invalidString.ToDateTimeOffset().Should().BeNull(Reason()); +invalidString.ToDateTimeOffset(dateTimeFormats).Should().BeNull(Reason()); + +nullString.ToDateTimeOffset().Should().BeNull(Reason()); +nullString.ToDateTimeOffset(dateTimeFormat).Should().BeNull(Reason()); +nullString.ToDateTimeOffset(dateTimeFormats).Should().BeNull(Reason()); + // TimeSpan. var expectedTimeSpan = new TimeSpan(9, 12, 34, 56); var timeSpanFormat = @"d\:hh\:mm\:ss"; var timeSpanFormats = new[] { timeSpanFormat.Replace(@"\:ss", ""), timeSpanFormat }; var timeSpanString = expectedTimeSpan.ToString(timeSpanFormat); +((ReadOnlySpan)expectedTimeSpan.ToString()).ToTimeSpan().Should().Be(expectedTimeSpan, Reason()); expectedTimeSpan.ToString().ToTimeSpan().Should().Be(expectedTimeSpan, Reason()); +((ReadOnlySpan)timeSpanString).ToTimeSpan((ReadOnlySpan)timeSpanFormat).Should().Be(expectedTimeSpan, Reason()); timeSpanString.ToTimeSpan(timeSpanFormat).Should().Be(expectedTimeSpan, Reason()); +((ReadOnlySpan)timeSpanString).ToTimeSpan(timeSpanFormats).Should().Be(expectedTimeSpan, Reason()); timeSpanString.ToTimeSpan(timeSpanFormats).Should().Be(expectedTimeSpan, Reason()); invalidString.ToTimeSpan().Should().BeNull(Reason()); @@ -93,3 +123,48 @@ invalidString.ToTimeSpan(timeSpanFormats).Should().BeNull(Reason()); nullString.ToTimeSpan().Should().BeNull(Reason()); nullString.ToTimeSpan(timeSpanFormat).Should().BeNull(Reason()); nullString.ToTimeSpan(timeSpanFormats).Should().BeNull(Reason()); + +#if NET6_0_OR_GREATER +// DateOnly. +var expectedDateOnly = new DateOnly(1978, 9, 30); +var dateOnlyFormat = @"yyyy-MM-dd"; +var dateOnlyFormats = new[] { dateOnlyFormat.Replace("-dd", ""), dateOnlyFormat }; +var dateOnlyString = expectedDateOnly.ToString(dateOnlyFormat); + +((ReadOnlySpan)expectedDateOnly.ToString()).ToDateOnly().Should().Be(expectedDateOnly, Reason()); +expectedDateOnly.ToString().ToDateOnly().Should().Be(expectedDateOnly, Reason()); + +((ReadOnlySpan)dateOnlyString).ToDateOnly((ReadOnlySpan)dateOnlyFormat).Should().Be(expectedDateOnly, Reason()); +dateOnlyString.ToDateOnly(dateOnlyFormat).Should().Be(expectedDateOnly, Reason()); +((ReadOnlySpan)dateOnlyString).ToDateOnly(dateOnlyFormats).Should().Be(expectedDateOnly, Reason()); +dateOnlyString.ToDateOnly(dateOnlyFormats).Should().Be(expectedDateOnly, Reason()); + +invalidString.ToDateOnly().Should().BeNull(Reason()); +invalidString.ToDateOnly(dateOnlyFormats).Should().BeNull(Reason()); + +nullString.ToDateOnly().Should().BeNull(Reason()); +nullString.ToDateOnly(dateOnlyFormat).Should().BeNull(Reason()); +nullString.ToDateOnly(dateOnlyFormats).Should().BeNull(Reason()); + +// TimeOnly. +var expectedTimeOnly = new TimeOnly(9, 10, 0); +var timeOnlyFormat = @"hh\:mm"; +var timeOnlyFormats = new[] { timeOnlyFormat.Replace(@"\:mm", ""), timeOnlyFormat }; +var timeOnlyString = expectedTimeOnly.ToString(timeOnlyFormat); + +((ReadOnlySpan)expectedTimeOnly.ToString()).ToTimeOnly().Should().Be(expectedTimeOnly, Reason()); +expectedTimeOnly.ToString().ToTimeOnly().Should().Be(expectedTimeOnly, Reason()); + +((ReadOnlySpan)timeOnlyString).ToTimeOnly((ReadOnlySpan)timeOnlyFormat).Should().Be(expectedTimeOnly, Reason()); +timeOnlyString.ToTimeOnly(timeOnlyFormat).Should().Be(expectedTimeOnly, Reason()); +((ReadOnlySpan)timeOnlyString).ToTimeOnly(timeOnlyFormats).Should().Be(expectedTimeOnly, Reason()); +timeOnlyString.ToTimeOnly(timeOnlyFormats).Should().Be(expectedTimeOnly, Reason()); + +invalidString.ToTimeOnly().Should().BeNull(Reason()); +invalidString.ToTimeOnly(timeOnlyFormat).Should().BeNull(Reason()); +invalidString.ToTimeOnly(timeOnlyFormats).Should().BeNull(Reason()); + +nullString.ToTimeOnly().Should().BeNull(Reason()); +nullString.ToTimeOnly(timeOnlyFormat).Should().BeNull(Reason()); +nullString.ToTimeOnly(timeOnlyFormats).Should().BeNull(Reason()); +#endif diff --git a/Tests/CsvLINQPadDriverTest/LPRunTests.cs b/Tests/CsvLINQPadDriverTest/LPRunTests.cs index cb74eaa..01f3163 100644 --- a/Tests/CsvLINQPadDriverTest/LPRunTests.cs +++ b/Tests/CsvLINQPadDriverTest/LPRunTests.cs @@ -119,49 +119,47 @@ IEnumerable> GetTestData() { const string? noContext = null; - var linqScriptNames = new[] - { - "Generation", - "Relations" - }; - // Multiple driver properties. yield return GetCsvDataContextDriverProperties() - .SelectMany(driverProperties => - linqScriptNames.Select(linqScriptName => new ScriptWithDriverPropertiesTestData - (linqScriptName, - $"new {{ {nameof(driverProperties.UseSingleClassForSameFiles)} = {driverProperties.UseSingleClassForSameFiles.ToString().ToLowerInvariant()} }}", - driverProperties, - driverProperties.UseRecordType ? "USE_RECORD_TYPE" : null))); + .SelectMany(static driverProperties => new[] { "Generation", "Relations"} + .Select(linqScriptName => new ScriptWithDriverPropertiesTestData( + linqScriptName, + $"new {{ {nameof(driverProperties.UseSingleClassForSameFiles)} = {driverProperties.UseSingleClassForSameFiles.ToString().ToLowerInvariant()} }}", + driverProperties, + driverProperties.UseRecordType ? "USE_RECORD_TYPE" : null))); // Single driver properties. yield return new[] { "Extensions", "SimilarFilesRelations" } - .Select(linqFile => new ScriptWithDriverPropertiesTestData - (linqFile, - noContext, - defaultCsvDataContextDriverProperties)); + .Select(linqFile => new ScriptWithDriverPropertiesTestData( + linqFile, + noContext, + defaultCsvDataContextDriverProperties +#if NET6_0_OR_GREATER + , "NET6_0_OR_GREATER" +#endif + )); // String comparison. yield return GetStringComparisons() - .SelectMany(static stringComparison => - new[] { "StringComparison", "Encoding" }.Select(linqFile => new ScriptWithDriverPropertiesTestData - (linqFile, + .SelectMany(static stringComparison => new[] { "StringComparison", "Encoding" } + .Select(linqFile => new ScriptWithDriverPropertiesTestData( + linqFile, GetStringComparisonContext(stringComparison), GetDefaultCsvDataContextDriverPropertiesObject(stringComparison)))); // String comparison for interning. yield return GetStringComparisons() - .Select(static stringComparison => new ScriptWithDriverPropertiesTestData - ("StringComparisonForInterning", - GetStringComparisonContext(stringComparison), - GetDefaultCsvDataContextDriverPropertiesObject(stringComparison, useStringComparerForStringIntern: true))); + .Select(static stringComparison => new ScriptWithDriverPropertiesTestData( + "StringComparisonForInterning", + GetStringComparisonContext(stringComparison), + GetDefaultCsvDataContextDriverPropertiesObject(stringComparison, useStringComparerForStringIntern: true))); // Allow comments. yield return new[] { true, false } - .Select(static allowComments => new ScriptWithDriverPropertiesTestData - ("Comments", - $"new {{ ExpectedCount = {(allowComments ? 1 : 2)} }}", - GetDefaultCsvDataContextDriverPropertiesObject(defaultStringComparison, allowComments))); + .Select(static allowComments => new ScriptWithDriverPropertiesTestData( + "Comments", + $"new {{ ExpectedCount = {(allowComments ? 1 : 2)} }}", + GetDefaultCsvDataContextDriverPropertiesObject(defaultStringComparison, allowComments))); } IEnumerable GetCsvDataContextDriverProperties()