using System.Globalization; using System.Text.RegularExpressions; using JetBrains.Annotations; using Remora.Commands.Parsers; using Remora.Results; namespace TeamOctolings.Octobot.Parsers; /// <summary> /// Parses <see cref="TimeSpan"/>s. /// </summary> [PublicAPI] public partial class TimeSpanParser : AbstractTypeParser<TimeSpan> { private static readonly Regex Pattern = ParseRegex(); /// <summary> /// Parses a <see cref="TimeSpan"/> from the <paramref name="timeSpanString"/>. /// </summary> /// <returns> /// The parsed <see cref="TimeSpan"/>, or <see cref="ArgumentInvalidError"/> if parsing failed. /// </returns> public static Result<TimeSpan> TryParse(string timeSpanString) { if (timeSpanString.StartsWith('-')) { return new ArgumentInvalidError(nameof(timeSpanString), "TimeSpans cannot be negative."); } if (TimeSpan.TryParse(timeSpanString, DateTimeFormatInfo.InvariantInfo, out var parsedTimeSpan)) { return parsedTimeSpan; } var matches = ParseRegex().Matches(timeSpanString); return matches.Count > 0 ? ParseFromRegex(matches) : new ArgumentInvalidError(nameof(timeSpanString), "The regex did not produce any matches."); } private static Result<TimeSpan> ParseFromRegex(MatchCollection matches) { var timeSpan = TimeSpan.Zero; foreach (var groups in matches.Select(match => match.Groups .Cast<Group>() .Where(g => g.Success) .Skip(1) .Select(g => (g.Name, g.Value)))) { foreach ((var key, var groupValue) in groups) { if (!int.TryParse(groupValue, out var parsedIntegerValue)) { return new ArgumentInvalidError(nameof(groupValue), "The input value was not an integer."); } var now = DateTimeOffset.UtcNow; timeSpan += key switch { "Years" => now.AddYears(parsedIntegerValue) - now, "Months" => now.AddMonths(parsedIntegerValue) - now, "Weeks" => TimeSpan.FromDays(parsedIntegerValue * 7), "Days" => TimeSpan.FromDays(parsedIntegerValue), "Hours" => TimeSpan.FromHours(parsedIntegerValue), "Minutes" => TimeSpan.FromMinutes(parsedIntegerValue), "Seconds" => TimeSpan.FromSeconds(parsedIntegerValue), _ => throw new ArgumentOutOfRangeException(key) }; } } return timeSpan; } [GeneratedRegex("(?<Years>\\d+(?=y|л|г))|(?<Months>\\d+(?=mo|мес))|(?<Weeks>\\d+(?=w|н|нед))|(?<Days>\\d+(?=d|д|дн))|(?<Hours>\\d+(?=h|ч))|(?<Minutes>\\d+(?=m|min|мин|м))|(?<Seconds>\\d+(?=s|sec|с|сек))")] private static partial Regex ParseRegex(); }