-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
API/BUG: Enforce "normalized" pytz timezones for DatetimeIndex #20510
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
b878540
0b0fb83
8316501
0bb1b13
4d6f0d1
4a202b0
29560c4
87cacf8
e8990fc
b1f2724
c1241f9
9833f01
43fab89
f1a5ca7
bba5da5
8b397d4
464a91b
c1db598
bf1ec9e
81ccb21
12f697b
867ef19
360c295
67a29d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -314,3 +314,41 @@ cpdef bint tz_compare(object start, object end): | |
""" | ||
# GH 18523 | ||
return get_timezone(start) == get_timezone(end) | ||
|
||
|
||
cpdef tz_standardize(object tz): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is called within a python file ( |
||
""" | ||
If the passed tz is a pytz timezone object, "normalize" it to the a | ||
consistent version | ||
|
||
Parameters | ||
---------- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add some examples |
||
tz : tz object | ||
|
||
Returns: | ||
------- | ||
tz object | ||
|
||
Examples: | ||
-------- | ||
>>> tz | ||
<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD> | ||
|
||
>>> tz_standardize(tz) | ||
<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD> | ||
|
||
>>> tz | ||
<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD> | ||
|
||
>>> tz_standardize(tz) | ||
<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD> | ||
|
||
>>> tz | ||
dateutil.tz.tz.tzutc | ||
|
||
>>> tz_standardize(tz) | ||
dateutil.tz.tz.tzutc | ||
""" | ||
if treat_tz_as_pytz(tz): | ||
return pytz.timezone(str(tz)) | ||
return tz |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1005,7 +1005,7 @@ def shift(self, n, freq=None): | |
result = self + offset | ||
|
||
if hasattr(self, 'tz'): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I generally avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah this impl is shared by DTI and TDI so this could be restructured a bit. Please file an issue . |
||
result.tz = self.tz | ||
result._tz = self.tz | ||
|
||
return result | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -511,13 +511,7 @@ def _generate(cls, start, end, periods, name, offset, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
'different timezones') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
inferred_tz = timezones.maybe_get_tz(inferred_tz) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# these may need to be localized | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tz = timezones.maybe_get_tz(tz) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if tz is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
date = start or end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if date.tzinfo is not None and hasattr(tz, 'localize'): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tz = tz.localize(date.replace(tzinfo=None)).tzinfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if tz is not None and inferred_tz is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not timezones.tz_compare(inferred_tz, tz): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -654,7 +648,7 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result._data = values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.name = name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.offset = freq | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.tz = timezones.maybe_get_tz(tz) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result._tz = timezones.maybe_get_tz(tz) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result._reset_identity() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -684,6 +678,17 @@ def _values(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return self.values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def tz(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# GH 18595 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i believe we now have this in setter / getter versions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, the only caching decorator I could find was implemented here: pandas/pandas/_libs/properties.pyx Lines 9 to 44 in c7af4ae
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return timezones.tz_standardize(self._tz) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@tz.setter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def tz(self, value): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# GH 3746 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add the same to Timestamp itself. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
raise AttributeError("Cannot directly set timezone. Use tz_localize() " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"or tz_convert() as appropriate") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def tzinfo(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -754,7 +759,7 @@ def _cached_range(cls, start=None, end=None, periods=None, offset=None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cachedRange = DatetimeIndex._simple_new(arr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cachedRange.offset = offset | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cachedRange.tz = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cachedRange._tz = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cachedRange.name = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
drc[offset] = cachedRange | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -831,7 +836,7 @@ def __setstate__(self, state): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.name = own_state[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.offset = own_state[1] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.tz = own_state[2] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self._tz = own_state[2] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# provide numpy < 1.7 compat | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if nd_state[2] == 'M8[us]': | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -1175,7 +1180,7 @@ def union(self, other): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result = Index.union(this, other) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if isinstance(result, DatetimeIndex): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.tz = this.tz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result._tz = this.tz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (result.freq is None and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(this.freq is not None or other.freq is not None)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result.offset = to_offset(result.inferred_freq) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -1223,7 +1228,7 @@ def union_many(self, others): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tz = this.tz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this = Index.union(this, other) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if isinstance(this, DatetimeIndex): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.tz = tz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this._tz = tz | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if this.freq is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.offset = to_offset(this.inferred_freq) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -441,6 +441,34 @@ def test_000constructor_resolution(self): | |
|
||
assert idx.nanosecond[0] == t1.nanosecond | ||
|
||
def test_disallow_setting_tz(self): | ||
# GH 3746 | ||
dti = DatetimeIndex(['2010'], tz='UTC') | ||
with pytest.raises(AttributeError): | ||
dti.tz = pytz.timezone('US/Pacific') | ||
|
||
@pytest.mark.parametrize('tz', [ | ||
None, 'America/Los_Angeles', pytz.timezone('America/Los_Angeles'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a similar battery of these tests to Timestamp as well (disabled setting already is disabled, but see if we have a test for it). |
||
Timestamp('2000', tz='America/Los_Angeles').tz]) | ||
def test_constructor_start_end_with_tz(self, tz): | ||
# GH 18595 | ||
start = Timestamp('2013-01-01 06:00:00', tz='America/Los_Angeles') | ||
end = Timestamp('2013-01-02 06:00:00', tz='America/Los_Angeles') | ||
result = DatetimeIndex(freq='D', start=start, end=end, tz=tz) | ||
expected = DatetimeIndex(['2013-01-01 06:00:00', | ||
'2013-01-02 06:00:00'], | ||
tz='America/Los_Angeles') | ||
tm.assert_index_equal(result, expected) | ||
# Especially assert that the timezone is consistent for pytz | ||
assert pytz.timezone('America/Los_Angeles') is result.tz | ||
|
||
@pytest.mark.parametrize('tz', ['US/Pacific', 'US/Eastern', 'Asia/Tokyo']) | ||
def test_constructor_with_non_normalized_pytz(self, tz): | ||
# GH 18595 | ||
non_norm_tz = Timestamp('2010', tz=tz).tz | ||
result = DatetimeIndex(['2010'], tz=non_norm_tz) | ||
assert pytz.timezone(tz) is result.tz | ||
|
||
|
||
class TestTimeSeries(object): | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you put here a more informative comment instead of referring to the issue (if it is needed of course)?
I think the only reason to have this is to have a more informative error message?