Skip to content

move and de-privatize _localize_pydatetime, move shift_day to liboffsets #21691

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
merged 5 commits into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -235,23 +235,6 @@ def _test_parse_iso8601(object ts):
return Timestamp(obj.value)


cpdef inline object _localize_pydatetime(object dt, object tz):
"""
Take a datetime/Timestamp in UTC and localizes to timezone tz.
"""
if tz is None:
return dt
elif isinstance(dt, Timestamp):
return dt.tz_localize(tz)
elif tz == 'UTC' or tz is UTC:
return UTC.localize(dt)
try:
# datetime.replace with pytz may be incorrect result
return tz.localize(dt)
except AttributeError:
return dt.replace(tzinfo=tz)


def format_array_from_datetime(ndarray[int64_t] values, object tz=None,
object format=None, object na_rep=None):
"""
Expand Down
2 changes: 2 additions & 0 deletions pandas/_libs/tslibs/conversion.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ cpdef int64_t pydt_to_i8(object pydt) except? -1
cdef maybe_datetimelike_to_i8(object val)

cdef int64_t tz_convert_utc_to_tzlocal(int64_t utc_val, tzinfo tz)

cpdef datetime localize_pydatetime(datetime dt, object tz)
29 changes: 28 additions & 1 deletion pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ cdef inline datetime _localize_pydatetime(datetime dt, tzinfo tz):
"""
Take a datetime/Timestamp in UTC and localizes to timezone tz.

NB: Unlike the version in tslib, this treats datetime and Timestamp objects
NB: Unlike the public version, this treats datetime and Timestamp objects
identically, i.e. discards nanos from Timestamps.
It also assumes that the `tz` input is not None.
"""
Expand All @@ -580,6 +580,33 @@ cdef inline datetime _localize_pydatetime(datetime dt, tzinfo tz):
# ----------------------------------------------------------------------
# Timezone Conversion

cpdef inline datetime localize_pydatetime(datetime dt, object tz):
"""
Take a datetime/Timestamp in UTC and localizes to timezone tz.

Parameters
----------
dt : datetime or Timestamp
tz : tzinfo, "UTC", or None

Returns
-------
localized : datetime or Timestamp
"""
if tz is None:
return dt
elif not PyDateTime_CheckExact(dt):
# i.e. is a Timestamp
return dt.tz_localize(tz)
elif tz == 'UTC' or tz is UTC:
return UTC.localize(dt)
try:
# datetime.replace with pytz may be incorrect result
return tz.localize(dt)
except AttributeError:
return dt.replace(tzinfo=tz)


cdef inline int64_t tz_convert_tzlocal_to_utc(int64_t val, tzinfo tz):
"""
Parameters
Expand Down
33 changes: 31 additions & 2 deletions pandas/_libs/tslibs/offsets.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ cimport cython
from cython cimport Py_ssize_t

import time
from cpython.datetime cimport datetime, timedelta, time as dt_time
from cpython.datetime cimport (PyDateTime_IMPORT, PyDateTime_CheckExact,
datetime, timedelta,
time as dt_time)
PyDateTime_IMPORT

from dateutil.relativedelta import relativedelta
from pytz import UTC

import numpy as np
cimport numpy as cnp
Expand All @@ -19,7 +23,7 @@ from util cimport is_string_object, is_integer_object

from ccalendar import MONTHS, DAYS
from ccalendar cimport get_days_in_month, dayofweek
from conversion cimport tz_convert_single, pydt_to_i8
from conversion cimport tz_convert_single, pydt_to_i8, localize_pydatetime
from frequencies cimport get_freq_code
from nattype cimport NPY_NAT
from np_datetime cimport (pandas_datetimestruct,
Expand Down Expand Up @@ -494,6 +498,31 @@ class BaseOffset(_BaseOffset):
# ----------------------------------------------------------------------
# RelativeDelta Arithmetic

cpdef datetime shift_day(datetime other, int days):
"""
Increment the datetime `other` by the given number of days, retaining
the time-portion of the datetime. For tz-naive datetimes this is
equivalent to adding a timedelta. For tz-aware datetimes it is similar to
dateutil's relativedelta.__add__, but handles pytz tzinfo objects.

Parameters
----------
other : datetime or Timestamp
days : int

Returns
-------
shifted: datetime or Timestamp
"""
if other.tzinfo is None:
return other + timedelta(days=days)

tz = other.tzinfo
naive = other.replace(tzinfo=None)
shifted = naive + timedelta(days=days)
return localize_pydatetime(shifted, tz)


cdef inline int year_add_months(pandas_datetimestruct dts, int months) nogil:
"""new year number after shifting pandas_datetimestruct number of months"""
return dts.year + (dts.month + months - 1) / 12
Expand Down
7 changes: 2 additions & 5 deletions pandas/tests/indexes/datetimes/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
DatetimeIndex, TimedeltaIndex,
date_range)
from pandas.core import ops
from pandas._libs import tslib
from pandas._libs.tslibs.conversion import localize_pydatetime
from pandas._libs.tslibs.offsets import shift_months


Expand Down Expand Up @@ -56,10 +56,7 @@ def test_dti_cmp_datetimelike(self, other, tz):
if isinstance(other, np.datetime64):
# no tzaware version available
return
elif isinstance(other, Timestamp):
other = other.tz_localize(dti.tzinfo)
else:
other = tslib._localize_pydatetime(other, dti.tzinfo)
other = localize_pydatetime(other, dti.tzinfo)

result = dti == other
expected = np.array([True, False])
Expand Down
9 changes: 4 additions & 5 deletions pandas/tests/indexes/datetimes/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import pandas.util._test_decorators as td

