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/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 3b0b362..669170a 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -7,8 +7,6 @@
 
 #include <time.h>
 
-#include "_time.h"
-
 /* Differentiate between building the core module and building extension
  * modules.
  */
@@ -2441,15 +2439,15 @@
 
 /* Return new date from localtime(t). */
 static PyObject *
-date_local_from_time_t(PyObject *cls, double ts)
+date_local_from_object(PyObject *cls, PyObject *obj)
 {
     struct tm *tm;
     time_t t;
     PyObject *result = NULL;
 
-    t = _PyTime_DoubleToTimet(ts);
-    if (t == (time_t)-1 && PyErr_Occurred())
+    if (_PyTime_ObjectToTime_t(obj, &t) == -1)
         return NULL;
+
     tm = localtime(&t);
     if (tm)
         result = PyObject_CallFunction(cls, "iii",
@@ -2494,11 +2492,11 @@
 static PyObject *
 date_fromtimestamp(PyObject *cls, PyObject *args)
 {
-    double timestamp;
+    PyObject *timestamp;
     PyObject *result = NULL;
 
-    if (PyArg_ParseTuple(args, "d:fromtimestamp", &timestamp))
-        result = date_local_from_time_t(cls, timestamp);
+    if (PyArg_ParseTuple(args, "O:fromtimestamp", &timestamp))
+        result = date_local_from_object(cls, timestamp);
     return result;
 }
 
@@ -4096,31 +4094,14 @@
  * to get that much precision (e.g., C time() isn't good enough).
  */
 static PyObject *
-datetime_from_timestamp(PyObject *cls, TM_FUNC f, double timestamp,
+datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
                         PyObject *tzinfo)
 {
     time_t timet;
-    double fraction;
-    int us;
+    long us;
 
-    timet = _PyTime_DoubleToTimet(timestamp);
-    if (timet == (time_t)-1 && PyErr_Occurred())
+    if (_PyTime_ObjectToTimeval(timestamp, &timet, &us) == -1)
         return NULL;
-    fraction = timestamp - (double)timet;
-    us = (int)round_to_long(fraction * 1e6);
-    if (us < 0) {
-        /* Truncation towards zero is not what we wanted
-           for negative numbers (Python's mod semantics) */
-        timet -= 1;
-        us += 1000000;
-    }
-    /* If timestamp is less than one microsecond smaller than a
-     * full second, round up. Otherwise, ValueErrors are raised
-     * for some floats. */
-    if (us == 1000000) {
-        timet += 1;
-        us = 0;
-    }
     return datetime_from_timet_and_us(cls, f, timet, us, tzinfo);
 }
 
@@ -4181,11 +4162,11 @@
 datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw)
 {
     PyObject *self;
-    double timestamp;
+    PyObject *timestamp;
     PyObject *tzinfo = Py_None;
     static char *keywords[] = {"timestamp", "tz", NULL};
 
-    if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp",
+    if (! PyArg_ParseTupleAndKeywords(args, kw, "O|O:fromtimestamp",
                                       keywords, &timestamp, &tzinfo))
         return NULL;
     if (check_tzinfo_subclass(tzinfo) < 0)
@@ -4210,10 +4191,10 @@
 static PyObject *
 datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
 {
-    double timestamp;
+    PyObject *timestamp;
     PyObject *result = NULL;
 
-    if (PyArg_ParseTuple(args, "d:utcfromtimestamp", &timestamp))
+    if (PyArg_ParseTuple(args, "O:utcfromtimestamp", &timestamp))
         result = datetime_from_timestamp(cls, gmtime, timestamp,
                                          Py_None);
     return result;
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 9294df3..9cafa73 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2323,6 +2323,42 @@
     return PyLong_FromLong(r);
 }
 
+static PyObject*
+_PyLong_FromTime_t(time_t value)
+{
+#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
+    return PyLong_FromLongLong(value);
+#else
+    assert(sizeof(time_t) <= sizeof(long));
+    return PyLong_FromLong(value);
+#endif
+}
+
+static PyObject *
+test_pytime_object_to_time_t(PyObject *self, PyObject *args)
+{
+    PyObject *obj;
+    time_t sec;
+    if (!PyArg_ParseTuple(args, "O:pytime_object_to_time_t", &obj))
+        return NULL;
+    if (_PyTime_ObjectToTime_t(obj, &sec) == -1)
+        return NULL;
+    return _PyLong_FromTime_t(sec);
+}
+
+static PyObject *
+test_pytime_object_to_timeval(PyObject *self, PyObject *args)
+{
+    PyObject *obj;
+    time_t sec;
+    long usec;
+    if (!PyArg_ParseTuple(args, "O:pytime_object_to_timeval", &obj))
+        return NULL;
+    if (_PyTime_ObjectToTimeval(obj, &sec, &usec) == -1)
+        return NULL;
+    return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec);
+}
+
 static PyObject *
 test_pytime_object_to_timespec(PyObject *self, PyObject *args)
 {
@@ -2333,12 +2369,7 @@
         return NULL;
     if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1)
         return NULL;
-#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
-    return Py_BuildValue("Ll", (PY_LONG_LONG)sec, nsec);
-#else
-    assert(sizeof(time_t) <= sizeof(long));
-    return Py_BuildValue("ll", (long)sec, nsec);
-#endif
+    return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec);
 }
 
 
