Repository: nodatime/nodatime (csharp)

Issue:
Consider adding Period.{Max,Min}Value
Similar to other NodaTime types.

The specific trigger here is PostgreSQL introducing `-infinity` and `infinity` to its `interval` type, which corresponds to Period (see https://github.com/npgsql/npgsql/issues/5696#issuecomment-2179805408).

Pull request:
closes #1807

Patch:
diff --git a/src/NodaTime/Period.cs b/src/NodaTime/Period.cs
index fc21b882e..7f763660c 100644
--- a/src/NodaTime/Period.cs
+++ b/src/NodaTime/Period.cs
@@ -69,6 +69,18 @@ public sealed class Period : IEquatable<Period?>
         /// <value>A period containing only zero-valued properties.</value>
         public static Period Zero { get; } = new Period(0, 0, 0, 0);
 
+        /// <summary>
+        /// A period containing the maximum value for all properties.
+        /// </summary>
+        /// <value>A period containing the maximum value for all properties.</value>
+        public static Period MaxValue { get; } = new Period(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, long.MaxValue, long.MaxValue, long.MaxValue, long.MaxValue, long.MaxValue, long.MaxValue);
+        
+        /// <summary>
+        /// A period containing the minimum value for all properties.
+        /// </summary>
+        /// <value>A period containing the minimum value for all properties.</value>
+        public static Period MinValue { get; } = new Period(int.MinValue, int.MinValue, int.MinValue, int.MinValue, long.MinValue, long.MinValue, long.MinValue, long.MinValue, long.MinValue, long.MinValue);
+
         /// <summary>
         /// Returns an equality comparer which compares periods by first normalizing them - so 24 hours is deemed equal to 1 day, and so on.
         /// Note that as per the <see cref="Normalize"/> method, years and months are unchanged by normalization - so 12 months does not


Test patch:
diff --git a/src/NodaTime.Test/PeriodTest.cs b/src/NodaTime.Test/PeriodTest.cs
index fdf7dfeb7..362ffb571 100644
--- a/src/NodaTime.Test/PeriodTest.cs
+++ b/src/NodaTime.Test/PeriodTest.cs
@@ -3,6 +3,7 @@
 // as found in the LICENSE.txt file.
 
 using System;
+using System.Reflection;
 using NodaTime.Calendars;
 using NodaTime.Text;
 using NUnit.Framework;
@@ -387,6 +388,15 @@ public void Addition_WithDifferent_PeriodTypes()
             Assert.AreEqual(20, sum.Minutes);
         }
 
+        [Test]
+        public void Addition_MaxAndMinValue()
+        {
+            Period p1 = Period.MaxValue;
+            Period p2 = Period.MinValue;
+            Period sum = p1 + p2;
+            Assert.AreEqual(new Period(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1), sum);
+        }
+
         [Test]
         public void Addition_With_IdenticalPeriodTypes()
         {
@@ -931,6 +941,37 @@ public void Comparer_NonDurationablePeriods()
             Assert.AreEqual(0, februaryComparer.Compare(month, month));
         }
 
+        private static TestCaseData[] PeriodMaxAndMinValues => 
+        [
+            new TestCaseData(Period.MaxValue, int.MaxValue, long.MaxValue).SetName("All members should be MaxValue"), 
+            new TestCaseData(Period.MinValue, int.MinValue, long.MinValue).SetName("All members should be MinValue")
+        ];
+
+        /// <summary>
+        /// Ensure that Period.MaxValue and Period.MinValue contain the max/min value assignable to each public property.
+        /// </summary>
+        [Test]
+        [TestCaseSource(nameof(PeriodMaxAndMinValues))]
+        public void Period_MaxAndMinValues_AllMembers(Period period, int expectedIntValue, long expectedLongValue)
+        {
+            foreach (var property in typeof(Period).GetProperties(BindingFlags.Instance | BindingFlags.Public))
+            {
+                var actualValue = property.GetValue(period);
+                
+                // HasTimeComponent is a public property that will get included in this loop.
+                // This block allows us to ignore it.
+                if (actualValue is bool)
+                {
+                    return;
+                }
+
+                var expectedValue = actualValue is int ? expectedIntValue
+                    : actualValue is long ? expectedLongValue
+                    : throw new InvalidOperationException($"Property {property.Name} has unexpected type {actualValue?.GetType().Name}.");
+                Assert.AreEqual(expectedValue, actualValue);
+            }
+        }
+
         [Test]
         [TestCaseSource(nameof(AllPeriodUnits))]
         public void Between_ExtremeValues(PeriodUnits units)