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);
+ }
}