Skip to content

Commit 9fb1f91

Browse files
authored
BUG: Timedelta components no longer rounded with high precision integers (#31380)
* BUG: Timedelta components no longer rounded with high precision integers * Simplify NaT.total_seconds * Also simplify another tz conversion code path
1 parent 83e8389 commit 9fb1f91

File tree

6 files changed

+19
-36
lines changed

6 files changed

+19
-36
lines changed

doc/source/whatsnew/v1.1.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Datetimelike
109109
Timedelta
110110
^^^^^^^^^
111111

112-
-
112+
- Bug in constructing a :class:`Timedelta` with a high precision integer that would round the :class:`Timedelta` components (:issue:`31354`)
113113
-
114114

115115
Timezones

pandas/_libs/tslibs/conversion.pyx

+1-9
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ from pandas._libs.tslibs.util cimport (
2929
from pandas._libs.tslibs.timedeltas cimport cast_from_unit
3030
from pandas._libs.tslibs.timezones cimport (
3131
is_utc, is_tzlocal, is_fixed_offset, get_utcoffset, get_dst_info,
32-
get_timezone, maybe_get_tz, tz_compare, treat_tz_as_dateutil)
32+
get_timezone, maybe_get_tz, tz_compare)
3333
from pandas._libs.tslibs.timezones import UTC
3434
from pandas._libs.tslibs.parsing import parse_datetime_string
3535

@@ -341,14 +341,6 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
341341
obj.tzinfo = tz
342342
else:
343343
obj.value = pydatetime_to_dt64(ts, &obj.dts)
344-
# GH 24329 When datetime is ambiguous,
345-
# pydatetime_to_dt64 doesn't take DST into account
346-
# but with dateutil timezone, get_utcoffset does
347-
# so we need to correct for it
348-
if treat_tz_as_dateutil(ts.tzinfo):
349-
if ts.tzinfo.is_ambiguous(ts):
350-
dst_offset = ts.tzinfo.dst(ts)
351-
obj.value += int(dst_offset.total_seconds() * 1e9)
352344
obj.tzinfo = ts.tzinfo
353345

354346
if obj.tzinfo is not None and not is_utc(obj.tzinfo):

pandas/_libs/tslibs/nattype.pyx

+2-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from cpython.object cimport (
22
PyObject_RichCompare,
33
Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE)
44

5-
from cpython.datetime cimport (datetime,
5+
from cpython.datetime cimport (datetime, timedelta,
66
PyDateTime_Check, PyDelta_Check,
77
PyDateTime_IMPORT)
88

@@ -276,13 +276,6 @@ cdef class _NaT(datetime):
276276
def __long__(self):
277277
return NPY_NAT
278278

279-
def total_seconds(self):
280-
"""
281-
Total duration of timedelta in seconds (to microsecond precision).
282-
"""
283-
# GH#10939
284-
return np.nan
285-
286279
@property
287280
def is_leap_year(self):
288281
return False
@@ -386,6 +379,7 @@ class NaTType(_NaT):
386379
# nan methods
387380
weekday = _make_nan_func('weekday', datetime.weekday.__doc__)
388381
isoweekday = _make_nan_func('isoweekday', datetime.isoweekday.__doc__)
382+
total_seconds = _make_nan_func('total_seconds', timedelta.total_seconds.__doc__)
389383
month_name = _make_nan_func('month_name', # noqa:E128
390384
"""
391385
Return the month name of the Timestamp with specified locale.

pandas/_libs/tslibs/timedeltas.pyx

+1-9
Original file line numberDiff line numberDiff line change
@@ -859,14 +859,6 @@ cdef class _Timedelta(timedelta):
859859
"""
860860
return self.to_timedelta64()
861861

862-
def total_seconds(self):
863-
"""
864-
Total duration of timedelta in seconds (to microsecond precision).
865-
"""
866-
# GH 31043
867-
# Microseconds precision to avoid confusing tzinfo.utcoffset
868-
return (self.value - self.value % 1000) / 1e9
869-
870862
def view(self, dtype):
871863
"""
872864
Array view compatibility.
@@ -1250,7 +1242,7 @@ class Timedelta(_Timedelta):
12501242
return NaT
12511243

12521244
# make timedelta happy
1253-
td_base = _Timedelta.__new__(cls, microseconds=int(value) / 1000)
1245+
td_base = _Timedelta.__new__(cls, microseconds=int(value) // 1000)
12541246
td_base.value = value
12551247
td_base.is_populated = 0
12561248
return td_base

pandas/tests/scalar/timedelta/test_timedelta.py

+13
Original file line numberDiff line numberDiff line change
@@ -821,3 +821,16 @@ def test_resolution_deprecated(self):
821821
def test_truthiness(value, expected):
822822
# https://github.com/pandas-dev/pandas/issues/21484
823823
assert bool(value) is expected
824+
825+
826+
def test_timedelta_attribute_precision():
827+
# GH 31354
828+
td = Timedelta(1552211999999999872, unit="ns")
829+
result = td.days * 86400
830+
result += td.seconds
831+
result *= 1000000
832+
result += td.microseconds
833+
result *= 1000
834+
result += td.nanoseconds
835+
expected = td.value
836+
assert result == expected

pandas/tests/scalar/timestamp/test_timestamp.py

+1-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import calendar
44
from datetime import datetime, timedelta
5-
from distutils.version import LooseVersion
65
import locale
76
import unicodedata
87

@@ -1088,20 +1087,13 @@ def test_constructor_ambigous_dst():
10881087
assert result == expected
10891088

10901089

1091-
@pytest.mark.xfail(
1092-
LooseVersion(compat._optional._get_version(dateutil)) < LooseVersion("2.7.0"),
1093-
reason="dateutil moved to Timedelta.total_seconds() in 2.7.0",
1094-
)
10951090
@pytest.mark.parametrize("epoch", [1552211999999999872, 1552211999999999999])
10961091
def test_constructor_before_dst_switch(epoch):
10971092
# GH 31043
10981093
# Make sure that calling Timestamp constructor
10991094
# on time just before DST switch doesn't lead to
11001095
# nonexistent time or value change
1101-
# Works only with dateutil >= 2.7.0 as dateutil overrid
1102-
# pandas.Timedelta.total_seconds with
1103-
# datetime.timedelta.total_seconds before
1104-
ts = Timestamp(epoch, tz="dateutil/US/Pacific")
1096+
ts = Timestamp(epoch, tz="dateutil/America/Los_Angeles")
11051097
result = ts.tz.dst(ts)
11061098
expected = timedelta(seconds=0)
11071099
assert Timestamp(ts).value == epoch

0 commit comments

Comments
 (0)