mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Issue #1289118: datetime.timedelta objects can now be multiplied by float
and divided by float and int objects.
This commit is contained in:
parent
9103597ee7
commit
1790bc43bf
4 changed files with 191 additions and 13 deletions
|
@ -220,12 +220,20 @@ Supported operations:
|
||||||
| | In general, *t1* \* i == *t1* \* (i-1) + *t1* |
|
| | In general, *t1* \* i == *t1* \* (i-1) + *t1* |
|
||||||
| | is true. (1) |
|
| | 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 |
|
| ``f = t2 / t3`` | Division (3) of *t2* by *t3*. Returns a |
|
||||||
| | :class:`float` object. |
|
| | :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 // i`` or | The floor is computed and the remainder (if |
|
||||||
| ``t1 = t2 // t3`` | any) is thrown away. In the second case, an |
|
| ``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 |
|
| ``t1 = t2 % t3`` | The remainder is computed as a |
|
||||||
| | :class:`timedelta` object. (3) |
|
| | :class:`timedelta` object. (3) |
|
||||||
|
@ -267,7 +275,9 @@ objects (see below).
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
Floor division and true division of a :class:`timedelta` object by
|
Floor division and true division of a :class:`timedelta` object by
|
||||||
another :class:`timedelta` object are now supported, as are
|
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
|
Comparisons of :class:`timedelta` objects are supported with the
|
||||||
|
|
|
@ -25,6 +25,16 @@ assert len(pickle_choices) == 3
|
||||||
OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
|
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
|
# module tests
|
||||||
|
|
||||||
|
@ -225,6 +235,36 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
|
||||||
eq(c//1000, td(0, 0, 1))
|
eq(c//1000, td(0, 0, 1))
|
||||||
eq(a//10, td(0, 7*24*360))
|
eq(a//10, td(0, 7*24*360))
|
||||||
eq(a//3600000, td(0, 0, 7*24*1000))
|
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):
|
def test_disallowed_computations(self):
|
||||||
a = timedelta(42)
|
a = timedelta(42)
|
||||||
|
@ -236,20 +276,19 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
|
||||||
self.assertRaises(TypeError, lambda: i+a)
|
self.assertRaises(TypeError, lambda: i+a)
|
||||||
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 of int by timedelta doesn't make sense.
|
||||||
# Division by zero doesn't make sense.
|
# Division by zero doesn't make sense.
|
||||||
zero = 0
|
zero = 0
|
||||||
self.assertRaises(TypeError, lambda: zero // a)
|
self.assertRaises(TypeError, lambda: zero // a)
|
||||||
self.assertRaises(ZeroDivisionError, lambda: a // zero)
|
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):
|
def test_basic_attributes(self):
|
||||||
days, seconds, us = 1, 7, 31
|
days, seconds, us = 1, 7, 31
|
||||||
|
@ -410,6 +449,19 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
|
||||||
|
|
||||||
self.assertRaises(OverflowError, lambda: -timedelta.max)
|
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):
|
def test_microsecond_rounding(self):
|
||||||
td = timedelta
|
td = timedelta
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
|
@ -489,7 +541,7 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
|
||||||
self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
|
self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
|
||||||
self.assertRaises(ZeroDivisionError, floordiv, 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*
|
# note: floor division of a timedelta by an integer *is*
|
||||||
# currently permitted.
|
# currently permitted.
|
||||||
|
|
||||||
|
|
|
@ -398,6 +398,11 @@ C-API
|
||||||
Library
|
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
|
- Issue #7150: Raise OverflowError if the result of adding or subtracting
|
||||||
timedelta from date or datetime falls outside of the MINYEAR:MAXYEAR range.
|
timedelta from date or datetime falls outside of the MINYEAR:MAXYEAR range.
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,25 @@ round_to_long(double x)
|
||||||
return (long)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
|
* General calendrical helper functions
|
||||||
*/
|
*/
|
||||||
|
@ -1647,6 +1666,37 @@ multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 *
|
static PyObject *
|
||||||
divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj)
|
divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj)
|
||||||
{
|
{
|
||||||
|
@ -1714,6 +1764,55 @@ truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 *
|
static PyObject *
|
||||||
delta_add(PyObject *left, PyObject *right)
|
delta_add(PyObject *left, PyObject *right)
|
||||||
{
|
{
|
||||||
|
@ -1838,10 +1937,16 @@ delta_multiply(PyObject *left, PyObject *right)
|
||||||
if (PyLong_Check(right))
|
if (PyLong_Check(right))
|
||||||
result = multiply_int_timedelta(right,
|
result = multiply_int_timedelta(right,
|
||||||
(PyDateTime_Delta *) left);
|
(PyDateTime_Delta *) left);
|
||||||
|
else if (PyFloat_Check(right))
|
||||||
|
result = multiply_float_timedelta(right,
|
||||||
|
(PyDateTime_Delta *) left);
|
||||||
}
|
}
|
||||||
else if (PyLong_Check(left))
|
else if (PyLong_Check(left))
|
||||||
result = multiply_int_timedelta(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)
|
if (result == Py_NotImplemented)
|
||||||
Py_INCREF(result);
|
Py_INCREF(result);
|
||||||
|
@ -1880,6 +1985,12 @@ delta_truedivide(PyObject *left, PyObject *right)
|
||||||
result = truedivide_timedelta_timedelta(
|
result = truedivide_timedelta_timedelta(
|
||||||
(PyDateTime_Delta *)left,
|
(PyDateTime_Delta *)left,
|
||||||
(PyDateTime_Delta *)right);
|
(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)
|
if (result == Py_NotImplemented)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue