-
-
Notifications
You must be signed in to change notification settings - Fork 18.6k
BUG: support ambiguous=infer with ZoneInfo #49700
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
Merged
mroeschke
merged 7 commits into
pandas-dev:main
from
jbrockmendel:enh-ambiguous-zoneinfo
Nov 23, 2022
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
f1a4d5f
BUG: rendering dt64tz values with non-pytz
jbrockmendel dff06c2
GH ref
jbrockmendel 211b167
py38 compat
jbrockmendel 5cdbb33
BUG: support ambiguous=infer with ZoneInfo
jbrockmendel be568e2
add type declaration
jbrockmendel 6bb724e
no-tzdata compat
jbrockmendel 3a2e037
Merge branch 'main' into enh-ambiguous-zoneinfo
jbrockmendel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,13 +36,15 @@ from pandas._libs.tslibs.np_datetime cimport ( | |
NPY_DATETIMEUNIT, | ||
npy_datetimestruct, | ||
pandas_datetime_to_datetimestruct, | ||
pydatetime_to_dt64, | ||
) | ||
from pandas._libs.tslibs.timezones cimport ( | ||
get_dst_info, | ||
is_fixed_offset, | ||
is_tzlocal, | ||
is_utc, | ||
is_zoneinfo, | ||
utc_stdlib, | ||
) | ||
|
||
|
||
|
@@ -154,7 +156,7 @@ cdef int64_t tz_localize_to_utc_single( | |
# TODO: test with non-nano | ||
return val | ||
|
||
elif is_tzlocal(tz) or is_zoneinfo(tz): | ||
elif is_tzlocal(tz): | ||
return val - _tz_localize_using_tzinfo_api(val, tz, to_utc=True, creso=creso) | ||
|
||
elif is_fixed_offset(tz): | ||
|
@@ -242,29 +244,6 @@ timedelta-like} | |
if info.use_utc: | ||
return vals.copy() | ||
|
||
result = cnp.PyArray_EMPTY(vals.ndim, vals.shape, cnp.NPY_INT64, 0) | ||
|
||
if info.use_tzlocal: | ||
for i in range(n): | ||
v = vals[i] | ||
if v == NPY_NAT: | ||
result[i] = NPY_NAT | ||
else: | ||
result[i] = v - _tz_localize_using_tzinfo_api( | ||
v, tz, to_utc=True, creso=creso | ||
) | ||
return result.base # to return underlying ndarray | ||
|
||
elif info.use_fixed: | ||
delta = info.delta | ||
for i in range(n): | ||
v = vals[i] | ||
if v == NPY_NAT: | ||
result[i] = NPY_NAT | ||
else: | ||
result[i] = v - delta | ||
return result.base # to return underlying ndarray | ||
|
||
# silence false-positive compiler warning | ||
ambiguous_array = np.empty(0, dtype=bool) | ||
if isinstance(ambiguous, str): | ||
|
@@ -299,11 +278,39 @@ timedelta-like} | |
"shift_backwards} or a timedelta object") | ||
raise ValueError(msg) | ||
|
||
result = cnp.PyArray_EMPTY(vals.ndim, vals.shape, cnp.NPY_INT64, 0) | ||
|
||
if info.use_tzlocal and not is_zoneinfo(tz): | ||
for i in range(n): | ||
v = vals[i] | ||
if v == NPY_NAT: | ||
result[i] = NPY_NAT | ||
else: | ||
result[i] = v - _tz_localize_using_tzinfo_api( | ||
v, tz, to_utc=True, creso=creso | ||
) | ||
return result.base # to return underlying ndarray | ||
|
||
elif info.use_fixed: | ||
delta = info.delta | ||
for i in range(n): | ||
v = vals[i] | ||
if v == NPY_NAT: | ||
result[i] = NPY_NAT | ||
else: | ||
result[i] = v - delta | ||
return result.base # to return underlying ndarray | ||
|
||
# Determine whether each date lies left of the DST transition (store in | ||
# result_a) or right of the DST transition (store in result_b) | ||
result_a, result_b =_get_utc_bounds( | ||
vals, info.tdata, info.ntrans, info.deltas, creso=creso | ||
) | ||
if is_zoneinfo(tz): | ||
result_a, result_b =_get_utc_bounds_zoneinfo( | ||
vals, tz, creso=creso | ||
) | ||
else: | ||
result_a, result_b =_get_utc_bounds( | ||
vals, info.tdata, info.ntrans, info.deltas, creso=creso | ||
) | ||
|
||
# silence false-positive compiler warning | ||
dst_hours = np.empty(0, dtype=np.int64) | ||
|
@@ -391,8 +398,7 @@ timedelta-like} | |
return result.base # .base to get underlying ndarray | ||
|
||
|
||
cdef inline Py_ssize_t bisect_right_i8(int64_t *data, | ||
int64_t val, Py_ssize_t n): | ||
cdef inline Py_ssize_t bisect_right_i8(int64_t *data, int64_t val, Py_ssize_t n): | ||
# Caller is responsible for checking n > 0 | ||
# This looks very similar to local_search_right in the ndarray.searchsorted | ||
# implementation. | ||
|
@@ -483,6 +489,72 @@ cdef _get_utc_bounds( | |
return result_a, result_b | ||
|
||
|
||
cdef _get_utc_bounds_zoneinfo(ndarray vals, tz, NPY_DATETIMEUNIT creso): | ||
""" | ||
For each point in 'vals', find the UTC time that it corresponds to if | ||
with fold=0 and fold=1. In non-ambiguous cases, these will match. | ||
|
||
Parameters | ||
---------- | ||
vals : ndarray[int64_t] | ||
tz : ZoneInfo | ||
creso : NPY_DATETIMEUNIT | ||
|
||
Returns | ||
------- | ||
ndarray[int64_t] | ||
ndarray[int64_t] | ||
""" | ||
cdef: | ||
Py_ssize_t i, n = vals.size | ||
npy_datetimestruct dts | ||
datetime dt, rt, left, right, aware, as_utc | ||
int64_t val, pps = periods_per_second(creso) | ||
ndarray result_a, result_b | ||
|
||
result_a = cnp.PyArray_EMPTY(vals.ndim, vals.shape, cnp.NPY_INT64, 0) | ||
result_b = cnp.PyArray_EMPTY(vals.ndim, vals.shape, cnp.NPY_INT64, 0) | ||
|
||
for i in range(n): | ||
val = vals[i] | ||
if val == NPY_NAT: | ||
result_a[i] = NPY_NAT | ||
result_b[i] = NPY_NAT | ||
continue | ||
|
||
pandas_datetime_to_datetimestruct(val, creso, &dts) | ||
# casting to pydatetime drops nanoseconds etc, which we will | ||
# need to re-add later as 'extra'' | ||
extra = (dts.ps // 1000) * (pps // 1_000_000_000) | ||
|
||
dt = datetime_new(dts.year, dts.month, dts.day, dts.hour, | ||
dts.min, dts.sec, dts.us, None) | ||
|
||
aware = dt.replace(tzinfo=tz) | ||
as_utc = aware.astimezone(utc_stdlib) | ||
rt = as_utc.astimezone(tz) | ||
if aware != rt: | ||
# AFAICT this means that 'aware' is non-existent | ||
# TODO: better way to check this? | ||
# mail.python.org/archives/list/[email protected]/ | ||
# thread/57Y3IQAASJOKHX4D27W463XTZIS2NR3M/ | ||
result_a[i] = NPY_NAT | ||
else: | ||
left = as_utc.replace(tzinfo=None) | ||
result_a[i] = pydatetime_to_dt64(left, &dts, creso) + extra | ||
|
||
aware = dt.replace(fold=1, tzinfo=tz) | ||
as_utc = aware.astimezone(utc_stdlib) | ||
rt = as_utc.astimezone(tz) | ||
if aware != rt: | ||
result_b[i] = NPY_NAT | ||
else: | ||
right = as_utc.replace(tzinfo=None) | ||
result_b[i] = pydatetime_to_dt64(right, &dts, creso) + extra | ||
|
||
return result_a, result_b | ||
|
||
|
||
@cython.boundscheck(False) | ||
cdef ndarray[int64_t] _get_dst_hours( | ||
# vals, creso only needed here to potential render an exception message | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.