Issue #23517: Fix rounding in fromtimestamp() and utcfromtimestamp() methods
of datetime.datetime: microseconds are now rounded to nearest with ties going
to nearest even integer (ROUND_HALF_EVEN), instead of being rounding towards
zero (ROUND_DOWN). It's important that these methods use the same rounding
mode than datetime.timedelta to keep the property:

   (datetime(1970,1,1) + timedelta(seconds=t)) == datetime.utcfromtimestamp(t)

It also the rounding mode used by round(float) for example.

Add more unit tests on the rounding mode in test_datetime.
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 34e5d38..3af12e7 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1362,49 +1362,42 @@
         return self._tzinfo
 
     @classmethod
+    def _fromtimestamp(cls, t, utc, tz):
+        """Construct a datetime from a POSIX timestamp (like time.time()).
+
+        A timezone info object may be passed in as well.
+        """
+        frac, t = _math.modf(t)
+        us = round(frac * 1e6)
+        if us >= 1000000:
+            t += 1
+            us -= 1000000
+        elif us < 0:
+            t -= 1
+            us += 1000000
+
+        converter = _time.gmtime if utc else _time.localtime
+        y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
+        ss = min(ss, 59)    # clamp out leap seconds if the platform has them
+        return cls(y, m, d, hh, mm, ss, us, tz)
+
+    @classmethod
     def fromtimestamp(cls, t, tz=None):
         """Construct a datetime from a POSIX timestamp (like time.time()).
 
         A timezone info object may be passed in as well.
         """
-
         _check_tzinfo_arg(tz)
 
-        converter = _time.localtime if tz is None else _time.gmtime
-
-        t, frac = divmod(t, 1.0)
-        us = int(frac * 1e6)
-
-        # If timestamp is less than one microsecond smaller than a
-        # full second, us can be rounded up to 1000000.  In this case,
-        # roll over to seconds, otherwise, ValueError is raised
-        # by the constructor.
-        if us == 1000000:
-            t += 1
-            us = 0
-        y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
-        ss = min(ss, 59)    # clamp out leap seconds if the platform has them
-        result = cls(y, m, d, hh, mm, ss, us, tz)
+        result = cls._fromtimestamp(t, tz is not None, tz)
         if tz is not None:
             result = tz.fromutc(result)
         return result
 
     @classmethod
     def utcfromtimestamp(cls, t):
-        "Construct a UTC datetime from a POSIX timestamp (like time.time())."
-        t, frac = divmod(t, 1.0)
-        us = int(frac * 1e6)
-
-        # If timestamp is less than one microsecond smaller than a
-        # full second, us can be rounded up to 1000000.  In this case,
-        # roll over to seconds, otherwise, ValueError is raised
-        # by the constructor.
-        if us == 1000000:
-            t += 1
-            us = 0
-        y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t)
-        ss = min(ss, 59)    # clamp out leap seconds if the platform has them
-        return cls(y, m, d, hh, mm, ss, us)
+        """Construct a naive UTC datetime from a POSIX timestamp."""
+        return cls._fromtimestamp(t, True, None)
 
     # XXX This is supposed to do better than we *can* do by using time.time(),
     # XXX if the platform supports a more accurate way.  The C implementation
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 8e48b9f..a942d4d 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -650,8 +650,16 @@
         # Single-field rounding.
         eq(td(milliseconds=0.4/1000), td(0))    # rounds to 0
         eq(td(milliseconds=-0.4/1000), td(0))    # rounds to 0
+        eq(td(milliseconds=0.5/1000), td(microseconds=0))
+        eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
         eq(td(milliseconds=0.6/1000), td(microseconds=1))
         eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
+        eq(td(milliseconds=1.5/1000), td(microseconds=2))
+        eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
+        eq(td(seconds=0.5/10**6), td(microseconds=0))
+        eq(td(seconds=-0.5/10**6), td(microseconds=-0))
+        eq(td(seconds=1/2**7), td(microseconds=7812))
+        eq(td(seconds=-1/2**7), td(microseconds=-7812))
 
         # Rounding due to contributions from more than one field.
         us_per_hour = 3600e6
@@ -1824,12 +1832,14 @@
                           tzinfo=timezone(timedelta(hours=-5), 'EST'))
         self.assertEqual(t.timestamp(),
                          18000 + 3600 + 2*60 + 3 + 4*1e-6)
+
     def test_microsecond_rounding(self):
         for fts in [self.theclass.fromtimestamp,
                     self.theclass.utcfromtimestamp]:
             zero = fts(0)
             self.assertEqual(zero.second, 0)
             self.assertEqual(zero.microsecond, 0)
+            one = fts(1e-6)
             try:
                 minus_one = fts(-1e-6)
             except OSError:
@@ -1840,22 +1850,28 @@
                 self.assertEqual(minus_one.microsecond, 999999)
 
                 t = fts(-1e-8)
-                self.assertEqual(t, minus_one)
+                self.assertEqual(t, zero)
                 t = fts(-9e-7)
                 self.assertEqual(t, minus_one)
                 t = fts(-1e-7)
-                self.assertEqual(t, minus_one)
+                self.assertEqual(t, zero)
+                t = fts(-1/2**7)
+                self.assertEqual(t.second, 59)
+                self.assertEqual(t.microsecond, 992188)
 
             t = fts(1e-7)
             self.assertEqual(t, zero)
             t = fts(9e-7)
-            self.assertEqual(t, zero)
+            self.assertEqual(t, one)
             t = fts(0.99999949)
             self.assertEqual(t.second, 0)
             self.assertEqual(t.microsecond, 999999)
             t = fts(0.9999999)
+            self.assertEqual(t.second, 1)
+            self.assertEqual(t.microsecond, 0)
+            t = fts(1/2**7)
             self.assertEqual(t.second, 0)
-            self.assertEqual(t.microsecond, 999999)
+            self.assertEqual(t.microsecond, 7812)
 
     def test_insane_fromtimestamp(self):
         # It's possible that some platform maps time_t to double,