diff --git a/X10D/src/StringExtensions/StringExtensions.cs b/X10D/src/StringExtensions/StringExtensions.cs index 77e5905..a618fe5 100644 --- a/X10D/src/StringExtensions/StringExtensions.cs +++ b/X10D/src/StringExtensions/StringExtensions.cs @@ -414,20 +414,21 @@ namespace X10D.StringExtensions } /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// . + /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of . /// - /// The input string. + /// The input string. /// Returns an instance of . - /// is . - public static TimeSpan ToTimeSpan(this string str) + /// is . + public static TimeSpan ToTimeSpan(this string input) { - if (str is null) + if (input is null) { - throw new ArgumentNullException(nameof(str)); + throw new ArgumentNullException(nameof(input)); } - return TimeSpanParser.Parse(str); + return TimeSpanParser.TryParse(input, out var result) + ? result + : default; } /// diff --git a/X10D/src/TimeSpanParser.cs b/X10D/src/TimeSpanParser.cs index 464543c..e681f57 100644 --- a/X10D/src/TimeSpanParser.cs +++ b/X10D/src/TimeSpanParser.cs @@ -1,5 +1,5 @@ using System; -using System.Diagnostics; +using System.Globalization; using System.Text.RegularExpressions; namespace X10D @@ -9,69 +9,96 @@ namespace X10D /// public static class TimeSpanParser { + private const string RealNumberPattern = @"(\d*\.\d+|\d+)"; + + private static readonly string Pattern = $"^(?:{RealNumberPattern} *y)? *" + + $"^(?:{RealNumberPattern} *mo)? *" + + $"^(?:{RealNumberPattern} *w)? *" + + $"(?:{RealNumberPattern} *d)? *" + + $"(?:{RealNumberPattern} *h)? *" + + $"(?:{RealNumberPattern} *m)? *" + + $"(?:{RealNumberPattern} *s)? *" + + $"(?:{RealNumberPattern} *ms)?$"; + + private static readonly Regex Regex = new(Pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// which represents that duration of time. + /// Attempts to parses a shorthand time span string (e.g. 3w 2d 1.5h), converting it to an instance of + /// which represents that duration of time. /// /// The input string. + /// The parsed result. /// The format provider. - /// An instance of constructed from the parsed . - public static TimeSpan Parse(string input, IFormatProvider? provider = null) + /// if the parse was successful, otherwise. + public static bool TryParse(string input, out TimeSpan result, IFormatProvider? provider = null) { - const string realNumberPattern = @"([0-9]*\.[0-9]+|[0-9]+)"; - var pattern = $"^(?:{realNumberPattern} *w)? *" + - $"(?:{realNumberPattern} *d)? *" + - $"(?:{realNumberPattern} *h)? *" + - $"(?:{realNumberPattern} *m)? *" + - $"(?:{realNumberPattern} *s)? *" + - $"(?:{realNumberPattern} *ms)?$"; + result = default; - var match = Regex.Match(input, pattern); - double weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0; + var match = Regex.Match(input); - if (match.Groups[1].Success) + if (!match.Success) { - weeks = double.Parse(match.Groups[1].Value, provider); + return false; } - if (match.Groups[2].Success) + bool TryParseAt(int group, out double parsedResult) { - days = double.Parse(match.Groups[2].Value, provider); + parsedResult = 0; + + return match.Groups[@group].Success + && double.TryParse(match.Groups[@group].Value, NumberStyles.Number, provider, out parsedResult); } - if (match.Groups[3].Success) + if (!TryParseAt(1, out var years)) { - hours = double.Parse(match.Groups[3].Value, provider); + return false; } - if (match.Groups[4].Success) + if (!TryParseAt(2, out var months)) { - minutes = double.Parse(match.Groups[4].Value, provider); + return false; } - if (match.Groups[5].Success) + if (!TryParseAt(3, out var weeks)) { - seconds = double.Parse(match.Groups[5].Value, provider); + return false; } - if (match.Groups[6].Success) + if (!TryParseAt(4, out var days)) { - milliseconds = double.Parse(match.Groups[6].Value, provider); + return false; } - Trace.WriteLine($"Input: {input}"); - Trace.WriteLine($"Parsed: {weeks}w {days}d {hours}h {minutes}m {seconds}s {milliseconds}ms"); + if (!TryParseAt(5, out var hours)) + { + return false; + } - var span = TimeSpan.Zero; + if (!TryParseAt(6, out var minutes)) + { + return false; + } - span += TimeSpan.FromDays(weeks * 7); - span += TimeSpan.FromDays(days); - span += TimeSpan.FromHours(hours); - span += TimeSpan.FromMinutes(minutes); - span += TimeSpan.FromSeconds(seconds); - span += TimeSpan.FromMilliseconds(milliseconds); + if (!TryParseAt(7, out var seconds)) + { + return false; + } - return span; + if (!TryParseAt(8, out var milliseconds)) + { + return false; + } + + result += TimeSpan.FromDays(years * 365); + result += TimeSpan.FromDays(months * 30); + result += TimeSpan.FromDays(weeks * 7); + result += TimeSpan.FromDays(days); + result += TimeSpan.FromHours(hours); + result += TimeSpan.FromMinutes(minutes); + result += TimeSpan.FromSeconds(seconds); + result += TimeSpan.FromMilliseconds(milliseconds); + + return true; } } }