This seems like a good place to use regular expressions; in particular, capture groups.
The following is a working example:
using System; using System.Text.RegularExpressions; namespace RegexCaptureGroups { class Program { // Below is a breakdown of this regular expression: // First, one or more digits followed by "d" or "D" to represent days. // Second, one or more digits followed by "h" or "H" to represent hours. // Third, one or more digits followed by "m" or "M" to represent minutes. // Each component can be separated by any number of spaces, or none. private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d+)d)?\s*((?<Hours>\d+)h)?\s*((?<Minutes>\d+)m)?", RegexOptions.IgnoreCase); public static TimeSpan ParseDuration(string input) { var match = DurationRegex.Match(input); var days = match.Groups["Days"].Value; var hours = match.Groups["Hours"].Value; var minutes = match.Groups["Minutes"].Value; int daysAsInt32, hoursAsInt32, minutesAsInt32; if (!int.TryParse(days, out daysAsInt32)) daysAsInt32 = 0; if (!int.TryParse(hours, out hoursAsInt32)) hoursAsInt32 = 0; if (!int.TryParse(minutes, out minutesAsInt32)) minutesAsInt32 = 0; return new TimeSpan(daysAsInt32, hoursAsInt32, minutesAsInt32, 0); } static void Main(string[] args) { Console.WriteLine(ParseDuration("30d")); Console.WriteLine(ParseDuration("12h")); Console.WriteLine(ParseDuration("20m")); Console.WriteLine(ParseDuration("1d 12h")); Console.WriteLine(ParseDuration("5d 30m")); Console.WriteLine(ParseDuration("1d 12h 20m")); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } }
EDIT: Below is an alternative, a slightly more succinct version above, although I'm not sure which one I prefer more. I'm usually not a fan of code that is too tight. I adjusted the regular expression to put a limit of 10 digits on each number. This allows me to use the int.Parse function int.Parse , because I know that the input consists of at least one digit and no more than ten (unless it is fixed at all, in which case it will be an empty string: therefore, the purpose of the ParseInt32ZeroIfNullOrEmpty method )
// Below is a breakdown of this regular expression: // First, one to ten digits followed by "d" or "D" to represent days. // Second, one to ten digits followed by "h" or "H" to represent hours. // Third, one to ten digits followed by "m" or "M" to represent minutes. // Each component can be separated by any number of spaces, or none. private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d{1,10})d)?\s*((?<Hours>\d{1,10})h)?\s*((?<Minutes>\d{1,10})m)?", RegexOptions.IgnoreCase); private static int ParseInt32ZeroIfNullOrEmpty(string input) { return string.IsNullOrEmpty(input) ? 0 : int.Parse(input); } public static TimeSpan ParseDuration(string input) { var match = DurationRegex.Match(input); return new TimeSpan( ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value), 0); }
EDIT: just to take this one more step, I added another version that handles days, hours, minutes, seconds and milliseconds, with different abbreviations for each. I split the regex into several lines for readability. Note that I also had to adjust the expression using (\b|(?=[^az])) at the end of each component: this is because the "ms" block was captured as the "m" unit. The special syntax "? =" Used with "[az]" indicates a match for the character, but does not "consume" it.
// Below is a breakdown of this regular expression: // First, one to ten digits followed by "d", "dy", "dys", "day", or "days". // Second, one to ten digits followed by "h", "hr", "hrs", "hour", or "hours". // Third, one to ten digits followed by "m", "min", "minute", or "minutes". // Fourth, one to ten digits followed by "s", "sec", "second", or "seconds". // Fifth, one to ten digits followed by "ms", "msec", "millisec", "millisecond", or "milliseconds". // Each component may be separated by any number of spaces, or none. // The expression is case-insensitive. private static readonly Regex DurationRegex = new Regex(@" ((?<Days>\d{1,10})(d|dy|dys|day|days)(\b|(?=[^az])))?\s* ((?<Hours>\d{1,10})(h|hr|hrs|hour|hours)(\b|(?=[^az])))?\s* ((?<Minutes>\d{1,10})(m|min|minute|minutes)(\b|(?=[^az])))?\s* ((?<Seconds>\d{1,10})(s|sec|second|seconds)(\b|(?=[^az])))?\s* ((?<Milliseconds>\d{1,10})(ms|msec|millisec|millisecond|milliseconds)(\b|(?=[^az])))?", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); private static int ParseInt32ZeroIfNullOrEmpty(string input) { return string.IsNullOrEmpty(input) ? 0 : int.Parse(input); } public static TimeSpan ParseDuration(string input) { var match = DurationRegex.Match(input); return new TimeSpan( ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Seconds"].Value), ParseInt32ZeroIfNullOrEmpty(match.Groups["Milliseconds"].Value)); }