Skip to content

Commit

Permalink
Fix parsing CLI time durations >24 hours in timespan notation (#1134)
Browse files Browse the repository at this point in the history
* Trim input string

* Fix parsing timespans >24 hours

* Update tests

* Override TimeDuration.ToString()
  • Loading branch information
ScrubN authored Jul 6, 2024
1 parent b2ffb6d commit 4df6672
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 7 deletions.
10 changes: 6 additions & 4 deletions TwitchDownloaderCLI.Tests/ModelTests/TimeDurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -33,10 +38,7 @@ public void CorrectlyParsesTimeStrings(string input, long expectedTicks)
[InlineData("0:12345")]
public void ThrowsOnBadFormat(string input)
{
Assert.ThrowsAny<Exception>(() =>
{
_ = TimeDuration.Parse(input);
});
Assert.ThrowsAny<Exception>(() => TimeDuration.Parse(input));
}
}
}
26 changes: 23 additions & 3 deletions TwitchDownloaderCLI/Models/TimeDuration.cs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -30,16 +28,20 @@ public TimeDuration(long ticks)
_timeSpan = TimeSpan.FromTicks(ticks);
}

public override string ToString() => _timeSpan.ToString();

public static TimeDuration Parse(string str)
{
if (string.IsNullOrWhiteSpace(str))
{
throw new FormatException();
}

str = str.Trim();

if (str.Contains(':'))
{
var timeSpan = TimeSpan.Parse(str);
var timeSpan = ParseTimeSpan(str);
return new TimeDuration(timeSpan);
}

Expand All @@ -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<char> trimmedInput)
{
if (char.IsDigit(input[^1]))
Expand Down

0 comments on commit 4df6672

Please sign in to comment.