@@ -2430,6 +2461,8 @@
      METH_NOARGS},
     {"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS},
     {"run_in_subinterp",        run_in_subinterp,                METH_VARARGS},
+    {"pytime_object_to_time_t", test_pytime_object_to_time_t,  METH_VARARGS},
+    {"pytime_object_to_timeval", test_pytime_object_to_timeval,  METH_VARARGS},
     {"pytime_object_to_timespec", test_pytime_object_to_timespec,  METH_VARARGS},
     {NULL, NULL} /* sentinel */
 };
diff --git a/Modules/_time.c b/Modules/_time.c
deleted file mode 100644
index 10cc8e1..0000000
--- a/Modules/_time.c
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "Python.h"
-#include "_time.h"
-
-/* Exposed in timefuncs.h. */
-time_t
-_PyTime_DoubleToTimet(double x)
-{
-    time_t result;
-    double diff;
-
-    result = (time_t)x;
-    /* How much info did we lose?  time_t may be an integral or
-     * floating type, and we don't know which.  If it's integral,
-     * we don't know whether C truncates, rounds, returns the floor,
-     * etc.  If we lost a second or more, the C rounding is
-     * unreasonable, or the input just doesn't fit in a time_t;
-     * call it an error regardless.  Note that the original cast to
-     * time_t can cause a C error too, but nothing we can do to
-     * work around that.
-     */
-    diff = x - (double)result;
-    if (diff <= -1.0 || diff >= 1.0) {
-        PyErr_SetString(PyExc_ValueError,
-                        "timestamp out of range for platform time_t");
-        result = (time_t)-1;
-    }
-    return result;
-}
diff --git a/Modules/_time.h b/Modules/_time.h
deleted file mode 100644
index 816593b..0000000
--- a/Modules/_time.h
+++ /dev/null
@@ -1,3 +0,0 @@
-/* XXX: It is probably best to move timefuncs.h content in here, and
-   remove it but user code may rely on it. */
-#include "timefuncs.h"
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 628b0b9..c7d48b0 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3540,31 +3540,6 @@
 #endif /* HAVE_UNAME */
 
 
-static int
-extract_time(PyObject *t, time_t* sec, long* nsec)
-{
-    time_t intval;
-    if (PyFloat_Check(t)) {
-        double d = PyFloat_AsDouble(t);
-        double mod;
-        *sec = (time_t)d;
-        mod = fmod(d, 1.0);
-        mod *= 1e9;
-        *nsec = (long)mod;
-        return 0;
-    }
-#if SIZEOF_TIME_T > SIZEOF_LONG
-    intval = PyLong_AsUnsignedLongLongMask(t);
-#else
-    intval = PyLong_AsLong(t);
-#endif
-    if (intval == -1 && PyErr_Occurred())
-        return -1;
-    *sec = intval;
-    *nsec = 0;
-    return 0;
-}
-
 PyDoc_STRVAR(posix_utime__doc__,
 "utime(path[, (atime, mtime)])\n\
 Set the access and modified time of the file to the given values.\n\
@@ -3633,12 +3608,12 @@
         goto done;
     }
     else {
-        if (extract_time(PyTuple_GET_ITEM(arg, 0),
-                         &atimesec, &ansec) == -1)
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
+                                     &atimesec, &ansec) == -1)
             goto done;
         time_t_to_FILE_TIME(atimesec, ansec, &atime);
-        if (extract_time(PyTuple_GET_ITEM(arg, 1),
-                         &mtimesec, &mnsec) == -1)
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
+                                     &mtimesec, &mnsec) == -1)
             goto done;
         time_t_to_FILE_TIME(mtimesec, mnsec, &mtime);
     }
