Close #14180: Factorize code to convert a number of seconds to time_t, timeval or timespec

time.ctime(), gmtime(), time.localtime(), datetime.date.fromtimestamp(),
datetime.datetime.fromtimestamp() and datetime.datetime.utcfromtimestamp() now
raises an OverflowError, instead of a ValueError, if the timestamp does not fit
in time_t.

datetime.datetime.fromtimestamp() and datetime.datetime.utcfromtimestamp() now
round microseconds towards zero instead of rounding to nearest with ties going
away from zero.
diff --git a/Lib/datetime.py b/Lib/datetime.py
index c5eeca4..59f3c68 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1360,7 +1360,7 @@
         converter = _time.localtime if tz is None else _time.gmtime
 
         t, frac = divmod(t, 1.0)
-        us = round(frac * 1e6)
+        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,
@@ -1380,7 +1380,7 @@
     def utcfromtimestamp(cls, t):
         "Construct a UTC datetime from a POSIX timestamp (like time.time())."
         t, frac = divmod(t, 1.0)
-        us = round(frac * 1e6)
+        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,
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 5c02b3a..6de29c5 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -977,7 +977,7 @@
         # exempt such platforms (provided they return reasonable
         # results!).
         for insane in -1e200, 1e200:
-            self.assertRaises(ValueError, self.theclass.fromtimestamp,
+            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
                               insane)
 
     def test_today(self):
@@ -1736,12 +1736,32 @@
         self.verify_field_equality(expected, got)
 
     def test_microsecond_rounding(self):
-        # Test whether fromtimestamp "rounds up" floats that are less
-        # than 1/2 microsecond smaller than an integer.
         for fts in [self.theclass.fromtimestamp,
                     self.theclass.utcfromtimestamp]:
-            self.assertEqual(fts(0.9999999), fts(1))
-            self.assertEqual(fts(0.99999949).microsecond, 999999)
+            zero = fts(0)
+            self.assertEqual(zero.second, 0)
+            self.assertEqual(zero.microsecond, 0)
+            minus_one = fts(-1e-6)
+            self.assertEqual(minus_one.second, 59)
+            self.assertEqual(minus_one.microsecond, 999999)
+
+            t = fts(-1e-8)
+            self.assertEqual(t, minus_one)
+            t = fts(-9e-7)
+            self.assertEqual(t, minus_one)
+            t = fts(-1e-7)
+            self.assertEqual(t, minus_one)
+
+            t = fts(1e-7)
+            self.assertEqual(t, zero)
+            t = fts(9e-7)
+            self.assertEqual(t, zero)
+            t = fts(0.99999949)
+            self.assertEqual(t.second, 0)
+            self.assertEqual(t.microsecond, 999999)
+            t = fts(0.9999999)
+            self.assertEqual(t.second, 0)
+            self.assertEqual(t.microsecond, 999999)
 
     def test_insane_fromtimestamp(self):
         # It's possible that some platform maps time_t to double,
@@ -1749,7 +1769,7 @@
         # exempt such platforms (provided they return reasonable
         # results!).
         for insane in -1e200, 1e200:
-            self.assertRaises(ValueError, self.theclass.fromtimestamp,
+            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
                               insane)
 
     def test_insane_utcfromtimestamp(self):
@@ -1758,7 +1778,7 @@
         # exempt such platforms (provided they return reasonable
         # results!).
         for insane in -1e200, 1e200:
-            self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
+            self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
                               insane)
     @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
     def test_negative_float_fromtimestamp(self):
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index 26492c1..1fc46aa 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -281,7 +281,7 @@
         # results!).
         for func in time.ctime, time.gmtime, time.localtime:
             for unreasonable in -1e200, 1e200:
-                self.assertRaises(ValueError, func, unreasonable)
+                self.assertRaises(OverflowError, func, unreasonable)
 
     def test_ctime_without_arg(self):
         # Not sure how to check the values, since the clock could tick
@@ -365,11 +365,8 @@
         for time_t in (-1, 2**30, 2**33, 2**60):
             try:
                 time.localtime(time_t)
-            except ValueError as err:
-                if str(err) == "timestamp out of range for platform time_t":
-                    self.skipTest("need 64-bit time_t")
-                else:
-                    raise
+            except OverflowError:
+                self.skipTest("need 64-bit time_t")
             except OSError:
                 invalid_time_t = time_t
                 break
@@ -498,19 +495,63 @@
 
 
 class TestPytime(unittest.TestCase):
+    def setUp(self):
+        self.invalid_values = (
+            -(2 ** 100), 2 ** 100,
+            -(2.0 ** 100.0), 2.0 ** 100.0,
+        )
+
+    def test_time_t(self):
+        from _testcapi import pytime_object_to_time_t
+        for obj, time_t in (
+            (0, 0),
+            (-1, -1),
+            (-1.0, -1),
+            (-1.9, -1),
+            (1.0, 1),
+            (1.9, 1),
+        ):
+            self.assertEqual(pytime_object_to_time_t(obj), time_t)
+
+        for invalid in self.invalid_values:
+            self.assertRaises(OverflowError, pytime_object_to_time_t, invalid)
+
+    def test_timeval(self):
+        from _testcapi import pytime_object_to_timeval
+        for obj, timeval in (
+            (0, (0, 0)),
+            (-1, (-1, 0)),
+            (-1.0, (-1, 0)),
+            (1e-6, (0, 1)),
+            (-1e-6, (-1, 999999)),
+            (-1.2, (-2, 800000)),
+            (1.1234560, (1, 123456)),
+            (1.1234569, (1, 123456)),
+            (-1.1234560, (-2, 876544)),
+            (-1.1234561, (-2, 876543)),
+        ):
+            self.assertEqual(pytime_object_to_timeval(obj), timeval)
+
+        for invalid in self.invalid_values:
+            self.assertRaises(OverflowError, pytime_object_to_timeval, invalid)
+
     def test_timespec(self):
         from _testcapi import pytime_object_to_timespec
         for obj, timespec in (
             (0, (0, 0)),
             (-1, (-1, 0)),
             (-1.0, (-1, 0)),
+            (1e-9, (0, 1)),
             (-1e-9, (-1, 999999999)),
             (-1.2, (-2, 800000000)),
-            (1.123456789, (1, 123456789)),
+            (1.1234567890, (1, 123456789)),
+            (1.1234567899, (1, 123456789)),
+            (-1.1234567890, (-2, 876543211)),
+            (-1.1234567891, (-2, 876543210)),
         ):
             self.assertEqual(pytime_object_to_timespec(obj), timespec)
 
-        for invalid in (-(2 ** 100), -(2.0 ** 100.0), 2 ** 100, 2.0 ** 100.0):
+        for invalid in self.invalid_values:
             self.assertRaises(OverflowError, pytime_object_to_timespec, invalid)