From 7c186e2a18bf174afd5cccc8c35b7167d05f7e2a Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Tue, 20 Apr 2010 22:32:49 +0000 Subject: Issue #2706: Add support for dividing a timedelta by another timedelta. Adds support for the three division operations: - timedelta / timedelta -> float - timedelta // timedelta -> int - timedelta % timedelta -> timedelta also adds support for divmod(timedelta, timedelta). Patch by Victor Stinner, adapted for py3k and extended by Alexander Belopolsky. --- Doc/library/datetime.rst | 22 ++++++- Lib/test/test_datetime.py | 54 ++++++++++++++++- Misc/NEWS | 4 ++ Modules/datetimemodule.c | 150 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 224 insertions(+), 6 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 65122f2..758e4d2 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -220,8 +220,20 @@ Supported operations: | | In general, *t1* \* i == *t1* \* (i-1) + *t1* | | | is true. (1) | +--------------------------------+-----------------------------------------------+ -| ``t1 = t2 // i`` | The floor is computed and the remainder (if | -| | any) is thrown away. (3) | +| ``f = t2 / t3`` | Division (3) of *t2* by *t3*. Returns a | +| | :class:`float` object. | ++--------------------------------+-----------------------------------------------+ +| ``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) | ++--------------------------------+-----------------------------------------------+ +| ``t1 = t2 % t3`` | The remainder is computed as a | +| | :class:`timedelta` object. (3) | ++--------------------------------+-----------------------------------------------+ +| ``q, r = divmod(t1, t2)`` | Computes the quotient and the remainder: | +| | ``q = t1 // t2`` (3) and ``r = t1 % t2``. | +| | q is an integer and r is a :class:`timedelta` | +| | object. | +--------------------------------+-----------------------------------------------+ | ``+t1`` | Returns a :class:`timedelta` object with the | | | same value. (2) | @@ -252,6 +264,12 @@ In addition to the operations listed above :class:`timedelta` objects support certain additions and subtractions with :class:`date` and :class:`datetime` 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. + + Comparisons of :class:`timedelta` objects are supported with the :class:`timedelta` object representing the smaller duration considered to be the smaller timedelta. In order to stop mixed-type comparisons from falling back to diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 4818482..a5b53fb 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -7,7 +7,7 @@ import os import pickle import unittest -from operator import lt, le, gt, ge, eq, ne +from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod from test import support @@ -469,6 +469,58 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): self.assertEqual(str(t3), str(t4)) self.assertEqual(t4.as_hours(), -1) + def test_division(self): + t = timedelta(hours=1, minutes=24, seconds=19) + second = timedelta(seconds=1) + self.assertEqual(t / second, 5059.0) + self.assertEqual(t // second, 5059) + + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + self.assertEqual(t / minute, 2.5) + self.assertEqual(t // minute, 2) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, truediv, t, zerotd) + self.assertRaises(ZeroDivisionError, floordiv, t, zerotd) + + self.assertRaises(TypeError, truediv, t, 2) + # note: floor division of a timedelta by an integer *is* + # currently permitted. + + def test_remainder(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + r = t % minute + self.assertEqual(r, timedelta(seconds=30)) + + t = timedelta(minutes=-2, seconds=30) + r = t % minute + self.assertEqual(r, timedelta(seconds=30)) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, mod, t, zerotd) + + self.assertRaises(TypeError, mod, t, 10) + + def test_divmod(self): + t = timedelta(minutes=2, seconds=30) + minute = timedelta(minutes=1) + q, r = divmod(t, minute) + self.assertEqual(q, 2) + self.assertEqual(r, timedelta(seconds=30)) + + t = timedelta(minutes=-2, seconds=30) + q, r = divmod(t, minute) + self.assertEqual(q, -2) + self.assertEqual(r, timedelta(seconds=30)) + + zerotd = timedelta(0) + self.assertRaises(ZeroDivisionError, divmod, t, zerotd) + + self.assertRaises(TypeError, divmod, t, 10) + + ############################################################################# # date tests diff --git a/Misc/NEWS b/Misc/NEWS index b73ffba..fc3b4c0 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -966,6 +966,10 @@ Library Extension Modules ----------------- +- Issue #2706: Allow division of a timedelta by another timedelta: + timedelta / timedelta, timedelta % timedelta, timedelta // timedelta + and divmod(timedelta, timedelta) are all supported. + - Issue #8314: Fix unsigned long long bug in libffi on Sparc v8. - Issue #8300: When passing a non-integer argument to struct.pack with any diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index c6efee0..2896486 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -1666,6 +1666,52 @@ divide_timedelta_int(PyDateTime_Delta *delta, PyObject *intobj) } static PyObject * +divide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *result; + + pyus_left = delta_to_microseconds(left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds(right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + result = PyNumber_FloorDivide(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + return result; +} + +static PyObject * +truedivide_timedelta_timedelta(PyDateTime_Delta *left, PyDateTime_Delta *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *result; + + pyus_left = delta_to_microseconds(left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds(right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + result = PyNumber_TrueDivide(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + return result; +} + +static PyObject * delta_add(PyObject *left, PyObject *right) { PyObject *result = Py_NotImplemented; @@ -1810,6 +1856,27 @@ delta_divide(PyObject *left, PyObject *right) result = divide_timedelta_int( (PyDateTime_Delta *)left, right); + else if (PyDelta_Check(right)) + result = divide_timedelta_timedelta( + (PyDateTime_Delta *)left, + (PyDateTime_Delta *)right); + } + + if (result == Py_NotImplemented) + Py_INCREF(result); + return result; +} + +static PyObject * +delta_truedivide(PyObject *left, PyObject *right) +{ + PyObject *result = Py_NotImplemented; + + if (PyDelta_Check(left)) { + if (PyDelta_Check(right)) + result = truedivide_timedelta_timedelta( + (PyDateTime_Delta *)left, + (PyDateTime_Delta *)right); } if (result == Py_NotImplemented) @@ -1817,6 +1884,83 @@ delta_divide(PyObject *left, PyObject *right) return result; } +static PyObject * +delta_remainder(PyObject *left, PyObject *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *pyus_remainder; + PyObject *remainder; + + if (!PyDelta_Check(left) || !PyDelta_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + pyus_remainder = PyNumber_Remainder(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + if (pyus_remainder == NULL) + return NULL; + + remainder = microseconds_to_delta(pyus_remainder); + if (remainder == NULL) { + Py_DECREF(divmod); + return NULL; + } + + return remainder; +} + +static PyObject * +delta_divmod(PyObject *left, PyObject *right) +{ + PyObject *pyus_left; + PyObject *pyus_right; + PyObject *divmod; + PyObject *microseconds, *delta; + + if (!PyDelta_Check(left) || !PyDelta_Check(right)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + pyus_left = delta_to_microseconds((PyDateTime_Delta *)left); + if (pyus_left == NULL) + return NULL; + + pyus_right = delta_to_microseconds((PyDateTime_Delta *)right); + if (pyus_right == NULL) { + Py_DECREF(pyus_left); + return NULL; + } + + divmod = PyNumber_Divmod(pyus_left, pyus_right); + Py_DECREF(pyus_left); + Py_DECREF(pyus_right); + if (divmod == NULL) + return NULL; + + microseconds = PyTuple_GetItem(divmod, 1); + delta = microseconds_to_delta(microseconds); + if (delta == NULL) { + Py_DECREF(divmod); + return NULL; + } + PyTuple_SetItem(divmod, 1, delta); + return divmod; +} + /* Fold in the value of the tag ("seconds", "weeks", etc) component of a * timedelta constructor. sofar is the # of microseconds accounted for * so far, and there are factor microseconds per current unit, the number @@ -2108,8 +2252,8 @@ static PyNumberMethods delta_as_number = { delta_add, /* nb_add */ delta_subtract, /* nb_subtract */ delta_multiply, /* nb_multiply */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ + delta_remainder, /* nb_remainder */ + delta_divmod, /* nb_divmod */ 0, /* nb_power */ (unaryfunc)delta_negative, /* nb_negative */ (unaryfunc)delta_positive, /* nb_positive */ @@ -2135,7 +2279,7 @@ static PyNumberMethods delta_as_number = { 0, /*nb_inplace_xor*/ 0, /*nb_inplace_or*/ delta_divide, /* nb_floor_divide */ - 0, /* nb_true_divide */ + delta_truedivide, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ }; -- cgit v0.12