diff --git a/src/Exceptionless.DateTimeExtensions/TimeSpanExtensions.cs b/src/Exceptionless.DateTimeExtensions/TimeSpanExtensions.cs index dc6e527..d84537c 100644 --- a/src/Exceptionless.DateTimeExtensions/TimeSpanExtensions.cs +++ b/src/Exceptionless.DateTimeExtensions/TimeSpanExtensions.cs @@ -88,6 +88,17 @@ public static AgeSpan ToAgeSpan(this TimeSpan span) { return new AgeSpan(span); } + + /// + /// Subtracts the specified TimeSpan from this TimeSpan, ensuring the result never goes below TimeSpan.Zero. + /// + /// The TimeSpan to subtract from. + /// The TimeSpan to subtract. + /// The result of the subtraction, or TimeSpan.Zero if the result would be negative. + public static TimeSpan SubtractSaturating(this TimeSpan self, TimeSpan other) + { + return self >= other ? self.Subtract(other) : TimeSpan.Zero; + } } public struct AgeSpan diff --git a/tests/Exceptionless.DateTimeExtensions.Tests/TimeSpanExtensionTests.cs b/tests/Exceptionless.DateTimeExtensions.Tests/TimeSpanExtensionTests.cs index e2c1806..27de18f 100644 --- a/tests/Exceptionless.DateTimeExtensions.Tests/TimeSpanExtensionTests.cs +++ b/tests/Exceptionless.DateTimeExtensions.Tests/TimeSpanExtensionTests.cs @@ -65,4 +65,90 @@ public void ApproximateAge() Assert.Equal("2 days ago", DateTime.Now.AddHours(-48).ToApproximateAgeString()); Assert.Equal("1 week ago", DateTime.Now.AddDays(-7).ToApproximateAgeString()); } + + [Fact] + public void SubtractSaturating_WithLargerValue_ReturnsNormalSubtraction() + { + var timeSpan1 = TimeSpan.FromHours(5); + var timeSpan2 = TimeSpan.FromHours(2); + + var result = timeSpan1.SubtractSaturating(timeSpan2); + + Assert.Equal(TimeSpan.FromHours(3), result); + } + + [Fact] + public void SubtractSaturating_WithSmallerValue_ReturnsZero() + { + var timeSpan1 = TimeSpan.FromHours(2); + var timeSpan2 = TimeSpan.FromHours(5); + + var result = timeSpan1.SubtractSaturating(timeSpan2); + + Assert.Equal(TimeSpan.Zero, result); + } + + [Fact] + public void SubtractSaturating_WithEqualValues_ReturnsZero() + { + var timeSpan1 = TimeSpan.FromMinutes(30); + var timeSpan2 = TimeSpan.FromMinutes(30); + + var result = timeSpan1.SubtractSaturating(timeSpan2); + + Assert.Equal(TimeSpan.Zero, result); + } + + [Fact] + public void SubtractSaturating_WithZeroValue_ReturnsOriginalValue() + { + var timeSpan = TimeSpan.FromSeconds(10); + + var result = timeSpan.SubtractSaturating(TimeSpan.Zero); + + Assert.Equal(TimeSpan.FromSeconds(10), result); + } + + [Fact] + public void SubtractSaturating_WithZeroAsBase_ReturnsZero() + { + var result1 = TimeSpan.Zero.SubtractSaturating(TimeSpan.FromSeconds(5)); + var result2 = TimeSpan.Zero.SubtractSaturating(TimeSpan.Zero); + + Assert.Equal(TimeSpan.Zero, result1); + Assert.Equal(TimeSpan.Zero, result2); + } + + [Fact] + public void SubtractSaturating_WithLargeValues_ReturnsCorrectResult() + { + var largeTimeSpan1 = TimeSpan.FromDays(100); + var largeTimeSpan2 = TimeSpan.FromDays(50); + + var result = largeTimeSpan1.SubtractSaturating(largeTimeSpan2); + + Assert.Equal(TimeSpan.FromDays(50), result); + } + + [Fact] + public void SubtractSaturating_WithNegativeBase_ReturnsZero() + { + var negativeTimeSpan = TimeSpan.FromHours(-2); + var positiveTimeSpan = TimeSpan.FromHours(1); + + var result = negativeTimeSpan.SubtractSaturating(positiveTimeSpan); + + Assert.Equal(TimeSpan.Zero, result); + } + + [Fact] + public void SubtractSaturating_WithNegativeSubtrahend_ReturnsAddedValue() + { + var timeSpan = TimeSpan.FromHours(3); + var negativeTimeSpan = TimeSpan.FromHours(-2); + + var result = timeSpan.SubtractSaturating(negativeTimeSpan); + + Assert.Equal(TimeSpan.FromHours(5), result); + } }