From 50b836539c69c17b917224416fa2889c44e53cde Mon Sep 17 00:00:00 2001
From: Jan Tattermusch <jtattermusch@google.com>
Date: Thu, 23 Jul 2015 13:37:46 -0700
Subject: [PATCH] Timespec.FromDateTime implementation and tests

---
 .../Grpc.Core.Tests/Internal/TimespecTest.cs  | 65 ++++++++++++++-----
 src/csharp/Grpc.Core/Internal/Timespec.cs     | 23 +++++--
 2 files changed, 66 insertions(+), 22 deletions(-)

diff --git a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
index 8469a9e3da..e38d48d464 100644
--- a/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
+++ b/src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
@@ -92,18 +92,16 @@ namespace Grpc.Core.Internal.Tests
             // before epoch
             Assert.AreEqual(new DateTime(1969, 12, 31, 23, 59, 55, DateTimeKind.Utc).AddTicks(10),
                 new Timespec(new IntPtr(-5), 1000).ToDateTime());
-        }
 
-        [Test]
-        public void ToDateTime_RoundUp()
-        {
+            // infinity
+            Assert.AreEqual(DateTime.MaxValue, Timespec.InfFuture.ToDateTime());
+            Assert.AreEqual(DateTime.MinValue, Timespec.InfPast.ToDateTime());
+
+            // nanos are rounded to ticks are rounded up
             Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddTicks(1),
                 new Timespec(IntPtr.Zero, 99).ToDateTime());
-        }
 
-        [Test]
-        public void ToDateTime_WrongInputs()
-        {
+            // Illegal inputs
             Assert.Throws(typeof(InvalidOperationException),
                 () => new Timespec(new IntPtr(0), -2).ToDateTime());
             Assert.Throws(typeof(InvalidOperationException),
@@ -120,14 +118,7 @@ namespace Grpc.Core.Internal.Tests
         }
 
         [Test]
-        public void ToDateTime_Infinity()
-        {
-            Assert.AreEqual(DateTime.MaxValue, Timespec.InfFuture.ToDateTime());
-            Assert.AreEqual(DateTime.MinValue, Timespec.InfPast.ToDateTime());
-        }
-
-        [Test]
-        public void ToDateTime_OverflowGivesMaxOrMinVal()
+        public void ToDateTime_Overflow()
         {
             // we can only get overflow in ticks arithmetic on 64-bit
             if (IntPtr.Size == 8)
@@ -145,7 +136,7 @@ namespace Grpc.Core.Internal.Tests
         }
 
         [Test]
-        public void ToDateTime_OutOfRangeGivesMaxOrMinVal()
+        public void ToDateTime_OutOfDateTimeRange()
         {
             // we can only get out of range on 64-bit, on 32 bit the max 
             // timestamp is ~ Jan 19 2038, which is far within range of DateTime
@@ -167,5 +158,45 @@ namespace Grpc.Core.Internal.Tests
                 Console.WriteLine("Test cannot be run on this platform, skipping the test");
             }
         }
+
+        [Test]
+        public void FromDateTime()
+        {
+            Assert.AreEqual(new Timespec(IntPtr.Zero, 0),
+                Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
+
+            Assert.AreEqual(new Timespec(new IntPtr(10), 5000),
+                Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 10, DateTimeKind.Utc).AddTicks(50)));
+
+            Assert.AreEqual(new Timespec(new IntPtr(1437452508), 0),
+                Timespec.FromDateTime(new DateTime(2015, 7, 21, 4, 21, 48, DateTimeKind.Utc)));
+
+            // before epoch
+            Assert.AreEqual(new Timespec(new IntPtr(-5), 1000),
+                Timespec.FromDateTime(new DateTime(1969, 12, 31, 23, 59, 55, DateTimeKind.Utc).AddTicks(10)));
+
+            // infinity
+            Assert.AreEqual(Timespec.InfFuture, Timespec.FromDateTime(DateTime.MaxValue));
+            Assert.AreEqual(Timespec.InfPast, Timespec.FromDateTime(DateTime.MinValue));
+
+            // illegal inputs
+            Assert.Throws(typeof(ArgumentException),
+                () => Timespec.FromDateTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)));
+        }
+
+        [Test]
+        public void FromDateTime_OutOfTimespecRange()
+        {
+            // we can only get overflow in Timespec on 32-bit
+            if (IntPtr.Size == 4)
+            {
+                Assert.AreEqual(Timespec.InfFuture, new DateTime(2040, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+                Assert.AreEqual(Timespec.InfPast, new DateTime(1800, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+            }
+            else
+            {
+                Console.WriteLine("Test cannot be run on this platform, skipping the test.");
+            }
+        }
     }
 }
diff --git a/src/csharp/Grpc.Core/Internal/Timespec.cs b/src/csharp/Grpc.Core/Internal/Timespec.cs
index 887eae5dd7..0e58e2048d 100644
--- a/src/csharp/Grpc.Core/Internal/Timespec.cs
+++ b/src/csharp/Grpc.Core/Internal/Timespec.cs
@@ -180,6 +180,14 @@ namespace Grpc.Core.Internal
             }
         }
             
+        /// <summary>
+        /// Creates DateTime to Timespec.
+        /// DateTime has to be in UTC (DateTimeKind.Utc) unless it's DateTime.MaxValue or DateTime.MinValue.
+        /// For DateTime.MaxValue of date time after the largest representable Timespec, Timespec.InfFuture is returned.
+        /// For DateTime.MinValue of date time before the lowest representable Timespec, Timespec.InfPast is returned.
+        /// </summary>
+        /// <returns>The date time.</returns>
+        /// <param name="dateTime">Date time.</param>
         public static Timespec FromDateTime(DateTime dateTime)
         {
             if (dateTime == DateTime.MaxValue)
@@ -199,11 +207,16 @@ namespace Grpc.Core.Internal
                 TimeSpan timeSpan = dateTime - UnixEpoch;
                 long ticks = timeSpan.Ticks;
 
-                IntPtr seconds = new IntPtr(ticks / TicksPerSecond);  // possible OverflowException
-                // (x % m + m) % m is workaround for modulo semantics with negative numbers.
-                int nanos = (int)(((ticks % TicksPerSecond + TicksPerSecond) % TicksPerSecond) * NanosPerTick);
-
-                return new Timespec(seconds, nanos);
+                long seconds = ticks / TicksPerSecond;  
+                int nanos = (int)((ticks % TicksPerSecond) * NanosPerTick);
+                if (nanos < 0) 
+                {
+                    // correct the result based on C# modulo semantics for negative dividend
+                    seconds--;
+                    nanos += (int)NanosPerSecond;
+                }
+                // new IntPtr possibly throws OverflowException
+                return new Timespec(new IntPtr(seconds), nanos);
             }
             catch (OverflowException)
             {
-- 
GitLab