[Python-checkins] cpython: Issue #22117: Add the new _PyTime_ROUND_FLOOR rounding method for the datetime

victor.stinner python-checkins at python.org
Sat Mar 28 05:20:05 CET 2015


https://hg.python.org/cpython/rev/47123ac83733
changeset:   95239:47123ac83733
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Sat Mar 28 05:02:39 2015 +0100
summary:
  Issue #22117: Add the new _PyTime_ROUND_FLOOR rounding method for the datetime
module. time.clock_settime() now uses this rounding method instead of
_PyTime_ROUND_DOWN to handle correctly dates before 1970.

files:
  Include/pytime.h          |  11 +++-
  Lib/test/test_time.py     |  66 +++++++-------------------
  Modules/_testcapimodule.c |   3 +-
  Modules/timemodule.c      |   4 +-
  Python/pytime.c           |  15 ++++-
  5 files changed, 43 insertions(+), 56 deletions(-)


diff --git a/Include/pytime.h b/Include/pytime.h
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -38,8 +38,12 @@
 typedef enum {
     /* Round towards zero. */
     _PyTime_ROUND_DOWN=0,
-    /* Round away from zero. */
-    _PyTime_ROUND_UP
+    /* Round away from zero.
+       For example, used for timeout to wait "at least" N seconds. */
+    _PyTime_ROUND_UP,
+    /* Round towards minus infinity (-inf).
+       For example, used to read a clock. */
+    _PyTime_ROUND_FLOOR
 } _PyTime_round_t;
 
 /* Convert a number of seconds, int or float, to time_t. */
@@ -81,6 +85,9 @@
 /****************** NEW _PyTime_t API **********************/
 
 #ifdef PY_INT64_T
+/* _PyTime_t: Python timestamp with subsecond precision. It can be used to
+   store a duration, and so indirectly a date (related to another date, like
+   UNIX epoch). */
 typedef PY_INT64_T _PyTime_t;
 #define _PyTime_MIN PY_LLONG_MIN
 #define _PyTime_MAX PY_LLONG_MAX
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
@@ -28,8 +28,13 @@
     ROUND_DOWN = 0
     # Round away from zero
     ROUND_UP = 1
+    # Round towards -Infinity
+    ROUND_FLOOR = 2
 
-ALL_ROUNDING_METHODS = (_PyTime.ROUND_UP, _PyTime.ROUND_DOWN)
+ALL_ROUNDING_METHODS = (
+    _PyTime.ROUND_UP,
+    _PyTime.ROUND_DOWN,
+    _PyTime.ROUND_FLOOR)
 
 
 class TimeTestCase(unittest.TestCase):
@@ -632,53 +637,6 @@
                               pytime_object_to_time_t, invalid, rnd)
 
     @support.cpython_only
