From 8fd097b0da203ac929eb4e4055e04fe3216ca3f6 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 23 Aug 2022 10:17:21 +0100 Subject: [PATCH] Implement IComparable for the Duration type (C#) Note that unlike Timestamp, this does *not* also overload comparison operators. Adding a `==` overload now would be a breaking change (as it would change the meaning of existing code from a reference comparison to a value comparison), and implementing `<`, `<=`, `>=` and `>` without implementing `==` would be odd. Implementing `IComparable` makes sorting much easier, however. Fixes #7628 --- .../WellKnownTypes/DurationTest.cs | 33 +++++++++++++++++++ .../WellKnownTypes/DurationPartial.cs | 23 ++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs index 141faf80e096..8124f0c91d6b 100644 --- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs +++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/DurationTest.cs @@ -128,5 +128,38 @@ public void ToString_NonNormalized() var duration = new Duration { Seconds = 1, Nanos = -1 }; Assert.AreEqual("{ \"@warning\": \"Invalid Duration\", \"seconds\": \"1\", \"nanos\": -1 }", duration.ToString()); } + + [Test] + public void Comparability() + { + Duration[] durationsInExpectedSortOrder = + { + null, + new Duration { Seconds = -10, Nanos = -10 }, + new Duration { Seconds = -10, Nanos = -1 }, + new Duration { Seconds = -1, Nanos = -10 }, + new Duration { Seconds = -1, Nanos = -1 }, + new Duration(), + new Duration { Seconds = 1, Nanos = 1 }, + new Duration { Seconds = 1, Nanos = 10 }, + new Duration { Seconds = 10, Nanos = 1 }, + new Duration { Seconds = 10, Nanos = 10 } + }; + + for (int i = 0; i < durationsInExpectedSortOrder.Length; i++) + { + var target = durationsInExpectedSortOrder[i]; + if (target is null) + { + continue; + } + for (int j = 0; j < durationsInExpectedSortOrder.Length; j++) + { + var expectedResult = Math.Sign(i - j); + var actualResult = target.CompareTo(durationsInExpectedSortOrder[j]); + Assert.AreEqual(expectedResult, actualResult); + } + } + } } } diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs index 9a468fc1988c..cdf3c31c70a1 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/DurationPartial.cs @@ -38,7 +38,7 @@ namespace Google.Protobuf.WellKnownTypes { // Manually-written partial class for the Duration well-known type, // providing a conversion to TimeSpan and convenience operators. - public partial class Duration : ICustomDiagnosticMessage + public partial class Duration : ICustomDiagnosticMessage, IComparable { /// /// The number of nanoseconds in a second. @@ -266,5 +266,26 @@ internal static void AppendNanoseconds(StringBuilder builder, int nanos) } } } + + + /// + /// Given another duration, returns 0 if the durations are equivalent, -1 if this duration is shorter than the other, and 1 otherwise. + /// + /// + /// This method expects that both durations are normalized; that is, that the values of + /// and are within the documented bounds. + /// If either value is not normalized, the results of this method are unspecified. + /// + /// The duration to compare with this object. + /// An integer indicating whether this duration is shorter or longer than . + public int CompareTo(Duration other) + { + return other == null ? 1 + : Seconds < other.Seconds ? -1 + : Seconds > other.Seconds ? 1 + : Nanos < other.Nanos ? -1 + : Nanos > other.Nanos ? 1 + : 0; + } } }