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.
This commit is contained in:
parent
b3b4544070
commit
02937aab13
|
@ -38,8 +38,12 @@ PyAPI_FUNC(void) _PyTime_gettimeofday(_PyTime_timeval *tp);
|
||||||
typedef enum {
|
typedef enum {
|
||||||
/* Round towards zero. */
|
/* Round towards zero. */
|
||||||
_PyTime_ROUND_DOWN=0,
|
_PyTime_ROUND_DOWN=0,
|
||||||
/* Round away from zero. */
|
/* Round away from zero.
|
||||||
_PyTime_ROUND_UP
|
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;
|
} _PyTime_round_t;
|
||||||
|
|
||||||
/* Convert a number of seconds, int or float, to time_t. */
|
/* Convert a number of seconds, int or float, to time_t. */
|
||||||
|
@ -81,6 +85,9 @@ PyAPI_FUNC(int) _PyTime_Init(void);
|
||||||
/****************** NEW _PyTime_t API **********************/
|
/****************** NEW _PyTime_t API **********************/
|
||||||
|
|
||||||
#ifdef PY_INT64_T
|
#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;
|
typedef PY_INT64_T _PyTime_t;
|
||||||
#define _PyTime_MIN PY_LLONG_MIN
|
#define _PyTime_MIN PY_LLONG_MIN
|
||||||
#define _PyTime_MAX PY_LLONG_MAX
|
#define _PyTime_MAX PY_LLONG_MAX
|
||||||
|
|
|
@ -28,8 +28,13 @@ class _PyTime(enum.IntEnum):
|
||||||
ROUND_DOWN = 0
|
ROUND_DOWN = 0
|
||||||
# Round away from zero
|
# Round away from zero
|
||||||
ROUND_UP = 1
|
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):
|
class TimeTestCase(unittest.TestCase):
|
||||||
|
@ -631,53 +636,6 @@ class TestPytime(unittest.TestCase):
|
||||||
self.assertRaises(OverflowError,
|
self.assertRaises(OverflowError,
|
||||||
pytime_object_to_time_t, invalid, rnd)
|
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
|
@support.cpython_only
|
||||||
def test_timespec(self):
|
def test_timespec(self):
|
||||||
from _testcapi import pytime_object_to_timespec
|
from _testcapi import pytime_object_to_timespec
|
||||||
|
@ -835,24 +793,31 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
# Conversion giving different results depending on the rounding method
|
# Conversion giving different results depending on the rounding method
|
||||||
UP = _PyTime.ROUND_UP
|
UP = _PyTime.ROUND_UP
|
||||||
DOWN = _PyTime.ROUND_DOWN
|
DOWN = _PyTime.ROUND_DOWN
|
||||||
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
for obj, ts, rnd in (
|
for obj, ts, rnd in (
|
||||||
# close to zero
|
# close to zero
|
||||||
( 1e-10, 1, UP),
|
( 1e-10, 1, UP),
|
||||||
( 1e-10, 0, DOWN),
|
( 1e-10, 0, DOWN),
|
||||||
|
( 1e-10, 0, FLOOR),
|
||||||
(-1e-10, 0, DOWN),
|
(-1e-10, 0, DOWN),
|
||||||
(-1e-10, -1, UP),
|
(-1e-10, -1, UP),
|
||||||
|
(-1e-10, -1, FLOOR),
|
||||||
|
|
||||||
# test rounding of the last nanosecond
|
# test rounding of the last nanosecond
|
||||||
( 1.1234567899, 1123456790, UP),
|
( 1.1234567899, 1123456790, UP),
|
||||||
( 1.1234567899, 1123456789, DOWN),
|
( 1.1234567899, 1123456789, DOWN),
|
||||||
|
( 1.1234567899, 1123456789, FLOOR),
|
||||||
(-1.1234567899, -1123456789, DOWN),
|
(-1.1234567899, -1123456789, DOWN),
|
||||||
(-1.1234567899, -1123456790, UP),
|
(-1.1234567899, -1123456790, UP),
|
||||||
|
(-1.1234567899, -1123456790, FLOOR),
|
||||||
|
|
||||||
# close to 1 second
|
# close to 1 second
|
||||||
( 0.9999999999, 1000000000, UP),
|
( 0.9999999999, 1000000000, UP),
|
||||||
( 0.9999999999, 999999999, DOWN),
|
( 0.9999999999, 999999999, DOWN),
|
||||||
|
( 0.9999999999, 999999999, FLOOR),
|
||||||
(-0.9999999999, -999999999, DOWN),
|
(-0.9999999999, -999999999, DOWN),
|
||||||
(-0.9999999999, -1000000000, UP),
|
(-0.9999999999, -1000000000, UP),
|
||||||
|
(-0.9999999999, -1000000000, FLOOR),
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj, round=rnd, timestamp=ts):
|
with self.subTest(obj=obj, round=rnd, timestamp=ts):
|
||||||
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
|
self.assertEqual(PyTime_FromSecondsObject(obj, rnd), ts)
|
||||||
|
@ -924,18 +889,23 @@ class TestPyTime_t(unittest.TestCase):
|
||||||
|
|
||||||
UP = _PyTime.ROUND_UP
|
UP = _PyTime.ROUND_UP
|
||||||
DOWN = _PyTime.ROUND_DOWN
|
DOWN = _PyTime.ROUND_DOWN
|
||||||
|
FLOOR = _PyTime.ROUND_FLOOR
|
||||||
for ns, tv, rnd in (
|
for ns, tv, rnd in (
|
||||||
# nanoseconds
|
# nanoseconds
|
||||||
(1, (0, 1), UP),
|
(1, (0, 1), UP),
|
||||||
(1, (0, 0), DOWN),
|
(1, (0, 0), DOWN),
|
||||||
|
(1, (0, 0), FLOOR),
|
||||||
(-1, (0, 0), DOWN),
|
(-1, (0, 0), DOWN),
|
||||||
(-1, (-1, 999999), UP),
|
(-1, (-1, 999999), UP),
|
||||||
|
(-1, (-1, 999999), FLOOR),
|
||||||
|
|
||||||
# seconds + nanoseconds
|
# seconds + nanoseconds
|
||||||
(1234567001, (1, 234568), UP),
|
(1234567001, (1, 234568), UP),
|
||||||
(1234567001, (1, 234567), DOWN),
|
(1234567001, (1, 234567), DOWN),
|
||||||
|
(1234567001, (1, 234567), FLOOR),
|
||||||
(-1234567001, (-2, 765433), DOWN),
|
(-1234567001, (-2, 765433), DOWN),
|
||||||
(-1234567001, (-2, 765432), UP),
|
(-1234567001, (-2, 765432), UP),
|
||||||
|
(-1234567001, (-2, 765432), FLOOR),
|
||||||
):
|
):
|
||||||
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
|
with self.subTest(nanoseconds=ns, timeval=tv, round=rnd):
|
||||||
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
|
self.assertEqual(PyTime_AsTimeval(ns, rnd), tv)
|
||||||
|
|
|
@ -2634,7 +2634,8 @@ run_in_subinterp(PyObject *self, PyObject *args)
|
||||||
static int
|
static int
|
||||||
check_time_rounding(int round)
|
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");
|
PyErr_SetString(PyExc_ValueError, "invalid rounding");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ time_clock_settime(PyObject *self, PyObject *args)
|
||||||
if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj))
|
if (!PyArg_ParseTuple(args, "iO:clock_settime", &clk_id, &obj))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_DOWN) < 0)
|
if (_PyTime_FromSecondsObject(&t, obj, _PyTime_ROUND_FLOOR) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (_PyTime_AsTimespec(t, &tp) == -1)
|
if (_PyTime_AsTimespec(t, &tp) == -1)
|
||||||
|
@ -322,7 +322,7 @@ parse_time_t_args(PyObject *args, char *format, time_t *pwhen)
|
||||||
whent = time(NULL);
|
whent = time(NULL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_DOWN) == -1)
|
if (_PyTime_ObjectToTime_t(ot, &whent, _PyTime_ROUND_FLOOR) == -1)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
*pwhen = whent;
|
*pwhen = whent;
|
||||||
|
|
|
@ -260,6 +260,14 @@ _PyTime_overflow(void)
|
||||||
"timestamp too large to convert to C _PyTime_t");
|
"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_t
|
||||||
_PyTime_FromNanoseconds(PY_LONG_LONG ns)
|
_PyTime_FromNanoseconds(PY_LONG_LONG ns)
|
||||||
{
|
{
|
||||||
|
@ -314,7 +322,7 @@ _PyTime_FromSecondsObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round)
|
||||||
d = PyFloat_AsDouble(obj);
|
d = PyFloat_AsDouble(obj);
|
||||||
d *= 1e9;
|
d *= 1e9;
|
||||||
|
|
||||||
if ((round == _PyTime_ROUND_UP) ^ (d < 0))
|
if (_PyTime_RoundTowardsInfinity(d < 0, round))
|
||||||
d = ceil(d);
|
d = ceil(d);
|
||||||
else
|
else
|
||||||
d = floor(d);
|
d = floor(d);
|
||||||
|
@ -380,7 +388,7 @@ _PyTime_Multiply(_PyTime_t t, unsigned int multiply, _PyTime_round_t round)
|
||||||
_PyTime_t k;
|
_PyTime_t k;
|
||||||
if (multiply < SEC_TO_NS) {
|
if (multiply < SEC_TO_NS) {
|
||||||
k = SEC_TO_NS / multiply;
|
k = SEC_TO_NS / multiply;
|
||||||
if (round == _PyTime_ROUND_UP)
|
if (_PyTime_RoundTowardsInfinity(t < 0, round))
|
||||||
return (t + k - 1) / k;
|
return (t + k - 1) / k;
|
||||||
else
|
else
|
||||||
return t / k;
|
return t / k;
|
||||||
|
@ -397,6 +405,7 @@ _PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round)
|
||||||
return _PyTime_Multiply(t, 1000, round);
|
return _PyTime_Multiply(t, 1000, round);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FIXME: write unit tests */
|
||||||
_PyTime_t
|
_PyTime_t
|
||||||
_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round)
|
_PyTime_AsMicroseconds(_PyTime_t t, _PyTime_round_t round)
|
||||||
{
|
{
|
||||||
|
@ -439,7 +448,7 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
|
||||||
res = -1;
|
res = -1;
|
||||||
#endif
|
#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);
|
tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS);
|
||||||
else
|
else
|
||||||
tv->tv_usec = (int)(ns / US_TO_NS);
|
tv->tv_usec = (int)(ns / US_TO_NS);
|
||||||
|
|
Loading…
Reference in New Issue