-    def test_timeval(self):
-        from _testcapi import pytime_object_to_timeval
-        for obj, timeval, rnd in (
-            # Round towards zero
-            (0, (0, 0), _PyTime.ROUND_DOWN),
-            (-1, (-1, 0), _PyTime.ROUND_DOWN),
-            (-1.0, (-1, 0), _PyTime.ROUND_DOWN),
-            (1e-6, (0, 1), _PyTime.ROUND_DOWN),
-            (1e-7, (0, 0), _PyTime.ROUND_DOWN),
-            (-1e-6, (-1, 999999), _PyTime.ROUND_DOWN),
-            (-1e-7, (-1, 999999), _PyTime.ROUND_DOWN),
-            (-1.2, (-2, 800000), _PyTime.ROUND_DOWN),
-            (0.9999999, (0, 999999), _PyTime.ROUND_DOWN),
-            (0.0000041, (0, 4), _PyTime.ROUND_DOWN),
-            (1.1234560, (1, 123456), _PyTime.ROUND_DOWN),
-            (1.1234569, (1, 123456), _PyTime.ROUND_DOWN),
-            (-0.0000040, (-1, 999996), _PyTime.ROUND_DOWN),
-            (-0.0000041, (-1, 999995), _PyTime.ROUND_DOWN),
-            (-1.1234560, (-2, 876544), _PyTime.ROUND_DOWN),
-            (-1.1234561, (-2, 876543), _PyTime.ROUND_DOWN),
-            # Round away from zero
-            (0, (0, 0), _PyTime.ROUND_UP),
-            (-1, (-1, 0), _PyTime.ROUND_UP),
-            (-1.0, (-1, 0), _PyTime.ROUND_UP),
-            (1e-6, (0, 1), _PyTime.ROUND_UP),
-            (1e-7, (0, 1), _PyTime.ROUND_UP),
-            (-1e-6, (-1, 999999), _PyTime.ROUND_UP),
-            (-1e-7, (-1, 999999), _PyTime.ROUND_UP),
-            (-1.2, (-2, 800000), _PyTime.ROUND_UP),
-            (0.9999999, (1, 0), _PyTime.ROUND_UP),
-            (0.0000041, (0, 5), _PyTime.ROUND_UP),
-            (1.1234560, (1, 123457), _PyTime.ROUND_UP),
-            (1.1234569, (1, 123457), _PyTime.ROUND_UP),
-            (-0.0000040, (-1, 999996), _PyTime.ROUND_UP),
-            (-0.0000041, (-1, 999995), _PyTime.ROUND_UP),
-            (-1.1234560, (-2, 876544), _PyTime.ROUND_UP),
-            (-1.1234561, (-2, 876543), _PyTime.ROUND_UP),
-        ):
-            with self.subTest(obj=obj, round=rnd, timeval=timeval):
-                self.assertEqual(pytime_object_to_timeval(obj, rnd), timeval)
-
-        rnd = _PyTime.ROUND_DOWN
-        for invalid in self.invalid_values:
-            self.assertRaises(OverflowError,
-                              pytime_object_to_timeval, invalid, rnd)
-
-    @support.cpython_only
     def test_timespec(self):
         from _testcapi import pytime_object_to_timespec
         for obj, timespec, rnd in (
@@ -835,24 +793,31 @@
         # Conversion giving different results depending on the rounding method
         UP = _PyTime.ROUND_UP
         DOWN = _PyTime.ROUND_DOWN
+        FLOOR = _PyTime.ROUND_FLOOR
         for obj, ts, rnd in (
             # close to zero
             ( 1e-10,  1, UP),
             ( 1e-10,  0, DOWN),
+            ( 1e-10,  0, FLOOR),
             (-1e-10,  0, DOWN),
             (-1e-10, -1, UP),
+            (-1e-10, -1, FLOOR),
 
             # test rounding of the last nanosecond
             ( 1.1234567899,  1123456790, UP),
             ( 1.1234567899,  1123456789, DOWN),
+            ( 1.1234567899,  1123456789, FLOOR),
             (-1.1234567899, -1123456789, DOWN),
             (-1.1234567899, -1123456790, UP),
+            (-1.1234567899, -1123456790, FLOOR),
 
             # close to 1 second
             ( 0.9999999999,  1000000000, UP),
             ( 0.9999999999,   999999999, DOWN),
+            ( 0.9999999999,   999999999, FLOOR),
             (-0.9999999999,  -999999999, DOWN),
             (-0.9999999999, -1000000000, UP),
+            (-0.9999999999, -1000000000, FLOOR),
         ):
             with self.subTest(obj=obj, round=rnd, timestamp=ts):
                 self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
@@ -924,18 +889,23 @@
 
         UP = _PyTime.ROUND_UP
         DOWN = _PyTime.ROUND_DOWN
+        FLOOR = _PyTime.ROUND_FLOOR
         for ns, tv, rnd in (
             # nanoseconds
             (1, (0, 1), UP),
             (1, (0, 0), DOWN),
+            (1, (0, 0), FLOOR),
             (-1, (0, 0), DOWN),
             (-1, (-1, 999999), UP),
+            (-1, (-1, 999999), FLOOR),
 
             # seconds + nanoseconds
             (1234567001, (1, 234568), UP),
             (1234567001, (1, 234567), DOWN),
+            (1234567001, (1, 234567), FLOOR),
             (-1234567001, (-2, 765433), DOWN),
             (-1234567001, (-2, 765432), UP),
+            (-1234567001, (-2, 765432), FLOOR),
         ):
             with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
                 self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2634,7 +2634,8 @@
 static int
 check_time_rounding(int round)
 {
-    if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP) {
+    if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP
+        && round != _PyTime_ROUND_FLOOR) {
         PyErr_SetString(PyExc_ValueError, "invalid rounding");
         return -1;
     }
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -173,7 +173,7 @@
     if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj))
         return NULL;
 
-    if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_DOWN) < 0)
+    if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_FLOOR) < 0)
         return NULL;
 
     if (_PyTime_AsTimespec(t, &tp) == -1)
@@ -322,7 +322,7 @@
         whent = time(NULL);
     }
     else {
-        if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1)
+        if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_FLOOR) == -1)
             return 0;
     }
     *pwhen = whent;
diff --git a/Python/pytime.c b/Python/pytime.c
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -260,6 +260,14 @@
                     "timestamp too large to convert to C _PyTime_t");
 }
 
+int
+_PyTime_RoundTowardsInfinity(int is_neg, _PyTime_round_t round)
+{
+    if (round == _PyTime_ROUND_FLOOR)
+        return 0;
+    return ((round == _PyTime_ROUND_UP) ^ is_neg);
+}
+
 _PyTime_t
 _PyTime_FromNanoseconds(PY_LONG_LONG ns)
 {
@@ -314,7 +322,7 @@
         d = PyFloat_AsDouble(obj);
         d *= 1e9;
 
-        if ((round == _PyTime_ROUND_UP) ^ (d < 0))
+        if (_PyTime_RoundTowardsInfinity(d < 0, round))
             d = ceil(d);
         else
             d = floor(d);
@@ -380,7 +388,7 @@
     _PyTime_t k;
     if (multiply < SEC_TO_NS) {
         k = SEC_TO_NS / multiply;
-        if (round == _PyTime_ROUND_UP)
+        if (_PyTime_RoundTowardsInfinity(t < 0, round))
             return (t + k - 1) / k;
         else
             return t / k;
@@ -397,6 +405,7 @@
     return _PyTime_Multiply(t, 1000, round);
 }
 
+/* FIXME: write unit tests */
 _PyTime_t
 _PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round)
 {
@@ -439,7 +448,7 @@
         res = -1;
 #endif
 
-    if ((round == _PyTime_ROUND_UP) ^ (tv->tv_sec < 0))
+    if (_PyTime_RoundTowardsInfinity(tv->tv_sec < 0, round))
         tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS);
     else
         tv->tv_usec = (int)(ns / US_TO_NS);

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


More information about the Python-checkins mailing list