Skip to content

Commit 3b2c8f6

Browse files
AlexKirkojreback
authored andcommitted
BUG: nonexistent Timestamp pre-summer/winter DST w/dateutil timezone (#31155)
1 parent e5227c4 commit 3b2c8f6

File tree

5 files changed

+34
-5
lines changed

5 files changed

+34
-5
lines changed

doc/source/whatsnew/v1.1.0.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ Categorical
129129

130130
Datetimelike
131131
^^^^^^^^^^^^
132+
132133
- Bug in :class:`Timestamp` where constructing :class:`Timestamp` from ambiguous epoch time and calling constructor again changed :meth:`Timestamp.value` property (:issue:`24329`)
133134
- :meth:`DatetimeArray.searchsorted`, :meth:`TimedeltaArray.searchsorted`, :meth:`PeriodArray.searchsorted` not recognizing non-pandas scalars and incorrectly raising ``ValueError`` instead of ``TypeError`` (:issue:`30950`)
134-
-
135+
- Bug in :class:`Timestamp` where constructing :class:`Timestamp` with dateutil timezone less than 128 nanoseconds before daylight saving time switch from winter to summer would result in nonexistent time (:issue:`31043`)
135136

136137
Timedelta
137138
^^^^^^^^^

pandas/_libs/tslibs/nattype.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ cdef class _NaT(datetime):
278278

279279
def total_seconds(self):
280280
"""
281-
Total duration of timedelta in seconds (to ns precision).
281+
Total duration of timedelta in seconds (to microsecond precision).
282282
"""
283283
# GH#10939
284284
return np.nan

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -861,9 +861,11 @@ cdef class _Timedelta(timedelta):
861861

862862
def total_seconds(self):
863863
"""
864-
Total duration of timedelta in seconds (to ns precision).
864+
Total duration of timedelta in seconds (to microsecond precision).
865865
"""
866-
return self.value / 1e9
866+
# GH 31043
867+
# Microseconds precision to avoid confusing tzinfo.utcoffset
868+
return (self.value - self.value % 1000) / 1e9
867869

868870
def view(self, dtype):
869871
"""

pandas/tests/indexes/datetimes/test_timezones.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Tests for DatetimeIndex timezone-related methods
33
"""
44
from datetime import date, datetime, time, timedelta, tzinfo
5+
from distutils.version import LooseVersion
56

67
import dateutil
78
from dateutil.tz import gettz, tzlocal
@@ -10,6 +11,7 @@
1011
import pytz
1112

1213
from pandas._libs.tslibs import conversion, timezones
14+
from pandas.compat._optional import _get_version
1315
import pandas.util._test_decorators as td
1416

1517
import pandas as pd
@@ -585,7 +587,10 @@ def test_dti_construction_ambiguous_endpoint(self, tz):
585587
"dateutil/US/Pacific",
586588
"shift_backward",
587589
"2019-03-10 01:00",
588-
marks=pytest.mark.xfail(reason="GH 24329"),
590+
marks=pytest.mark.xfail(
591+
LooseVersion(_get_version(dateutil)) < LooseVersion("2.7.0"),
592+
reason="GH 31043",
593+
),
589594
),
590595
["US/Pacific", timedelta(hours=1), "2019-03-10 03:00"],
591596
],

pandas/tests/scalar/timestamp/test_timestamp.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import calendar
44
from datetime import datetime, timedelta
5+
from distutils.version import LooseVersion
56
import locale
67
import unicodedata
78

@@ -1096,3 +1097,23 @@ def test_constructor_ambigous_dst():
10961097
expected = ts.value
10971098
result = Timestamp(ts).value
10981099
assert result == expected
1100+
1101+
1102+
@pytest.mark.xfail(
1103+
LooseVersion(compat._optional._get_version(dateutil)) < LooseVersion("2.7.0"),
1104+
reason="dateutil moved to Timedelta.total_seconds() in 2.7.0",
1105+
)
1106+
@pytest.mark.parametrize("epoch", [1552211999999999872, 1552211999999999999])
1107+
def test_constructor_before_dst_switch(epoch):
1108+
# GH 31043
1109+
# Make sure that calling Timestamp constructor
1110+
# on time just before DST switch doesn't lead to
1111+
# nonexistent time or value change
1112+
# Works only with dateutil >= 2.7.0 as dateutil overrid
1113+
# pandas.Timedelta.total_seconds with
1114+
# datetime.timedelta.total_seconds before
1115+
ts = Timestamp(epoch, tz="dateutil/US/Pacific")
1116+
result = ts.tz.dst(ts)
1117+
expected = timedelta(seconds=0)
1118+
assert Timestamp(ts).value == epoch
1119+
assert result == expected

0 commit comments

Comments
 (0)