[Python-checkins] cpython: Close #14180: Factorize code to convert a number of seconds to time_t, timeval

victor.stinner python-checkins at python.org
Tue Mar 13 13:38:42 CET 2012


http://hg.python.org/cpython/rev/1e9cc1a03365
changeset:   75590:1e9cc1a03365
parent:      75587:8df496684d99
user:        Victor Stinner <vstinner at wyplay.com>
date:        Tue Mar 13 13:35:55 2012 +0100
summary:
  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.

files:
  Doc/library/datetime.rst   |   17 +++-
  Include/pytime.h           |   17 +++-
  Include/timefuncs.h        |   25 ------
  Lib/datetime.py            |    4 +-
  Lib/test/datetimetester.py |   34 ++++++-
  Lib/test/test_time.py      |   57 ++++++++++++-
  Misc/NEWS                  |    9 ++
  Modules/_datetimemodule.c  |   47 +++--------
  Modules/_testcapimodule.c  |   45 +++++++++-
  Modules/_time.c            |   28 ------
  Modules/_time.h            |    3 -
  Modules/posixmodule.c      |   65 ++++-----------
  Modules/selectmodule.c     |   39 +-------
  Modules/timemodule.c       |    7 +-
  Python/pytime.c            |  101 +++++++++++++++++++-----
  setup.py                   |    4 +-
  16 files changed, 279 insertions(+), 223 deletions(-)


diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -395,12 +395,17 @@
 .. classmethod:: date.fromtimestamp(timestamp)
 
    Return the local date corresponding to the POSIX timestamp, such as is returned
-   by :func:`time.time`.  This may raise :exc:`ValueError`, if the timestamp is out
+   by :func:`time.time`.  This may raise :exc:`OverflowError`, if the timestamp is out
    of the range of values supported by the platform C :c:func:`localtime` function.
    It's common for this to be restricted to years from 1970 through 2038.  Note
    that on non-POSIX systems that include leap seconds in their notion of a
    timestamp, leap seconds are ignored by :meth:`fromtimestamp`.
 
+   .. versionchanged:: 3.3
+      Raise :exc:`OverflowError` instead of :exc:`ValueError` if the timestamp
+      is out of the range of values supported by the platform C
+      :c:func:`localtime` function.
+
 
 .. classmethod:: date.fromordinal(ordinal)
 
@@ -712,6 +717,11 @@
    and then it's possible to have two timestamps differing by a second that yield
    identical :class:`.datetime` objects. See also :meth:`utcfromtimestamp`.
 
+   .. versionchanged:: 3.3
+      Raise :exc:`OverflowError` instead of :exc:`ValueError` if the timestamp
+      is out of the range of values supported by the platform C
+      :c:func:`localtime` or :c:func:`gmtime` functions
+
 
 .. classmethod:: datetime.utcfromtimestamp(timestamp)
 
@@ -737,6 +747,11 @@
 
       timestamp = (dt - datetime(1970, 1, 1, tzinfo=timezone.utc)) / timedelta(seconds=1)
 
+   .. versionchanged:: 3.3
+      Raise :exc:`OverflowError` instead of :exc:`ValueError` if the timestamp
+      is out of the range of values supported by the platform C
+      :c:func:`gmtime` function.
+
 
 .. classmethod:: datetime.fromordinal(ordinal)
 
diff --git a/Include/pytime.h b/Include/pytime.h
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -39,9 +39,22 @@
      (tv_end.tv_usec - tv_start.tv_usec) * 0.000001)
 
 #ifndef Py_LIMITED_API
+/* Convert a number of seconds, int or float, to time_t. */
+PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
+    PyObject *obj,
+    time_t *sec);
+
+/* Convert a number of seconds, int or float, to a timeval structure.
+   usec is in the range [0; 999999] and rounded towards zero.
+   For example, -1.2 is converted to (-2, 800000). */
+PyAPI_FUNC(int) _PyTime_ObjectToTimeval(
+    PyObject *obj,
+    time_t *sec,
+    long *usec);
+
 /* Convert a number of seconds, int or float, to a timespec structure.
-   nsec is always in the range [0; 999999999]. For example, -1.2 is converted
-   to (-2, 800000000). */
+   nsec is in the range [0; 999999999] and rounded towards zero.
+   For example, -1.2 is converted to (-2, 800000000). */
 PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
     PyObject *obj,
     time_t *sec,
