diff --git a/TwitchDownloaderCLI.Tests/ModelTests/TimeDurationTests.cs b/TwitchDownloaderCLI.Tests/ModelTests/TimeDurationTests.cs index 6a320f7f..22e14a8f 100644 --- a/TwitchDownloaderCLI.Tests/ModelTests/TimeDurationTests.cs +++ b/TwitchDownloaderCLI.Tests/ModelTests/TimeDurationTests.cs @@ -16,6 +16,11 @@ public class TimeDurationTests [InlineData("0:09:27", 9 * TicksPerMinute + 27 * TicksPerSecond)] [InlineData("11:30", 11 * TicksPerHour + 30 * TicksPerMinute)] [InlineData("12:03:45", 12 * TicksPerHour + 3 * TicksPerMinute + 45 * TicksPerSecond)] + [InlineData("39:23:02", 39 * TicksPerHour + 23 * TicksPerMinute + 2 * TicksPerSecond)] + [InlineData("47:22:08.123", 47 * TicksPerHour + 22 * TicksPerMinute + 8 * TicksPerSecond + 123 * TicksPerMillisecond)] + [InlineData("47:22:08.12345", 47 * TicksPerHour + 22 * TicksPerMinute + 8 * TicksPerSecond + 123 * TicksPerMillisecond)] + [InlineData("1.2:3:4.5", 1 * TicksPerDay + 2 * TicksPerHour + 3 * TicksPerMinute + 4 * TicksPerSecond + 500 * TicksPerMillisecond)] + [InlineData("2:03:54:27.26", 2 * TicksPerDay + 3 * TicksPerHour + 54 * TicksPerMinute + 27 * TicksPerSecond + 260 * TicksPerMillisecond)] public void CorrectlyParsesTimeStrings(string input, long expectedTicks) { var expected = new TimeDuration(expectedTicks); @@ -33,10 +38,7 @@ public void CorrectlyParsesTimeStrings(string input, long expectedTicks) [InlineData("0:12345")] public void ThrowsOnBadFormat(string input) { - Assert.ThrowsAny(() => - { - _ = TimeDuration.Parse(input); - }); + Assert.ThrowsAny(() => TimeDuration.Parse(input)); } } } \ No newline at end of file diff --git a/TwitchDownloaderCLI/Models/TimeDuration.cs b/TwitchDownloaderCLI/Models/TimeDuration.cs index 67daabf9..1b28d504 100644 --- a/TwitchDownloaderCLI/Models/TimeDuration.cs +++ b/TwitchDownloaderCLI/Models/TimeDuration.cs @@ -1,11 +1,9 @@ using System; -using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; namespace TwitchDownloaderCLI.Models { - [DebuggerDisplay("{_timeSpan}")] public readonly record struct TimeDuration { public static TimeDuration MinusOneSeconds { get; } = new(-1 * TimeSpan.TicksPerSecond); @@ -30,6 +28,8 @@ public TimeDuration(long ticks) _timeSpan = TimeSpan.FromTicks(ticks); } + public override string ToString() => _timeSpan.ToString(); + public static TimeDuration Parse(string str) { if (string.IsNullOrWhiteSpace(str)) @@ -37,9 +37,11 @@ public static TimeDuration Parse(string str) throw new FormatException(); } + str = str.Trim(); + if (str.Contains(':')) { - var timeSpan = TimeSpan.Parse(str); + var timeSpan = ParseTimeSpan(str); return new TimeDuration(timeSpan); } @@ -53,6 +55,24 @@ public static TimeDuration Parse(string str) throw new FormatException(); } + private static TimeSpan ParseTimeSpan(string str) + { + // TimeSpan.Parse interprets '36:01:02' as 36 days, 1 hour, and 2 minutes, so we need to manually parse it ourselves + var match = Regex.Match(str, @"^(?:(\d{1,})[.:])?(\d{2,}):(\d{1,2}):(\d{1,2})(?:\.(\d{1,3})\d*)?$"); + if (match.Success) + { + if (!int.TryParse(match.Groups[1].ValueSpan, out var days)) days = 0; + if (!int.TryParse(match.Groups[2].ValueSpan, out var hours)) hours = 0; + if (!int.TryParse(match.Groups[3].ValueSpan, out var minutes)) minutes = 0; + if (!int.TryParse(match.Groups[4].ValueSpan, out var seconds)) seconds = 0; + if (!int.TryParse(match.Groups[5].Value.PadRight(3, '0'), out var milliseconds)) milliseconds = 0; + + return new TimeSpan(days, hours, minutes, seconds, milliseconds); + } + + return TimeSpan.Parse(str); // Parse formats not covered by the regex + } + private static long GetMultiplier(string input, out ReadOnlySpan trimmedInput) { if (char.IsDigit(input[^1]))