@@ -3681,13 +3656,13 @@
         return NULL;
     }
     else {
-        if (extract_time(PyTuple_GET_ITEM(arg, 0),
-                         &atime, &ansec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
+                                     &atime, &ansec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
-        if (extract_time(PyTuple_GET_ITEM(arg, 1),
-                         &mtime, &mnsec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
+                                     &mtime, &mnsec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
@@ -3763,12 +3738,12 @@
         return NULL;
     }
     else {
-        if (extract_time(PyTuple_GET_ITEM(arg, 0),
-                &atime, &ansec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
+                                     &atime, &ansec) == -1) {
             return NULL;
         }
-        if (extract_time(PyTuple_GET_ITEM(arg, 1),
-                &mtime, &mnsec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
+                                     &mtime, &mnsec) == -1) {
             return NULL;
         }
         Py_BEGIN_ALLOW_THREADS
@@ -3829,13 +3804,13 @@
         return NULL;
     }
     else {
-        if (extract_time(PyTuple_GET_ITEM(arg, 0),
-                &atime, &ansec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
+                                     &atime, &ansec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
-        if (extract_time(PyTuple_GET_ITEM(arg, 1),
-                &mtime, &mnsec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
+                                     &mtime, &mnsec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
@@ -9610,13 +9585,13 @@
         return NULL;
     }
     else {
-        if (extract_time(PyTuple_GET_ITEM(arg, 0),
-                         &atime, &ansec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
+                                     &atime, &ansec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
-        if (extract_time(PyTuple_GET_ITEM(arg, 1),
-                         &mtime, &mnsec) == -1) {
+        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
+                                     &mtime, &mnsec) == -1) {
             Py_DECREF(opath);
             return NULL;
         }
diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c
index 945055f..4d99250 100644
--- a/Modules/selectmodule.c
+++ b/Modules/selectmodule.c
@@ -206,9 +206,7 @@
     PyObject *ret = NULL;
     PyObject *tout = Py_None;
     fd_set ifdset, ofdset, efdset;
-    double timeout;
     struct timeval tv, *tvp;
-    long seconds;
     int imax, omax, emax, max;
     int n;
 
@@ -225,23 +223,12 @@
         return NULL;
     }
     else {
-        timeout = PyFloat_AsDouble(tout);
-        if (timeout == -1 && PyErr_Occurred())
+        if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv.tv_usec) == -1)
             return NULL;
-        if (timeout > (double)LONG_MAX) {
-            PyErr_SetString(PyExc_OverflowError,
-                            "timeout period too long");
+        if (tv.tv_sec < 0) {
+            PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
             return NULL;
         }
-        if (timeout < 0) {
-            PyErr_SetString(PyExc_ValueError,
-                        "timeout must be non-negative");
-            return NULL;
-        }
-        seconds = (long)timeout;
-        timeout = timeout - (double)seconds;
-        tv.tv_sec = seconds;
-        tv.tv_usec = (long)(timeout * 1E6);
         tvp = &tv;
     }
 
@@ -1870,27 +1857,15 @@
         ptimeoutspec = NULL;
     }
     else if (PyNumber_Check(otimeout)) {
-        double timeout;
-        long seconds;
+        if (_PyTime_ObjectToTimespec(otimeout,
+                                     &timeout.tv_sec, &timeout.tv_nsec) == -1)
+            return NULL;
 
-        timeout = PyFloat_AsDouble(otimeout);
-        if (timeout == -1 && PyErr_Occurred())
-            return NULL;
-        if (timeout > (double)LONG_MAX) {
-            PyErr_SetString(PyExc_OverflowError,
-                            "timeout period too long");
-            return NULL;
-        }
-        if (timeout < 0) {
+        if (timeout.tv_sec < 0) {
             PyErr_SetString(PyExc_ValueError,
                             "timeout must be positive or None");
             return NULL;
         }
-
-        seconds = (long)timeout;
-        timeout = timeout - (double)seconds;
-        timeoutspec.tv_sec = seconds;
-        timeoutspec.tv_nsec = (long)(timeout * 1E9);
         ptimeoutspec = &timeoutspec;
     }
     else {
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 6ebd3ef..efebd49 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1,7 +1,6 @@
 /* Time module */
 
 #include "Python.h"
-#include "_time.h"
 
 #include <ctype.h>
 
@@ -288,11 +287,7 @@
         whent = time(NULL);
     }
     else {
-        double d = PyFloat_AsDouble(ot);
-        if (PyErr_Occurred())
-            return 0;
-        whent = _PyTime_DoubleToTimet(d);
-        if (whent == (time_t)-1 && PyErr_Occurred())
+        if (_PyTime_ObjectToTime_t(ot, &whent) == -1)
             return 0;
     }
     *pwhen = whent;