| | 1 | | using System; |
| | 2 | | using System.Globalization; |
| | 3 | | using System.Text.RegularExpressions; |
| | 4 | |
|
| | 5 | | namespace Plainquire.Filter.Abstractions; |
| | 6 | |
|
| | 7 | | /// <summary> |
| | 8 | | /// Extension methods for <see cref="DateTime"/>. |
| | 9 | | /// </summary> |
| | 10 | | public static class DateTimeRangeExtensions |
| | 11 | | { |
| | 12 | | /// <summary> |
| | 13 | | /// Creates a new <see cref="Range{TType}"/> using given values. Values not given are expanded to start and end of i |
| | 14 | | /// </summary> |
| | 15 | | private static Range<DateTimeOffset> CreateDateTimeRange(int? year = null, int? month = null, int? day = null, int? |
| | 16 | | { |
| 7643 | 17 | | var start = new DateTimeOffset( |
| 7643 | 18 | | year ?? DateTimeOffset.MinValue.Year, |
| 7643 | 19 | | month ?? 1, |
| 7643 | 20 | | day ?? 1, |
| 7643 | 21 | | hour ?? 0, |
| 7643 | 22 | | minute ?? 0, |
| 7643 | 23 | | second ?? 0, |
| 7643 | 24 | | millisecond ?? 0, |
| 7643 | 25 | | offset); |
| | 26 | |
|
| 7571 | 27 | | var end = start; |
| 7571 | 28 | | if (year == null) |
| 0 | 29 | | end = DateTimeOffset.MaxValue; |
| 7571 | 30 | | else if (month == null) |
| 408 | 31 | | end = end.AddYears(1); |
| 7163 | 32 | | else if (day == null) |
| 360 | 33 | | end = end.AddMonths(1); |
| 6803 | 34 | | else if (hour == null) |
| 3228 | 35 | | end = end.AddDays(1); |
| 3575 | 36 | | else if (minute == null) |
| 216 | 37 | | end = end.AddHours(1); |
| 3359 | 38 | | else if (second == null) |
| 360 | 39 | | end = end.AddMinutes(1); |
| 2999 | 40 | | else if (millisecond == null) |
| 2999 | 41 | | end = end.AddSeconds(1); |
| | 42 | | else |
| 0 | 43 | | end = end.AddMilliseconds(millisecond.Value); |
| | 44 | |
|
| 7571 | 45 | | return new Range<DateTimeOffset>(start, end); |
| | 46 | | } |
| | 47 | |
|
| | 48 | | internal static bool TryConvertDateTimeRangeFormattedString(string value, IFormatProvider? cultureInfo, out Range<Da |
| | 49 | | { |
| 7179 | 50 | | dateTimeRange = new Range<DateTimeOffset>(DateTimeOffset.MinValue, DateTimeOffset.MinValue); |
| | 51 | |
|
| 7179 | 52 | | var startAndEnd = Regex.Match(value, "^(?<start>.+?)_(?<end>.+)$", RegexOptions.None, RegexDefaults.Timeout); |
| 7179 | 53 | | if (!startAndEnd.Success) |
| 5415 | 54 | | return false; |
| | 55 | |
|
| 1764 | 56 | | var startParsed = TryConvertIso8601FormattedString(startAndEnd.Groups["start"].Value, cultureInfo, out var start |
| 1764 | 57 | | var endParsed = TryConvertIso8601FormattedString(startAndEnd.Groups["end"].Value, cultureInfo, out var end); |
| 1764 | 58 | | if (!startParsed || !endParsed) |
| 180 | 59 | | return false; |
| | 60 | |
|
| 1584 | 61 | | dateTimeRange = new Range<DateTimeOffset>(start.Start, end.End); |
| 1584 | 62 | | return true; |
| | 63 | | } |
| | 64 | |
|
| | 65 | | internal static bool TryConvertIso8601FormattedString(string value, IFormatProvider? cultureInfo, out Range<DateTime |
| | 66 | | { |
| 9123 | 67 | | dateTimeRange = new Range<DateTimeOffset>(DateTimeOffset.MinValue, DateTimeOffset.MinValue); |
| | 68 | |
|
| | 69 | | const string offsetPattern = @"^(?<datetime>.+?)(?<offset>Z|[\+\-]\d{1,2}:\d{1,2})?$"; |
| | 70 | | const string dateTimePattern = @"^(?<year>\d\d\d\d)(\D(?<month>\d\d))?(\D(?<day>\d\d))?(\D(?<hour>\d\d))?(\D(?<m |
| | 71 | |
|
| 9123 | 72 | | var dateTimeAndOffset = Regex.Match(value, offsetPattern, RegexOptions.None, RegexDefaults.Timeout); |
| 9123 | 73 | | if (!dateTimeAndOffset.Success) |
| 324 | 74 | | return false; |
| | 75 | |
|
| 8799 | 76 | | var dateTime = dateTimeAndOffset.Groups["datetime"].Value; |
| 8799 | 77 | | var offset = dateTimeAndOffset.Groups["offset"].Value; |
| | 78 | |
|
| 8799 | 79 | | var dateAndTime = Regex.Match(dateTime, dateTimePattern, RegexOptions.ExplicitCapture, RegexDefaults.Timeout); |
| 8799 | 80 | | if (!dateAndTime.Success) |
| 1156 | 81 | | return false; |
| | 82 | |
|
| | 83 | | try |
| | 84 | | { |
| 7643 | 85 | | var year = ParseDateTimePart(dateAndTime, "year", cultureInfo) ?? throw new InvalidOperationException($"Year |
| 7643 | 86 | | var month = ParseDateTimePart(dateAndTime, "month", cultureInfo); |
| 7643 | 87 | | var day = ParseDateTimePart(dateAndTime, "day", cultureInfo); |
| 7643 | 88 | | var hour = ParseDateTimePart(dateAndTime, "hour", cultureInfo); |
| 7643 | 89 | | var minute = ParseDateTimePart(dateAndTime, "minute", cultureInfo); |
| 7643 | 90 | | var second = ParseDateTimePart(dateAndTime, "second", cultureInfo); |
| 7643 | 91 | | var offsetTimeSpan = ParseOffset(offset); |
| 7643 | 92 | | dateTimeRange = CreateDateTimeRange(year, month, day, hour, minute, second, null, offsetTimeSpan); |
| 7571 | 93 | | return true; |
| | 94 | | } |
| 144 | 95 | | catch (ArgumentException) { } |
| | 96 | |
|
| 72 | 97 | | return false; |
| 7571 | 98 | | } |
| | 99 | |
|
| | 100 | | internal static bool TryConvertUnknownFormattedString(string value, CultureInfo? cultureInfo, out Range<DateTimeOffs |
| | 101 | | { |
| 1084 | 102 | | var result = DateTimeOffset.TryParse(value, cultureInfo, DateTimeStyles.AssumeUniversal, out var startDate); |
| | 103 | |
|
| | 104 | | DateTimeOffset endDate; |
| 1084 | 105 | | if (startDate.Second != 0) |
| 0 | 106 | | endDate = startDate.AddSeconds(1); |
| 1084 | 107 | | else if (startDate.Minute != 0) |
| 0 | 108 | | endDate = startDate.AddMinutes(1); |
| 1084 | 109 | | else if (startDate.Hour != 0) |
| 0 | 110 | | endDate = startDate.AddHours(1); |
| 1084 | 111 | | else if (startDate.Day != 1) |
| 72 | 112 | | endDate = startDate.AddDays(1); |
| 1012 | 113 | | else if (startDate.Month != 1) |
| 72 | 114 | | endDate = startDate.AddMonths(1); |
| | 115 | | else |
| 940 | 116 | | endDate = startDate.AddYears(1); |
| | 117 | |
|
| 1084 | 118 | | dateTimeRange = new Range<DateTimeOffset>(startDate, endDate); |
| 1084 | 119 | | return result; |
| | 120 | | } |
| | 121 | |
|
| | 122 | | private static int? ParseDateTimePart(Match lMatch, string name, IFormatProvider? cultureInfo) |
| 45858 | 123 | | => int.TryParse(lMatch.Groups[name].Value, NumberStyles.Any, cultureInfo, out var intValue) ? intValue : null; |
| | 124 | |
|
| | 125 | | private static TimeSpan ParseOffset(string offset) |
| | 126 | | { |
| 7643 | 127 | | if (string.IsNullOrEmpty(offset) || offset.EqualsOrdinal("Z")) |
| 5099 | 128 | | return TimeSpan.Zero; |
| | 129 | |
|
| 2544 | 130 | | var offsetHasSign = offset[0] == '+' || offset[0] == '-'; |
| 2544 | 131 | | var absoluteOffset = offsetHasSign ? offset[1..] : offset; |
| 2544 | 132 | | if (!TimeSpan.TryParse(absoluteOffset, out var timeSpan)) |
| 0 | 133 | | return TimeSpan.Zero; |
| | 134 | |
|
| 2544 | 135 | | var signMultiplier = offset[0] == '+' ? 1 : -1; |
| 2544 | 136 | | return timeSpan * signMultiplier; |
| | 137 | | } |
| | 138 | | } |