Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 84ff4ab

Browse files
committed
Merge 3.4 (datetime rounding)
2 parents ec26f83 + 511491a commit 84ff4ab

4 files changed

Lines changed: 110 additions & 45 deletions

File tree

Lib/datetime.py

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,48 +1366,42 @@ def tzinfo(self):
13661366
return self._tzinfo
13671367

13681368
@classmethod
1369-
def fromtimestamp(cls, t, tz=None):
1369+
def _fromtimestamp(cls, t, utc, tz):
13701370
"""Construct a datetime from a POSIX timestamp (like time.time()).
13711371
13721372
A timezone info object may be passed in as well.
13731373
"""
1374-
_check_tzinfo_arg(tz)
1375-
1376-
converter = _time.localtime if tz is None else _time.gmtime
1377-
1378-
t, frac = divmod(t, 1.0)
1379-
us = int(frac * 1e6)
1380-
1381-
# If timestamp is less than one microsecond smaller than a
1382-
# full second, us can be rounded up to 1000000. In this case,
1383-
# roll over to seconds, otherwise, ValueError is raised
1384-
# by the constructor.
1385-
if us == 1000000:
1374+
frac, t = _math.modf(t)
1375+
us = round(frac * 1e6)
1376+
if us >= 1000000:
13861377
t += 1
1387-
us = 0
1378+
us -= 1000000
1379+
elif us < 0:
1380+
t -= 1
1381+
us += 1000000
1382+
1383+
converter = _time.gmtime if utc else _time.localtime
13881384
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
13891385
ss = min(ss, 59) # clamp out leap seconds if the platform has them
1390-
result = cls(y, m, d, hh, mm, ss, us, tz)
1386+
return cls(y, m, d, hh, mm, ss, us, tz)
1387+
1388+
@classmethod
1389+
def fromtimestamp(cls, t, tz=None):
1390+
"""Construct a datetime from a POSIX timestamp (like time.time()).
1391+
1392+
A timezone info object may be passed in as well.
1393+
"""
1394+
_check_tzinfo_arg(tz)
1395+
1396+
result = cls._fromtimestamp(t, tz is not None, tz)
13911397
if tz is not None:
13921398
result = tz.fromutc(result)
13931399
return result
13941400

13951401
@classmethod
13961402
def utcfromtimestamp(cls, t):
13971403
"""Construct a naive UTC datetime from a POSIX timestamp."""
1398-
t, frac = divmod(t, 1.0)
1399-
us = int(frac * 1e6)
1400-
1401-
# If timestamp is less than one microsecond smaller than a
1402-
# full second, us can be rounded up to 1000000. In this case,
1403-
# roll over to seconds, otherwise, ValueError is raised
1404-
# by the constructor.
1405-
if us == 1000000:
1406-
t += 1
1407-
us = 0
1408-
y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t)
1409-
ss = min(ss, 59) # clamp out leap seconds if the platform has them
1410-
return cls(y, m, d, hh, mm, ss, us)
1404+
return cls._fromtimestamp(t, True, None)
14111405

14121406
@classmethod
14131407
def now(cls, tz=None):

Lib/test/datetimetester.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -663,11 +663,15 @@ def test_microsecond_rounding(self):
663663
eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
664664
eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
665665
eq(td(milliseconds=0.5/1000), td(microseconds=0))
666-
eq(td(milliseconds=-0.5/1000), td(microseconds=0))
666+
eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
667667
eq(td(milliseconds=0.6/1000), td(microseconds=1))
668668
eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
669+
eq(td(milliseconds=1.5/1000), td(microseconds=2))
670+
eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
669671
eq(td(seconds=0.5/10**6), td(microseconds=0))
670-
eq(td(seconds=-0.5/10**6), td(microseconds=0))
672+
eq(td(seconds=-0.5/10**6), td(microseconds=-0))
673+
eq(td(seconds=1/2**7), td(microseconds=7812))
674+
eq(td(seconds=-1/2**7), td(microseconds=-7812))
671675

672676
# Rounding due to contributions from more than one field.
673677
us_per_hour = 3600e6
@@ -1851,6 +1855,7 @@ def test_microsecond_rounding(self):
18511855
zero = fts(0)
18521856
self.assertEqual(zero.second, 0)
18531857
self.assertEqual(zero.microsecond, 0)
1858+
one = fts(1e-6)
18541859
try:
18551860
minus_one = fts(-1e-6)
18561861
except OSError:
@@ -1861,22 +1866,28 @@ def test_microsecond_rounding(self):
18611866
self.assertEqual(minus_one.microsecond, 999999)
18621867

18631868
t = fts(-1e-8)
1864-
self.assertEqual(t, minus_one)
1869+
self.assertEqual(t, zero)
18651870
t = fts(-9e-7)
18661871
self.assertEqual(t, minus_one)
18671872
t = fts(-1e-7)
1868-
self.assertEqual(t, minus_one)
1873+
self.assertEqual(t, zero)
1874+
t = fts(-1/2**7)
1875+
self.assertEqual(t.second, 59)
1876+
self.assertEqual(t.microsecond, 992188)
18691877

18701878
t = fts(1e-7)
18711879
self.assertEqual(t, zero)
18721880
t = fts(9e-7)
1873-
self.assertEqual(t, zero)
1881+
self.assertEqual(t, one)
18741882
t = fts(0.99999949)
18751883
self.assertEqual(t.second, 0)
18761884
self.assertEqual(t.microsecond, 999999)
18771885
t = fts(0.9999999)
1886+
self.assertEqual(t.second, 1)
1887+
self.assertEqual(t.microsecond, 0)
1888+
t = fts(1/2**7)
18781889
self.assertEqual(t.second, 0)
1879-
self.assertEqual(t.microsecond, 999999)
1890+
self.assertEqual(t.microsecond, 7812)
18801891

18811892
def test_insane_fromtimestamp(self):
18821893
# It's possible that some platform maps time_t to double,

Misc/NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Core and Builtins
1414
Library
1515
-------
1616

17+
- Issue #23517: Fix rounding in fromtimestamp() and utcfromtimestamp() methods
18+
of datetime.datetime: microseconds are now rounded to nearest with ties
19+
going to nearest even integer (ROUND_HALF_EVEN), instead of being rounding
20+
towards minus infinity (ROUND_FLOOR). It's important that these methods use
21+
the same rounding mode than datetime.timedelta to keep the property:
22+
(datetime(1970,1,1) + timedelta(seconds=t)) == datetime.utcfromtimestamp(t).
23+
It also the rounding mode used by round(float) for example.
24+
1725
- Issue #25155: Fix datetime.datetime.now() and datetime.datetime.utcnow() on
1826
Windows to support date after year 2038. It was a regression introduced in
1927
Python 3.5.0.

Modules/_datetimemodule.c

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
40934131
static 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 *
41754227
datetime_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)
42034255
static PyObject *
42044256
datetime_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

Comments
 (0)