diff --git a/TrayApp.Test/CommandLineParserTests.cs b/TrayApp.Test/CommandLineParserTests.cs
index c685c21..b2b05cc 100644
--- a/TrayApp.Test/CommandLineParserTests.cs
+++ b/TrayApp.Test/CommandLineParserTests.cs
@@ -68,9 +68,18 @@ public void TryParse_WithAnUnreadableDuration_Fails()
}
[TestMethod]
- public void TryParse_WithADurationLongerThanATimerCanWait_FailsWithAUsageError()
+ public void TryParse_WithADurationUnderATick_Fails()
{
- Assert.IsFalse(CommandLineParser.TryParse(["--for", "25d"], NoOptions, out _, out string error));
+ Assert.IsFalse(CommandLineParser.TryParse(["--for", "0.00000001s"], NoOptions, out _, out string error));
+ Assert.Contains("is not a duration", error, StringComparison.Ordinal);
+ }
+
+ [TestMethod]
+ [DataRow("25d")]
+ [DataRow("20000000d")]
+ public void TryParse_WithADurationLongerThanATimerCanWait_FailsWithAUsageError(string text)
+ {
+ Assert.IsFalse(CommandLineParser.TryParse(["--for", text], NoOptions, out _, out string error));
Assert.Contains("--for can be at most 24 days", error, StringComparison.Ordinal);
}
diff --git a/TrayApp.Test/DurationParserTests.cs b/TrayApp.Test/DurationParserTests.cs
index f584e9a..5357f0d 100644
--- a/TrayApp.Test/DurationParserTests.cs
+++ b/TrayApp.Test/DurationParserTests.cs
@@ -64,6 +64,8 @@ public void TryParse_WithABareNumberAfterAUnit_ReturnsFalse()
[DataRow("5x")]
[DataRow("m5")]
[DataRow("1e9h")]
+ [DataRow("0.00000001s")]
+ [DataRow("0.00000004s")]
public void TryParse_WithAnInvalidDuration_ReturnsFalse(string? text)
{
Assert.IsFalse(DurationParser.TryParse(text, out TimeSpan duration));
@@ -81,6 +83,8 @@ public void TryParse_AtTheLongestWaitATimerTakes_ReturnsTheDuration()
[TestMethod]
[DataRow("25d")]
[DataRow("24d21h")]
+ [DataRow("20000000d")]
+ [DataRow("99999999999999999999999999999999999999999999999999999999999999999999h")]
public void TryParse_LongerThanATimerCanWait_ReturnsFalse(string text)
{
// ManualResetEventSlim.Wait and the tray's timer both throw past int.MaxValue milliseconds, about
@@ -89,6 +93,26 @@ public void TryParse_LongerThanATimerCanWait_ReturnsFalse(string text)
Assert.AreEqual(TimeSpan.Zero, duration);
}
+ [TestMethod]
+ [DataRow("0.00000001s")]
+ [DataRow("0.00000004s")]
+ public void TryParseAnyLength_WithADurationThatRoundsToZero_ReturnsFalse(string text)
+ {
+ // The value is positive, but under one tick, so it would be a zero-length run.
+ Assert.IsFalse(DurationParser.TryParseAnyLength(text, out TimeSpan duration));
+ Assert.AreEqual(TimeSpan.Zero, duration);
+ }
+
+ [TestMethod]
+ [DataRow("20000000d")]
+ [DataRow("99999999999999999999999999999999999999999999999999999999999999999999h")]
+ public void TryParseAnyLength_WithADurationBeyondTimeSpan_ReturnsTheLongestTimeSpan(string text)
+ {
+ // It is a duration, just one too long to hold, so the caller reports it as too long.
+ Assert.IsTrue(DurationParser.TryParseAnyLength(text, out TimeSpan duration));
+ Assert.AreEqual(TimeSpan.MaxValue, duration);
+ }
+
[TestMethod]
public void MaxDuration_IsTheLongestTimeoutAWaitTakes()
{
diff --git a/TrayApp/Cli/DurationParser.cs b/TrayApp/Cli/DurationParser.cs
index 531bf9b..e5824bc 100644
--- a/TrayApp/Cli/DurationParser.cs
+++ b/TrayApp/Cli/DurationParser.cs
@@ -52,7 +52,10 @@ public static bool TryParse(string? text, out TimeSpan duration)
///
/// The text to read.
/// Receives the duration when parsing succeeds.
- /// when is a positive duration.
+ ///
+ /// when is a duration of at least one tick. A duration too long
+ /// for to hold is returned as .
+ ///
internal static bool TryParseAnyLength(string? text, out TimeSpan duration)
{
duration = TimeSpan.Zero;
@@ -78,12 +81,29 @@ internal static bool TryParseAnyLength(string? text, out TimeSpan duration)
sawAnyPart = true;
}
- if (!sawAnyPart || totalSeconds <= 0 || totalSeconds > TimeSpan.MaxValue.TotalSeconds)
+ if (!sawAnyPart || totalSeconds <= 0)
+ {
+ return false;
+ }
+
+ // A value TimeSpan cannot hold is still a duration, just one too long for anything to wait out, so it
+ // is reported as the longest TimeSpan and the caller's upper bound turns it away as too long.
+ if (totalSeconds >= TimeSpan.MaxValue.TotalSeconds)
+ {
+ duration = TimeSpan.MaxValue;
+ return true;
+ }
+
+ TimeSpan converted = TimeSpan.FromSeconds(totalSeconds);
+
+ // The check above ran on the double: a value under one tick passes it and then rounds to zero, which
+ // would start the tool and stop it in the same moment.
+ if (converted <= TimeSpan.Zero)
{
return false;
}
- duration = TimeSpan.FromSeconds(totalSeconds);
+ duration = converted;
return true;
}