@@ -4083,6 +4083,44 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us,
40834083 tzinfo );
40844084}
40854085
4086+ static time_t
4087+ _PyTime_DoubleToTimet (double x )
4088+ {
4089+ time_t result ;
4090+ double diff ;
4091+
4092+ result = (time_t )x ;
4093+ /* How much info did we lose? time_t may be an integral or
4094+ * floating type, and we don't know which. If it's integral,
4095+ * we don't know whether C truncates, rounds, returns the floor,
4096+ * etc. If we lost a second or more, the C rounding is
4097+ * unreasonable, or the input just doesn't fit in a time_t;
4098+ * call it an error regardless. Note that the original cast to
4099+ * time_t can cause a C error too, but nothing we can do to
4100+ * worm around that.
4101+ */
4102+ diff = x - (double )result ;
4103+ if (diff <= -1.0 || diff >= 1.0 ) {
4104+ PyErr_SetString (PyExc_OverflowError ,
4105+ "timestamp out of range for platform time_t" );
4106+ result = (time_t )- 1 ;
4107+ }
4108+ return result ;
4109+ }
4110+
4111+ /* Round a double to the nearest long. |x| must be small enough to fit
4112+ * in a C long; this is not checked.
4113+ */
4114+ static double
4115+ _PyTime_RoundHalfEven (double x )
4116+ {
4117+ double rounded = round (x );
4118+ if (fabs (x - rounded ) == 0.5 )
4119+ /* halfway case: round to even */
4120+ rounded = 2.0 * round (x /2.0 );
4121+ return rounded ;
4122+ }
4123+
40864124/* Internal helper.
40874125 * Build datetime from a Python timestamp. Pass localtime or gmtime for f,
40884126 * 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,
40914129 * to get that much precision (e.g., C time() isn't good enough).
40924130 */
40934131static PyObject *
4094- datetime_from_timestamp (PyObject * cls , TM_FUNC f , PyObject * timestamp ,
4132+ datetime_from_timestamp (PyObject * cls , TM_FUNC f , double timestamp ,
40954133 PyObject * tzinfo )
40964134{
40974135 time_t timet ;
4098- long us ;
4136+ double fraction ;
4137+ int us ;
40994138
4100- if ( _PyTime_ObjectToTimeval ( timestamp ,
4101- & timet , & us , _PyTime_ROUND_FLOOR ) == -1 )
4139+ timet = _PyTime_DoubleToTimet ( timestamp );
4140+ if ( timet == ( time_t ) - 1 && PyErr_Occurred () )
41024141 return NULL ;
4103- assert (0 <= us && us <= 999999 );
4104-
4105- return datetime_from_timet_and_us (cls , f , timet , (int )us , tzinfo );
4142+ fraction = timestamp - (double )timet ;
4143+ us = (int )_PyTime_RoundHalfEven (fraction * 1e6 );
4144+ if (us < 0 ) {
4145+ /* Truncation towards zero is not what we wanted
4146+ for negative numbers (Python's mod semantics) */
4147+ timet -= 1 ;
4148+ us += 1000000 ;
4149+ }
4150+ /* If timestamp is less than one microsecond smaller than a
4151+ * full second, round up. Otherwise, ValueErrors are raised
4152+ * for some floats. */
4153+ if (us == 1000000 ) {
4154+ timet += 1 ;
4155+ us = 0 ;
4156+ }
4157+ return datetime_from_timet_and_us (cls , f , timet , us , tzinfo );
41064158}
41074159
41084160/* Internal helper.
@@ -4175,11 +4227,11 @@ static PyObject *
41754227datetime_fromtimestamp (PyObject * cls , PyObject * args , PyObject * kw )
41764228{
41774229 PyObject * self ;
4178- PyObject * timestamp ;
4230+ double timestamp ;
41794231 PyObject * tzinfo = Py_None ;
41804232 static char * keywords [] = {"timestamp" , "tz" , NULL };
41814233
4182- if (! PyArg_ParseTupleAndKeywords (args , kw , "O |O:fromtimestamp" ,
4234+ if (! PyArg_ParseTupleAndKeywords (args , kw , "d |O:fromtimestamp" ,
41834235 keywords , & timestamp , & tzinfo ))
41844236 return NULL ;
41854237 if (check_tzinfo_subclass (tzinfo ) < 0 )
@@ -4203,10 +4255,10 @@ datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw)
42034255static PyObject *
42044256datetime_utcfromtimestamp (PyObject * cls , PyObject * args )
42054257{
4206- PyObject * timestamp ;
4258+ double timestamp ;
42074259 PyObject * result = NULL ;
42084260
4209- if (PyArg_ParseTuple (args , "O :utcfromtimestamp" , & timestamp ))
4261+ if (PyArg_ParseTuple (args , "d :utcfromtimestamp" , & timestamp ))
42104262 result = datetime_from_timestamp (cls , gmtime , timestamp ,
42114263 Py_None );
42124264 return result ;
0 commit comments