forked from pandas-dev/pandas
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_dst.py
256 lines (227 loc) · 8.37 KB
/
test_dst.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
"""
Tests for DateOffset additions over Daylight Savings Time
"""
from datetime import timedelta
import pytest
import pytz
from pandas._libs.tslibs import Timestamp
from pandas._libs.tslibs.offsets import (
BMonthBegin,
BMonthEnd,
BQuarterBegin,
BQuarterEnd,
BYearBegin,
BYearEnd,
CBMonthBegin,
CBMonthEnd,
CustomBusinessDay,
DateOffset,
Day,
MonthBegin,
MonthEnd,
QuarterBegin,
QuarterEnd,
SemiMonthBegin,
SemiMonthEnd,
Week,
YearBegin,
YearEnd,
)
import pandas as pd
import pandas._testing as tm
from pandas.tests.tseries.offsets.test_offsets import get_utc_offset_hours
from pandas.util.version import Version
# error: Module has no attribute "__version__"
pytz_version = Version(pytz.__version__) # type: ignore[attr-defined]
class TestDST:
# one microsecond before the DST transition
ts_pre_fallback = "2013-11-03 01:59:59.999999"
ts_pre_springfwd = "2013-03-10 01:59:59.999999"
# test both basic names and dateutil timezones
timezone_utc_offsets = {
"US/Eastern": {"utc_offset_daylight": -4, "utc_offset_standard": -5},
"dateutil/US/Pacific": {"utc_offset_daylight": -7, "utc_offset_standard": -8},
}
valid_date_offsets_singular = [
"weekday",
"day",
"hour",
"minute",
"second",
"microsecond",
]
valid_date_offsets_plural = [
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
]
def _test_all_offsets(self, n, **kwds):
valid_offsets = (
self.valid_date_offsets_plural
if n > 1
else self.valid_date_offsets_singular
)
for name in valid_offsets:
self._test_offset(offset_name=name, offset_n=n, **kwds)
def _test_offset(self, offset_name, offset_n, tstart, expected_utc_offset):
offset = DateOffset(**{offset_name: offset_n})
t = tstart + offset
if expected_utc_offset is not None:
assert get_utc_offset_hours(t) == expected_utc_offset
if offset_name == "weeks":
# dates should match
assert t.date() == timedelta(days=7 * offset.kwds["weeks"]) + tstart.date()
# expect the same day of week, hour of day, minute, second, ...
assert (
t.dayofweek == tstart.dayofweek
and t.hour == tstart.hour
and t.minute == tstart.minute
and t.second == tstart.second
)
elif offset_name == "days":
# dates should match
assert timedelta(offset.kwds["days"]) + tstart.date() == t.date()
# expect the same hour of day, minute, second, ...
assert (
t.hour == tstart.hour
and t.minute == tstart.minute
and t.second == tstart.second
)
elif offset_name in self.valid_date_offsets_singular:
# expect the singular offset value to match between tstart and t
datepart_offset = getattr(
t, offset_name if offset_name != "weekday" else "dayofweek"
)
assert datepart_offset == offset.kwds[offset_name]
else:
# the offset should be the same as if it was done in UTC
assert t == (tstart.tz_convert("UTC") + offset).tz_convert("US/Pacific")
def _make_timestamp(self, string, hrs_offset, tz):
if hrs_offset >= 0:
offset_string = f"{hrs_offset:02d}00"
else:
offset_string = f"-{(hrs_offset * -1):02}00"
return Timestamp(string + offset_string).tz_convert(tz)
def test_springforward_plural(self):
# test moving from standard to daylight savings
for tz, utc_offsets in self.timezone_utc_offsets.items():
hrs_pre = utc_offsets["utc_offset_standard"]
hrs_post = utc_offsets["utc_offset_daylight"]
self._test_all_offsets(
n=3,
tstart=self._make_timestamp(self.ts_pre_springfwd, hrs_pre, tz),
expected_utc_offset=hrs_post,
)
def test_fallback_singular(self):
# in the case of singular offsets, we don't necessarily know which utc
# offset the new Timestamp will wind up in (the tz for 1 month may be
# different from 1 second) so we don't specify an expected_utc_offset
for tz, utc_offsets in self.timezone_utc_offsets.items():
hrs_pre = utc_offsets["utc_offset_standard"]
self._test_all_offsets(
n=1,
tstart=self._make_timestamp(self.ts_pre_fallback, hrs_pre, tz),
expected_utc_offset=None,
)
def test_springforward_singular(self):
for tz, utc_offsets in self.timezone_utc_offsets.items():
hrs_pre = utc_offsets["utc_offset_standard"]
self._test_all_offsets(
n=1,
tstart=self._make_timestamp(self.ts_pre_springfwd, hrs_pre, tz),
expected_utc_offset=None,
)
offset_classes = {
MonthBegin: ["11/2/2012", "12/1/2012"],
MonthEnd: ["11/2/2012", "11/30/2012"],
BMonthBegin: ["11/2/2012", "12/3/2012"],
BMonthEnd: ["11/2/2012", "11/30/2012"],
CBMonthBegin: ["11/2/2012", "12/3/2012"],
CBMonthEnd: ["11/2/2012", "11/30/2012"],
SemiMonthBegin: ["11/2/2012", "11/15/2012"],
SemiMonthEnd: ["11/2/2012", "11/15/2012"],
Week: ["11/2/2012", "11/9/2012"],
YearBegin: ["11/2/2012", "1/1/2013"],
YearEnd: ["11/2/2012", "12/31/2012"],
BYearBegin: ["11/2/2012", "1/1/2013"],
BYearEnd: ["11/2/2012", "12/31/2012"],
QuarterBegin: ["11/2/2012", "12/1/2012"],
QuarterEnd: ["11/2/2012", "12/31/2012"],
BQuarterBegin: ["11/2/2012", "12/3/2012"],
BQuarterEnd: ["11/2/2012", "12/31/2012"],
Day: ["11/4/2012", "11/4/2012 23:00"],
}.items()
@pytest.mark.parametrize("tup", offset_classes)
def test_all_offset_classes(self, tup):
offset, test_values = tup
first = Timestamp(test_values[0], tz="US/Eastern") + offset()
second = Timestamp(test_values[1], tz="US/Eastern")
assert first == second
@pytest.mark.parametrize(
"original_dt, target_dt, offset, tz",
[
pytest.param(
Timestamp("1900-01-01"),
Timestamp("1905-07-01"),
MonthBegin(66),
"Africa/Kinshasa",
marks=pytest.mark.xfail(
pytz_version < Version("2020.5") or pytz_version == Version("2022.2"),
reason="GH#41906: pytz utc transition dates changed",
),
),
(
Timestamp("2021-10-01 01:15"),
Timestamp("2021-10-31 01:15"),
MonthEnd(1),
"Europe/London",
),
(
Timestamp("2010-12-05 02:59"),
Timestamp("2010-10-31 02:59"),
SemiMonthEnd(-3),
"Europe/Paris",
),
(
Timestamp("2021-10-31 01:20"),
Timestamp("2021-11-07 01:20"),
CustomBusinessDay(2, weekmask="Sun Mon"),
"US/Eastern",
),
(
Timestamp("2020-04-03 01:30"),
Timestamp("2020-11-01 01:30"),
YearBegin(1, month=11),
"America/Chicago",
),
],
)
def test_nontick_offset_with_ambiguous_time_error(original_dt, target_dt, offset, tz):
# .apply for non-Tick offsets throws AmbiguousTimeError when the target dt
# is dst-ambiguous
localized_dt = original_dt.tz_localize(tz)
msg = f"Cannot infer dst time from {target_dt}, try using the 'ambiguous' argument"
with pytest.raises(pytz.AmbiguousTimeError, match=msg):
localized_dt + offset
def test_series_dst_addition():
# GH#43784
startdates = pd.Series(
[
Timestamp("2020-10-25", tz="Europe/Berlin"),
Timestamp("2017-03-12", tz="US/Pacific"),
]
)
offset1 = DateOffset(hours=3)
offset2 = DateOffset(days=1)
expected1 = pd.Series(
[Timestamp("2020-10-25 02:00:00+01:00"), Timestamp("2017-03-12 04:00:00-07:00")]
)
expected2 = pd.Series(
[Timestamp("2020-10-26 00:00:00+01:00"), Timestamp("2017-03-13 00:00:00-07:00")]
)
tm.assert_series_equal((startdates + offset1), expected1)
tm.assert_series_equal((startdates + offset2), expected2)