diff options
Diffstat (limited to 'Modules/_datetimemodule.c')
-rw-r--r-- | Modules/_datetimemodule.c | 74 |
1 files changed, 63 insertions, 11 deletions
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 662dcbc..1d583a5 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4083,6 +4083,44 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, tzinfo); } +static time_t +_PyTime_DoubleToTimet(double x) +{ + time_t result; + double diff; + + result = (time_t)x; + /* How much info did we lose? time_t may be an integral or + * floating type, and we don't know which. If it's integral, + * we don't know whether C truncates, rounds, returns the floor, + * etc. If we lost a second or more, the C rounding is + * unreasonable, or the input just doesn't fit in a time_t; + * call it an error regardless. Note that the original cast to + * time_t can cause a C error too, but nothing we can do to + * worm around that. + */ + diff = x - (double)result; + if (diff <= -1.0 || diff >= 1.0) { + PyErr_SetString(PyExc_OverflowError, + "timestamp out of range for platform time_t"); + result = (time_t)-1; + } + return result; +} + +/* Round a double to the nearest long. |x| must be small enough to fit + * in a C long; this is not checked. + */ +static double +_PyTime_RoundHalfEven(double x) +{ + double rounded = round(x); + if (fabs(x-rounded) == 0.5) + /* halfway case: round to even */ + rounded = 2.0*round(x/2.0); + return rounded; +} + /* Internal helper. * Build datetime from a Python timestamp. Pass localtime or gmtime for f, * to control the interpretation of the timestamp. Since a double doesn't @@ -4091,18 +4129,32 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, * to get that much precision (e.g., C time() isn't good enough). */ static PyObject * -datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp, +datetime_from_timestamp(PyObject *cls, TM_FUNC f, double timestamp, PyObject *tzinfo) { time_t timet; - long us; + double fraction; + int us; - if (_PyTime_ObjectToTimeval(timestamp, - &timet, &us, _PyTime_ROUND_FLOOR) == -1) + timet = _PyTime_DoubleToTimet(timestamp); + if (timet == (time_t)-1 && PyErr_Occurred()) return NULL; - assert(0 <= us && us <= 999999); - - return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo); + fraction = timestamp - (double)timet; + us = (int)_PyTime_RoundHalfEven(fraction * 1e6); + if (us < 0) { + /* Truncation towards zero is not what we wanted + for negative numbers (Python's mod semantics) */ + timet -= 1; + us += 1000000; + } + /* If timestamp is less than one microsecond smaller than a + * full second, round up. Otherwise, ValueErrors are raised + * for some floats. */ + if (us == 1000000) { + timet += 1; + us = 0; + } + return datetime_from_timet_and_us(cls, f, timet, us, tzinfo); } /* Internal helper. @@ -4175,11 +4227,11 @@ static PyObject * datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) { PyObject *self; - PyObject *timestamp; + double timestamp; PyObject *tzinfo = Py_None; static char *keywords[] = {"timestamp", "tz", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, kw, "O|O:fromtimestamp", + if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", keywords, ×tamp, &tzinfo)) return NULL; if (check_tzinfo_subclass(tzinfo) < 0) @@ -4203,10 +4255,10 @@ datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) static PyObject * datetime_utcfromtimestamp(PyObject *cls, PyObject *args) { - PyObject *timestamp; + double timestamp; PyObject *result = NULL; - if (PyArg_ParseTuple(args, "O:utcfromtimestamp", ×tamp)) + if (PyArg_ParseTuple(args, "d:utcfromtimestamp", ×tamp)) result = datetime_from_timestamp(cls, gmtime, timestamp, Py_None); return result; |