diff options
author | Alexander Belopolsky <alexander.belopolsky@gmail.com> | 2010-05-31 17:33:47 (GMT) |
---|---|---|
committer | Alexander Belopolsky <alexander.belopolsky@gmail.com> | 2010-05-31 17:33:47 (GMT) |
commit | 1790bc43bf8430b219116f4af0423c3a732c9eb8 (patch) | |
tree | 57771b07db60bebd3fb1d038fb7c5c541f3dcb6e | |
parent | 9103597ee7e318f5c7183a1b1c97e5322f1f77aa (diff) | |
download | cpython-1790bc43bf8430b219116f4af0423c3a732c9eb8.zip cpython-1790bc43bf8430b219116f4af0423c3a732c9eb8.tar.gz cpython-1790bc43bf8430b219116f4af0423c3a732c9eb8.tar.bz2 |
Issue #1289118: datetime.timedelta objects can now be multiplied by float
and divided by float and int objects.
-rw-r--r-- | Doc/library/datetime.rst | 14 | ||||
-rw-r--r-- | Lib/test/test_datetime.py | 72 | ||||
-rw-r--r-- | Misc/NEWS | 5 | ||||
-rw-r--r-- | Modules/datetimemodule.c | 113 |
4 files changed, 191 insertions, 13 deletions
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 908a792..2a90e42 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -220,12 +220,20 @@ Supported operations: | | In general, *t1* \* i == *t1* \* (i-1) + *t1* | | | is true. (1) | +--------------------------------+-----------------------------------------------+ +| ``t1 = t2 * f or t1 = f * t2`` | Delta multiplied by a float. The result is | +| | rounded to the nearest multiple of | +| | timedelta.resolution using round-half-to-even.| ++--------------------------------+-----------------------------------------------+ | ``f = t2 / t3`` | Division (3) of *t2* by *t3*. Returns a | | | :class:`float` object. | +--------------------------------+-----------------------------------------------+ +| ``t1 = t2 / f or t1 = t2 / i`` | Delta divided by a float or an int. The result| +| | is rounded to the nearest multiple of | +| | timedelta.resolution using round-half-to-even.| ++--------------------------------+-----------------------------------------------+ | ``t1 = t2 // i`` or | The floor is computed and the remainder (if | | ``t1 = t2 // t3`` | any) is thrown away. In the second case, an | -| | integer is returned (3) | +| | integer is returned. (3) | +--------------------------------+-----------------------------------------------+ | ``t1 = t2 % t3`` | The remainder is computed as a | | | :class:`timedelta` object. (3) | @@ -267,7 +275,9 @@ objects (see below). .. versionadded:: 3.2 Floor division and true division of a :class:`timedelta` object by another :class:`timedelta` object are now supported, as are - remainder operations and the :func:`divmod` function. + remainder operations and the :func:`divmod` function. True + division and multiplication of a :class:`timedelta` object by + a :class:`float` object are now supported. Comparisons of :class:`timedelta` objects are supported with the diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index b751820..7c5fa9f 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -25,6 +25,16 @@ assert len(pickle_choices) == 3 OTHERSTUFF = (10, 34.5, "abc", {}, [], ()) +# XXX Copied from test_float. +INF = float("inf") +NAN = float("nan") + +# decorator for skipping tests on non-IEEE 754 platforms +requires_IEEE_754 = unittest.skipUnless( + float.__getformat__("double").startswith("IEEE"), + "test requires IEEE 754 doubles") + + ############################################################################# # module tests @@ -225,6 +235,36 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): eq(c//1000, td(0, 0, 1)) eq(a//10, td(0, 7*24*360)) eq(a//3600000, td(0, 0, 7*24*1000)) + eq(a/0.5, td(14)) + eq(b/0.5, td(0, 120)) + eq(a/7, td(1)) + eq(b/10, td(0, 6)) + eq(c/1000, td(0, 0, 1)) + eq(a/10, td(0, 7*24*360)) + eq(a/3600000, td(0, 0, 7*24*1000)) + + # Multiplication by float + us = td(microseconds=1) + eq((3*us) * 0.5, 2*us) + eq((5*us) * 0.5, 2*us) + eq(0.5 * (3*us), 2*us) + eq(0.5 * (5*us), 2*us) + eq((-3*us) * 0.5, -2*us) + eq((-5*us) * 0.5, -2*us) + + # Division by int and float + eq((3*us) / 2, 2*us) + eq((5*us) / 2, 2*us) + eq((-3*us) / 2.0, -2*us) + eq((-5*us) / 2.0, -2*us) + eq((3*us) / -2, -2*us) + eq((5*us) / -2, -2*us) + eq((3*us) / -2.0, -2*us) + eq((5*us) / -2.0, -2*us) + for i in range(-10, 10): + eq((i*us/3)//us, round(i/3)) + for i in range(-10, 10): + eq((i*us/-3)//us, round(i/-3)) def test_disallowed_computations(self): a = timedelta(42) @@ -236,20 +276,19 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): self.assertRaises(TypeError, lambda: i+a) self.assertRaises(TypeError, lambda: i-a) - # Mul/div by float isn't supported. - x = 2.3 - self.assertRaises(TypeError, lambda: a*x) - self.assertRaises(TypeError, lambda: x*a) - self.assertRaises(TypeError, lambda: a/x) - self.assertRaises(TypeError, lambda: x/a) - self.assertRaises(TypeError, lambda: a // x) - self.assertRaises(TypeError, lambda: x // a) - # Division of int by timedelta doesn't make sense. # Division by zero doesn't make sense. zero = 0 self.assertRaises(TypeError, lambda: zero // a) self.assertRaises(ZeroDivisionError, lambda: a // zero) + self.assertRaises(ZeroDivisionError, lambda: a / zero) + self.assertRaises(ZeroDivisionError, lambda: a / 0.0) + + @requires_IEEE_754 + def test_disallowed_special(self): + a = timedelta(42) + self.assertRaises(ValueError, a.__mul__, NAN) + self.assertRaises(ValueError, a.__truediv__, NAN) def test_basic_attributes(self): days, seconds, us = 1, 7, 31 @@ -410,6 +449,19 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): self.assertRaises(OverflowError, lambda: -timedelta.max) + day = timedelta(1) + self.assertRaises(OverflowError, day.__mul__, 10**9) + self.assertRaises(OverflowError, day.__mul__, 1e9) + self.assertRaises(OverflowError, day.__truediv__, 1e-20) + self.assertRaises(OverflowError, day.__truediv__, 1e-10) + self.assertRaises(OverflowError, day.__truediv__, 9e-10) + + @requires_IEEE_754 + def _test_overflow_special(self): + day = timedelta(1) + self.assertRaises(OverflowError, day.__mul__, INF) + self.assertRaises(OverflowError, day.__mul__, -INF) + def test_microsecond_rounding(self): td = timedelta eq = self.assertEqual @@ -489,7 +541,7 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): self.assertRaises(ZeroDivisionError, truediv, t, zerotd) self.assertRaises(ZeroDivisionError, floordiv, t, zerotd) - self.assertRaises(TypeError, truediv, t, 2) + # self.assertRaises(TypeError, truediv, t, 2) # note: floor division of a timedelta by an integer *is* # currently permitted. @@ -398,6 +398,11 @@ C-API Library ------- +- Issue #1289118: datetime.timedelta objects can now be multiplied by float + and divided by float and int objects. Results are rounded to the nearest + multiple of timedelta.resolution with ties resolved using round-half-to-even + method. + - Issue #7150: Raise OverflowError if the result of adding or subtracting timedelta from date or datetime falls outside of the MINYEAR:MAXYEAR range. diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 0f6b68f..8d7c5d1 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -152,6 +152,25 @@ round_to_long(double x) return (long)x; } +/* Nearest integer to m / n for integers m and n. Half-integer results + * are rounded to even. + */ +static PyObject * +divide_nearest(PyObject *m, PyObject *n) +{ + PyObject *result; + PyObject *temp; + + temp = _PyLong_Divmod_Near(m, n); + if (temp == NULL) + return NULL; + result = PyTuple_GET_ITEM(temp, 0); + Py_INCREF(result); + Py_DECREF(temp); + + return result; +} + /* --------------------------------------------------------------------------- * General calendrical helper functions */ @@ -1648,6 +1667,37 @@ multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta) } static PyObject * +multiply_float_timedelta(PyObject *floatobj, PyDateTime_Delta *delta) +{ + PyObject *result = NULL; + PyObject *pyus_in = NULL, *temp, *pyus_out; + PyObject *ratio = NULL; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + ratio = PyObject_CallMethod(floatobj, "as_integer_ratio", NULL); + if (ratio == NULL) + goto error; + temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 0)); + Py_DECREF(pyus_in); + pyus_in = NULL; + if (temp == NULL) + goto error; + pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 1)); + Py_DECREF(temp); + if (pyus_out == NULL) + goto error; + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + error: + Py_XDECREF(pyus_in); + Py_XDECREF(ratio); + + return result; +} + +static PyObject * divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj) { PyObject *pyus_in; @@ -1715,6 +1765,55 @@ truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) } static PyObject * +truedivide_timedelta_float(PyDateTime_Delta *delta, PyObject *f) +{ + PyObject *result = NULL; + PyObject *pyus_in = NULL, *temp, *pyus_out; + PyObject *ratio = NULL; + + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + ratio = PyObject_CallMethod(f, "as_integer_ratio", NULL); + if (ratio == NULL) + goto error; + temp = PyNumber_Multiply(pyus_in, PyTuple_GET_ITEM(ratio, 1)); + Py_DECREF(pyus_in); + pyus_in = NULL; + if (temp == NULL) + goto error; + pyus_out = divide_nearest(temp, PyTuple_GET_ITEM(ratio, 0)); + Py_DECREF(temp); + if (pyus_out == NULL) + goto error; + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + error: + Py_XDECREF(pyus_in); + Py_XDECREF(ratio); + + return result; +} + +static PyObject * +truedivide_timedelta_int(PyDateTime_Delta *delta, PyObject *i) +{ + PyObject *result; + PyObject *pyus_in, *pyus_out; + pyus_in = delta_to_microseconds(delta); + if (pyus_in == NULL) + return NULL; + pyus_out = divide_nearest(pyus_in, i); + Py_DECREF(pyus_in); + if (pyus_out == NULL) + return NULL; + result = microseconds_to_delta(pyus_out); + Py_DECREF(pyus_out); + + return result; +} + +static PyObject * delta_add(PyObject *left, PyObject *right) { PyObject *result = Py_NotImplemented; @@ -1838,10 +1937,16 @@ delta_multiply(PyObject *left, PyObject *right) if (PyLong_Check(right)) result = multiply_int_timedelta(right, (PyDateTime_Delta *) left); + else if (PyFloat_Check(right)) + result = multiply_float_timedelta(right, + (PyDateTime_Delta *) left); } else if (PyLong_Check(left)) result = multiply_int_timedelta(left, - (PyDateTime_Delta *) right); + (PyDateTime_Delta *) right); + else if (PyFloat_Check(left)) + result = multiply_float_timedelta(left, + (PyDateTime_Delta *) right); if (result == Py_NotImplemented) Py_INCREF(result); @@ -1880,6 +1985,12 @@ delta_truedivide(PyObject *left, PyObject *right) result = truedivide_timedelta_timedelta( (PyDateTime_Delta *)left, (PyDateTime_Delta *)right); + else if (PyFloat_Check(right)) + result = truedivide_timedelta_float( + (PyDateTime_Delta *)left, right); + else if (PyLong_Check(right)) + result = truedivide_timedelta_int( + (PyDateTime_Delta *)left, right); } if (result == Py_NotImplemented) |