diff -r 8bb2c3ae9402 Include/pytime.h --- a/Include/pytime.h Mon Jan 20 23:56:40 2014 +0100 +++ b/Include/pytime.h Tue Jan 21 01:32:22 2014 +0100 @@ -53,10 +53,19 @@ do { \ (tv_end.tv_usec - tv_start.tv_usec) * 0.000001) #ifndef Py_LIMITED_API + +typedef enum { + /* Round towards zero. */ + _PyTime_ROUND_DOWN=0, + /* Round away from zero. */ + _PyTime_ROUND_UP +} _PyTime_round_t; + /* Convert a number of seconds, int or float, to time_t. */ PyAPI_FUNC(int) _PyTime_ObjectToTime_t( PyObject *obj, - time_t *sec); + time_t *sec, + _PyTime_round_t); /* Convert a time_t to a PyLong. */ PyAPI_FUNC(PyObject *) _PyLong_FromTime_t( @@ -72,7 +81,8 @@ PyAPI_FUNC(time_t) _PyLong_AsTime_t( PyAPI_FUNC(int) _PyTime_ObjectToTimeval( PyObject *obj, time_t *sec, - long *usec); + long *usec, + _PyTime_round_t); /* Convert a number of seconds, int or float, to a timespec structure. nsec is in the range [0; 999999999] and rounded towards zero. @@ -80,7 +90,8 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimeval( PyAPI_FUNC(int) _PyTime_ObjectToTimespec( PyObject *obj, time_t *sec, - long *nsec); + long *nsec, + _PyTime_round_t); #endif /* Dummy to force linking. */ diff -r 8bb2c3ae9402 Lib/test/test_time.py --- a/Lib/test/test_time.py Mon Jan 20 23:56:40 2014 +0100 +++ b/Lib/test/test_time.py Tue Jan 21 01:32:22 2014 +0100 @@ -14,6 +14,8 @@ except ImportError: SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1 TIME_MINYEAR = -TIME_MAXYEAR - 1 +_PyTime_ROUND_DOWN = 0 +_PyTime_ROUND_UP = 1 class TimeTestCase(unittest.TestCase): @@ -584,56 +586,114 @@ class TestPytime(unittest.TestCase): 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), + for obj, time_t, rnd in ( + # Round towards zero + (0, 0, _PyTime_ROUND_DOWN), + (-1, -1, _PyTime_ROUND_DOWN), + (-1.0, -1, _PyTime_ROUND_DOWN), + (-1.9, -1, _PyTime_ROUND_DOWN), + (1.0, 1, _PyTime_ROUND_DOWN), + (1.9, 1, _PyTime_ROUND_DOWN), + # Round away from zero + (0, 0, _PyTime_ROUND_UP), + (-1, -1, _PyTime_ROUND_UP), + (-1.0, -1, _PyTime_ROUND_UP), + (-1.9, -2, _PyTime_ROUND_UP), + (1.0, 1, _PyTime_ROUND_UP), + (1.9, 2, _PyTime_ROUND_UP), ): - self.assertEqual(pytime_object_to_time_t(obj), time_t) + self.assertEqual(pytime_object_to_time_t(obj, rnd), time_t) + rnd = _PyTime_ROUND_DOWN for invalid in self.invalid_values: - self.assertRaises(OverflowError, pytime_object_to_time_t, invalid) + self.assertRaises(OverflowError, + pytime_object_to_time_t, invalid, rnd) 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)), + 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), ): - self.assertEqual(pytime_object_to_timeval(obj), timeval) + 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) + self.assertRaises(OverflowError, + pytime_object_to_timeval, invalid, rnd) 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.1234567890, (1, 123456789)), - (1.1234567899, (1, 123456789)), - (-1.1234567890, (-2, 876543211)), - (-1.1234567891, (-2, 876543210)), + for obj, timespec, 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-9, (0, 1), _PyTime_ROUND_DOWN), + (1e-10, (0, 0), _PyTime_ROUND_DOWN), + (-1e-9, (-1, 999999999), _PyTime_ROUND_DOWN), + (-1e-10, (-1, 999999999), _PyTime_ROUND_DOWN), + (-1.2, (-2, 800000000), _PyTime_ROUND_DOWN), + (0.9999999999, (0, 999999999), _PyTime_ROUND_DOWN), + (1.1234567890, (1, 123456789), _PyTime_ROUND_DOWN), + (1.1234567899, (1, 123456789), _PyTime_ROUND_DOWN), + (-1.1234567890, (-2, 876543211), _PyTime_ROUND_DOWN), + (-1.1234567891, (-2, 876543210), _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-9, (0, 1), _PyTime_ROUND_UP), + (1e-10, (0, 1), _PyTime_ROUND_UP), + (-1e-9, (-1, 999999999), _PyTime_ROUND_UP), + (-1e-10, (-1, 999999999), _PyTime_ROUND_UP), + (-1.2, (-2, 800000000), _PyTime_ROUND_UP), + (0.9999999999, (1, 0), _PyTime_ROUND_UP), + (1.1234567890, (1, 123456790), _PyTime_ROUND_UP), + (1.1234567899, (1, 123456790), _PyTime_ROUND_UP), + (-1.1234567890, (-2, 876543211), _PyTime_ROUND_UP), + (-1.1234567891, (-2, 876543210), _PyTime_ROUND_UP), ): - self.assertEqual(pytime_object_to_timespec(obj), timespec) + with self.subTest(obj=obj, round=rnd, timespec=timespec): + self.assertEqual(pytime_object_to_timespec(obj, rnd), timespec) + rnd = _PyTime_ROUND_DOWN for invalid in self.invalid_values: - self.assertRaises(OverflowError, pytime_object_to_timespec, invalid) + self.assertRaises(OverflowError, + pytime_object_to_timespec, invalid, rnd) @unittest.skipUnless(time._STRUCT_TM_ITEMS == 11, "needs tm_zone support") def test_localtime_timezone(self): diff -r 8bb2c3ae9402 Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/_datetimemodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -2459,7 +2459,7 @@ date_local_from_object(PyObject *cls, Py struct tm *tm; time_t t; - if (_PyTime_ObjectToTime_t(obj, &t) == -1) + if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_DOWN) == -1) return NULL; tm = localtime(&t); @@ -4127,7 +4127,7 @@ datetime_from_timestamp(PyObject *cls, T time_t timet; long us; - if (_PyTime_ObjectToTimeval(timestamp, &timet, &us) == -1) + if (_PyTime_ObjectToTimeval(timestamp, &timet, &us, _PyTime_ROUND_DOWN) == -1) return NULL; return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); } diff -r 8bb2c3ae9402 Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/_testcapimodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -2516,14 +2516,27 @@ run_in_subinterp(PyObject *self, PyObjec return PyLong_FromLong(r); } +static int +check_time_rounding(int round) +{ + if (round != _PyTime_ROUND_DOWN && round != _PyTime_ROUND_UP) { + PyErr_SetString(PyExc_ValueError, "invalid rounding"); + return -1; + } + return 0; +} + 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)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_time_t", &obj, &round)) return NULL; - if (_PyTime_ObjectToTime_t(obj, &sec) == -1) + if (check_time_rounding(round) < 0) + return NULL; + if (_PyTime_ObjectToTime_t(obj, &sec, round) == -1) return NULL; return _PyLong_FromTime_t(sec); } @@ -2534,9 +2547,12 @@ test_pytime_object_to_timeval(PyObject * PyObject *obj; time_t sec; long usec; - if (!PyArg_ParseTuple(args, "O:pytime_object_to_timeval", &obj)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timeval", &obj, &round)) return NULL; - if (_PyTime_ObjectToTimeval(obj, &sec, &usec) == -1) + if (check_time_rounding(round) < 0) + return NULL; + if (_PyTime_ObjectToTimeval(obj, &sec, &usec, round) == -1) return NULL; return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), usec); } @@ -2547,9 +2563,12 @@ test_pytime_object_to_timespec(PyObject PyObject *obj; time_t sec; long nsec; - if (!PyArg_ParseTuple(args, "O:pytime_object_to_timespec", &obj)) + int round; + if (!PyArg_ParseTuple(args, "Oi:pytime_object_to_timespec", &obj, &round)) return NULL; - if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1) + if (check_time_rounding(round) < 0) + return NULL; + if (_PyTime_ObjectToTimespec(obj, &sec, &nsec, round) == -1) return NULL; return Py_BuildValue("Nl", _PyLong_FromTime_t(sec), nsec); } diff -r 8bb2c3ae9402 Modules/posixmodule.c --- a/Modules/posixmodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/posixmodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -4877,9 +4877,9 @@ posix_utime(PyObject *self, PyObject *ar } utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), - &a_sec, &a_nsec) == -1 || + &a_sec, &a_nsec, _PyTime_ROUND_DOWN) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), - &m_sec, &m_nsec) == -1) { + &m_sec, &m_nsec, _PyTime_ROUND_DOWN) == -1) { goto exit; } utime.atime_s = a_sec; diff -r 8bb2c3ae9402 Modules/selectmodule.c --- a/Modules/selectmodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/selectmodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -215,7 +215,8 @@ select_select(PyObject *self, PyObject * else { #ifdef MS_WINDOWS time_t sec; - if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec) == -1) + if (_PyTime_ObjectToTimeval(tout, &sec, &tv.tv_usec, + _PyTime_ROUND_UP) == -1) return NULL; assert(sizeof(tv.tv_sec) == sizeof(long)); #if SIZEOF_TIME_T > SIZEOF_LONG @@ -230,7 +231,8 @@ select_select(PyObject *self, PyObject * /* 64-bit OS X has struct timeval.tv_usec as an int (and thus still 4 bytes as required), but no longer defined by a long. */ long tv_usec; - if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec) == -1) + if (_PyTime_ObjectToTimeval(tout, &tv.tv_sec, &tv_usec, + _PyTime_ROUND_UP) == -1) return NULL; tv.tv_usec = tv_usec; #endif @@ -1458,7 +1460,9 @@ pyepoll_poll(pyEpoll_Object *self, PyObj return NULL; } else { - timeout = (int)(dtimeout * 1000.0); + /* epoll_wait() has a resolution of 1 millisecond, round to the upper + bound to wait *at least* dtimeout seconds. */ + timeout = (int)ceil(dtimeout * 1000.0); } if (maxevents == -1) { @@ -2036,8 +2040,8 @@ kqueue_queue_control(kqueue_queue_Object ptimeoutspec = NULL; } else if (PyNumber_Check(otimeout)) { - if (_PyTime_ObjectToTimespec(otimeout, - &timeout.tv_sec, &timeout.tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(otimeout, &timeout.tv_sec, + &timeout.tv_nsec, _PyTime_ROUND_UP) == -1) return NULL; if (timeout.tv_sec < 0) { diff -r 8bb2c3ae9402 Modules/signalmodule.c --- a/Modules/signalmodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/signalmodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -799,7 +799,7 @@ signal_sigtimedwait(PyObject *self, PyOb &signals, &timeout)) return NULL; - if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(timeout, &tv_sec, &tv_nsec, _PyTime_ROUND_UP) == -1) return NULL; buf.tv_sec = tv_sec; buf.tv_nsec = tv_nsec; diff -r 8bb2c3ae9402 Modules/timemodule.c --- a/Modules/timemodule.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Modules/timemodule.c Tue Jan 21 01:32:22 2014 +0100 @@ -193,7 +193,7 @@ time_clock_settime(PyObject *self, PyObj if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj)) return NULL; - if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec) == -1) + if (_PyTime_ObjectToTimespec(obj, &tv_sec, &tv_nsec, _PyTime_ROUND_DOWN) == -1) return NULL; tp.tv_sec = tv_sec; tp.tv_nsec = tv_nsec; @@ -341,7 +341,7 @@ parse_time_t_args(PyObject *args, char * whent = time(NULL); } else { - if (_PyTime_ObjectToTime_t(ot, &whent) == -1) + if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1) return 0; } *pwhen = whent; diff -r 8bb2c3ae9402 Python/pytime.c --- a/Python/pytime.c Mon Jan 20 23:56:40 2014 +0100 +++ b/Python/pytime.c Tue Jan 21 01:32:22 2014 +0100 @@ -152,7 +152,7 @@ PyObject * static int _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator, - double denominator) + double denominator, _PyTime_round_t round) { assert(denominator <= LONG_MAX); if (PyFloat_Check(obj)) { @@ -167,6 +167,20 @@ static int intpart -= 1.0; } + floatpart *= denominator; + if (round == _PyTime_ROUND_UP) { + if (intpart >= 0) { + floatpart = ceil(floatpart); + if (floatpart >= denominator) { + floatpart = 0.0; + intpart += 1.0; + } + } + else { + floatpart = floor(floatpart); + } + } + *sec = (time_t)intpart; err = intpart - (double)*sec; if (err <= -1.0 || err >= 1.0) { @@ -174,7 +188,6 @@ static int return -1; } - floatpart *= denominator; *numerator = (long)floatpart; return 0; } @@ -188,12 +201,18 @@ static int } int -_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec) +_PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) { if (PyFloat_Check(obj)) { double d, intpart, err; d = PyFloat_AsDouble(obj); + if (round == _PyTime_ROUND_UP) { + if (d >= 0) + d = ceil(d); + else + d = floor(d); + } (void)modf(d, &intpart); *sec = (time_t)intpart; @@ -213,15 +232,17 @@ int } int -_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec) +_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec, + _PyTime_round_t round) { - return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9); + return _PyTime_ObjectToDenominator(obj, sec, nsec, 1e9, round); } int -_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec) +_PyTime_ObjectToTimeval(PyObject *obj, time_t *sec, long *usec, + _PyTime_round_t round) { - return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6); + return _PyTime_ObjectToDenominator(obj, sec, usec, 1e6, round); } void