diff --git a/Include/timefuncs.h b/Include/timefuncs.h
deleted file mode 100644
--- a/Include/timefuncs.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*  timefuncs.h
- */
-
-/* Utility function related to timemodule.c. */
-
-#ifndef TIMEFUNCS_H
-#define TIMEFUNCS_H
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-/* Cast double x to time_t, but raise ValueError if x is too large
- * to fit in a time_t.  ValueError is set on return iff the return
- * value is (time_t)-1 and PyErr_Occurred().
- */
-#ifndef Py_LIMITED_API
-PyAPI_FUNC(time_t) _PyTime_DoubleToTimet(double x);
-#endif
-
-
-#ifdef __cplusplus
-}
-#endif
-#endif  /* TIMEFUNCS_H */
diff --git a/Lib/datetime.py b/Lib/datetime.py
--- 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
--- 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
--- 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)
 
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,15 @@
 Library
 -------
 
+- Issue #14180: 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.
+
+- Issue #14180: datetime.datetime.fromtimestamp() and
+  datetime.datetime.utcfromtimestamp() now round microseconds towards zero
+  instead of rounding to nearest with ties going away from zero.
+
 - Issue #10543: Fix unittest test discovery with Jython bytecode files.
 
 - Issue #1178863: Separate initialisation from setting when initializing
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
--- 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;
-
-    timet = _PyTime_DoubleToTimet(timestamp);
-    if (timet == (time_t)-1 && PyErr_Occurred())
+    long us;
+
+    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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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;
diff --git a/Python/pytime.c b/Python/pytime.c
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -70,9 +70,37 @@
 #endif /* MS_WINDOWS */
 }
 
-int
-_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec)
+static void
+error_time_t_overflow(void)
 {
+    PyErr_SetString(PyExc_OverflowError,
+                    "timestamp out of range for platform time_t");
+}
+
+static time_t
+_PyLong_AsTime_t(PyObject *obj)
+{
+#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
+    PY_LONG_LONG val;
+    val = PyLong_AsLongLong(obj);
+#else
+    long val;
+    assert(sizeof(time_t) <= sizeof(long));
+    val = PyLong_AsLong(obj);
+#endif
+    if (val == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches(PyExc_OverflowError))
+            error_time_t_overflow();
+        return -1;
+    }
+    return (time_t)val;
+}
+
+static int
+_PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
+                            double denominator)
+{
+    assert(denominator <= LONG_MAX);
     if (PyFloat_Check(obj)) {
         double d, intpart, floatpart, err;
 
@@ -85,34 +113,61 @@
 
         *sec = (time_t)intpart;
         err = intpart - (double)*sec;
-        if (err <= -1.0 || err >= 1.0)
-            goto overflow;
+        if (err <= -1.0 || err >= 1.0) {
+            error_time_t_overflow();
+            return -1;
+        }
 
-        floatpart *= 1e9;
-        *nsec = (long)floatpart;
+        floatpart *= denominator;
+        *numerator = (long)floatpart;
         return 0;
     }
     else {
-#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
-        *sec = PyLong_AsLongLong(obj);
-#else
-        assert(sizeof(time_t) <= sizeof(long));
-        *sec = PyLong_AsLong(obj);
-#endif
-        if (*sec == -1 && PyErr_Occurred()) {
-            if (PyErr_ExceptionMatches(PyExc_OverflowError))
-                goto overflow;
-            else
-                return -1;
-        }
-        *nsec = 0;
+        *sec = _PyLong_AsTime_t(obj);
+        if (*sec == (time_t)-1 && PyErr_Occurred())
+            return -1;
+        *numerator = 0;
         return 0;
     }
+}
 
-overflow:
-    PyErr_SetString(PyExc_OverflowError,
-                    "timestamp out of range for platform time_t");
-    return -1;
+int
+_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec)
+{
+    if (PyFloat_Check(obj)) {
+        double d, intpart, err;
+
+        /*whent = _PyTime_DoubleToTimet(d);*/
+
+        d = PyFloat_AsDouble(obj);
+        (void)modf(d, &intpart);
+
+        *sec = (time_t)intpart;
+        err = intpart - (double)*sec;
+        if (err <= -1.0 || err >= 1.0) {
+            error_time_t_overflow();
+            return -1;
+        }
+        return 0;
+    }
+    else {
+        *sec = _PyLong_AsTime_t(obj);
+        if (*sec == (time_t)-1 && PyErr_Occurred())
+            return -1;
+        return 0;
+    }
+}
+
+int
+_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec)
+{
+    return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9);
+}
+
+int
+_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec)
+{
+    return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6);
 }
 
 void
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -512,9 +512,9 @@
             time_libs.append(lib)
 
         # time operations and variables
-        exts.append( Extension('time', ['timemodule.c', '_time.c'],
+        exts.append( Extension('time', ['timemodule.c'],
                                libraries=time_libs) )
-        exts.append( Extension('_datetime', ['_datetimemodule.c', '_time.c']) )
+        exts.append( Extension('_datetime', ['_datetimemodule.c']) )
         # random number generator implemented in C
         exts.append( Extension("_random", ["_randommodule.c"]) )
         # bisect

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list