import pandas as pd
from pandas._libs import tslib
from pandas._libs.tslibs import timezones
from pandas._libs.tslibs import timezones, conversion
from pandas.compat import lrange, zip, PY3
from pandas import (DatetimeIndex, date_range, bdate_range,
Timestamp, isna, to_datetime, Index)
Expand Down Expand Up @@ -911,12 +910,12 @@ def test_with_tz(self, tz):
central = dr.tz_convert(tz)
assert central.tz is tz
naive = central[0].to_pydatetime().replace(tzinfo=None)
comp = tslib._localize_pydatetime(naive, tz).tzinfo
comp = conversion.localize_pydatetime(naive, tz).tzinfo
assert central[0].tz is comp

# compare vs a localized tz
naive = dr[0].to_pydatetime().replace(tzinfo=None)
comp = tslib._localize_pydatetime(naive, tz).tzinfo
comp = conversion.localize_pydatetime(naive, tz).tzinfo
assert central[0].tz is comp

# datetimes with tzinfo set
Expand Down Expand Up @@ -946,7 +945,7 @@ def test_dti_convert_tz_aware_datetime_datetime(self, tz):
dates = [datetime(2000, 1, 1), datetime(2000, 1, 2),
datetime(2000, 1, 3)]

dates_aware = [tslib._localize_pydatetime(x, tz) for x in dates]
dates_aware = [conversion.localize_pydatetime(x, tz) for x in dates]
result = DatetimeIndex(dates_aware)
assert timezones.tz_compare(result.tz, tz)

Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/scalar/timestamp/test_unary_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import pandas.util._test_decorators as td

from pandas.compat import PY3
from pandas._libs import tslib
from pandas._libs.tslibs import conversion
from pandas._libs.tslibs.frequencies import _INVALID_FREQ_ERROR
from pandas import Timestamp, NaT

Expand Down Expand Up @@ -242,7 +242,7 @@ def test_replace_across_dst(self, tz, normalize):
# GH#18319 check that 1) timezone is correctly normalized and
# 2) that hour is not incorrectly changed by this normalization
ts_naive = Timestamp('2017-12-03 16:03:30')
ts_aware = tslib._localize_pydatetime(ts_naive, tz)
ts_aware = conversion.localize_pydatetime(ts_naive, tz)

# Preliminary sanity-check
assert ts_aware == normalize(ts_aware)
Expand Down
5 changes: 2 additions & 3 deletions pandas/tests/series/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from dateutil.tz import tzoffset

import pandas.util.testing as tm
from pandas._libs import tslib
from pandas._libs.tslibs import timezones
from pandas._libs.tslibs import timezones, conversion
from pandas.compat import lrange
from pandas.core.indexes.datetimes import date_range
from pandas import Series, Timestamp, DatetimeIndex, Index
Expand Down Expand Up @@ -298,7 +297,7 @@ def test_getitem_pydatetime_tz(self, tzstr):
time_pandas = Timestamp('2012-12-24 17:00', tz=tzstr)

dt = datetime(2012, 12, 24, 17, 0)
time_datetime = tslib._localize_pydatetime(dt, tz)
time_datetime = conversion.localize_pydatetime(dt, tz)
assert ts[time_pandas] == ts[time_datetime]

def test_series_truncate_datetimeindex_tz(self):
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/tseries/offsets/test_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pandas.compat.numpy import np_datetime64_compat

from pandas.core.series import Series
from pandas._libs.tslibs import conversion
from pandas._libs.tslibs.frequencies import (get_freq_code, get_freq_str,
_INVALID_FREQ_ERROR)
from pandas.tseries.frequencies import _offset_map, get_offset
Expand Down Expand Up @@ -319,7 +320,7 @@ def _check_offsetfunc_works(self, offset, funcname, dt, expected,
for tz in self.timezones:
expected_localize = expected.tz_localize(tz)
tz_obj = timezones.maybe_get_tz(tz)
dt_tz = tslib._localize_pydatetime(dt, tz_obj)
dt_tz = conversion.localize_pydatetime(dt, tz_obj)

result = func(dt_tz)
assert isinstance(result, Timestamp)
Expand Down
11 changes: 5 additions & 6 deletions pandas/tests/tslibs/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import pytz
import dateutil.tz

from pandas._libs import tslib
from pandas._libs.tslibs import timezones
from pandas._libs.tslibs import timezones, conversion
from pandas import Timestamp


Expand Down Expand Up @@ -51,17 +50,17 @@ def test_infer_tz(eastern, localize):
end = localize(eastern, end_naive)

assert (timezones.infer_tzinfo(start, end) is
tslib._localize_pydatetime(start_naive, eastern).tzinfo)
conversion.localize_pydatetime(start_naive, eastern).tzinfo)
assert (timezones.infer_tzinfo(start, None) is
tslib._localize_pydatetime(start_naive, eastern).tzinfo)
conversion.localize_pydatetime(start_naive, eastern).tzinfo)
assert (timezones.infer_tzinfo(None, end) is
tslib._localize_pydatetime(end_naive, eastern).tzinfo)
conversion.localize_pydatetime(end_naive, eastern).tzinfo)

start = utc.localize(start_naive)
end = utc.localize(end_naive)
assert timezones.infer_tzinfo(start, end) is utc

end = tslib._localize_pydatetime(end_naive, eastern)
end = conversion.localize_pydatetime(end_naive, eastern)
with pytest.raises(Exception):
timezones.infer_tzinfo(start, end)
with pytest.raises(Exception):
Expand Down
Loading