Skip to content

Separate out _convert_datetime_to_tsobject #17715

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 7 commits into from
Oct 2, 2017
Merged
Changes from 6 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
165 changes: 114 additions & 51 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ class Timestamp(_Timestamp):
# reconstruct & check bounds
ts_input = datetime(dts.year, dts.month, dts.day, dts.hour, dts.min,
dts.sec, dts.us, tzinfo=_tzinfo)
ts = convert_to_tsobject(ts_input, _tzinfo, None, 0, 0)
ts = convert_datetime_to_tsobject(ts_input, _tzinfo)
value = ts.value + (dts.ps // 1000)
if value != NPY_NAT:
_check_dts_bounds(&dts)
Expand Down Expand Up @@ -1475,52 +1475,11 @@ cdef convert_to_tsobject(object ts, object tz, object unit,
obj.value = ts
pandas_datetime_to_datetimestruct(ts, PANDAS_FR_ns, &obj.dts)
elif PyDateTime_Check(ts):
if tz is not None:
# sort of a temporary hack
if ts.tzinfo is not None:
if (hasattr(tz, 'normalize') and
hasattr(ts.tzinfo, '_utcoffset')):
ts = tz.normalize(ts)
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo
else: #tzoffset
try:
tz = ts.astimezone(tz).tzinfo
except:
pass
obj.value = _pydatetime_to_dts(ts, &obj.dts)
ts_offset = get_utcoffset(ts.tzinfo, ts)
obj.value -= _delta_to_nanoseconds(ts_offset)
tz_offset = get_utcoffset(tz, ts)
obj.value += _delta_to_nanoseconds(tz_offset)
pandas_datetime_to_datetimestruct(obj.value,
PANDAS_FR_ns, &obj.dts)
obj.tzinfo = tz
elif not is_utc(tz):
ts = _localize_pydatetime(ts, tz)
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo
else:
# UTC
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = pytz.utc
else:
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo

if obj.tzinfo is not None and not is_utc(obj.tzinfo):
offset = get_utcoffset(obj.tzinfo, ts)
obj.value -= _delta_to_nanoseconds(offset)

if is_timestamp(ts):
obj.value += ts.nanosecond
obj.dts.ps = ts.nanosecond * 1000
_check_dts_bounds(&obj.dts)
return obj
return convert_datetime_to_tsobject(ts, tz)
elif PyDate_Check(ts):
# Keep the converter same as PyDateTime's
ts = datetime.combine(ts, datetime_time())
return convert_to_tsobject(ts, tz, None, 0, 0)
return convert_datetime_to_tsobject(ts, tz)
elif getattr(ts, '_typ', None) == 'period':
raise ValueError(
"Cannot convert Period to Timestamp "
Expand All @@ -1538,6 +1497,83 @@ cdef convert_to_tsobject(object ts, object tz, object unit,
return obj


cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
int32_t nanos=0):
"""
Convert a datetime (or Timestamp) input `ts`, along with optional timezone
object `tz` to a _TSObject.

The optional argument `nanos` allows for cases where datetime input
needs to be supplemented with higher-precision information.

Parameters
----------
ts : datetime or Timestamp
Value to be converted to _TSObject
tz : tzinfo or None
timezone for the timezone-aware output
nanos : int32_t, default is 0
nanoseconds supplement the precision of the datetime input ts

Returns
-------
obj : _TSObject
"""
cdef:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls pls always add doc-strings

_TSObject obj = _TSObject()

if tz is not None:
tz = maybe_get_tz(tz)

# sort of a temporary hack
if ts.tzinfo is not None:
if (hasattr(tz, 'normalize') and
hasattr(ts.tzinfo, '_utcoffset')):
ts = tz.normalize(ts)
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo
else:
# tzoffset
try:
tz = ts.astimezone(tz).tzinfo
except:
pass
obj.value = _pydatetime_to_dts(ts, &obj.dts)
ts_offset = get_utcoffset(ts.tzinfo, ts)
obj.value -= int(ts_offset.total_seconds() * 1e9)
tz_offset = get_utcoffset(tz, ts)
obj.value += int(tz_offset.total_seconds() * 1e9)
pandas_datetime_to_datetimestruct(obj.value,
PANDAS_FR_ns, &obj.dts)
obj.tzinfo = tz
elif not is_utc(tz):
ts = _localize_pydatetime(ts, tz)
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo
else:
# UTC
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = pytz.utc
else:
obj.value = _pydatetime_to_dts(ts, &obj.dts)
obj.tzinfo = ts.tzinfo

if obj.tzinfo is not None and not is_utc(obj.tzinfo):
offset = get_utcoffset(obj.tzinfo, ts)
obj.value -= int(offset.total_seconds() * 1e9)

if is_timestamp(ts):
obj.value += ts.nanosecond
obj.dts.ps = ts.nanosecond * 1000

if nanos:
obj.value += nanos
obj.dts.ps = nanos * 1000

_check_dts_bounds(&obj.dts)
return obj


cpdef convert_str_to_tsobject(object ts, object tz, object unit,
dayfirst=False, yearfirst=False):
""" ts must be a string """
Expand All @@ -1558,11 +1594,12 @@ cpdef convert_str_to_tsobject(object ts, object tz, object unit,
elif ts == 'now':
# Issue 9000, we short-circuit rather than going
# into np_datetime_strings which returns utc
ts = Timestamp.now(tz)
ts = datetime.now(tz)
elif ts == 'today':
# Issue 9000, we short-circuit rather than going
# into np_datetime_strings which returns a normalized datetime
ts = Timestamp.today(tz)
ts = datetime.now(tz)
# equiv: datetime.today().replace(tzinfo=tz)
else:
try:
_string_to_dts(ts, &obj.dts, &out_local, &out_tzoffset)
Expand All @@ -1576,8 +1613,8 @@ cpdef convert_str_to_tsobject(object ts, object tz, object unit,
_check_dts_bounds(&obj.dts)
return obj
else:
# Keep the converter same as PyDateTime's
ts = Timestamp(obj.value, tz=obj.tzinfo)
return _convert_tsobject_tz(obj, tz)

else:
ts = obj.value
if tz is not None:
Expand All @@ -1595,6 +1632,32 @@ cpdef convert_str_to_tsobject(object ts, object tz, object unit,
return convert_to_tsobject(ts, tz, unit, dayfirst, yearfirst)


cdef _TSObject _convert_tsobject_tz(_TSObject obj, object tz):
"""
Given a _TSObject with a FixedOffset tzinfo, convert to a new _TSObject
instance with the given timezone `tz` (or tz-naive if tz is None).

Parameters
----------
obj : _TSObject
tz : tzinfo or None

Returns
-------
obj : _TSObject
"""
# Keep the converter same as PyDateTime's
cdef:
datetime dtime

obj = convert_to_tsobject(obj.value, obj.tzinfo, None, 0, 0)
dtime = datetime(obj.dts.year, obj.dts.month, obj.dts.day,
obj.dts.hour, obj.dts.min, obj.dts.sec, obj.dts.us,
tz)
obj = convert_datetime_to_tsobject(dtime, tz, nanos=obj.dts.ps / 1000)
return obj


def _test_parse_iso8601(object ts):
"""
TESTING ONLY: Parse string into Timestamp using iso8601 parser. Used
Expand Down Expand Up @@ -1726,7 +1789,7 @@ def datetime_to_datetime64(ndarray[object] values):
else:
inferred_tz = get_timezone(val.tzinfo)

_ts = convert_to_tsobject(val, None, None, 0, 0)
_ts = convert_datetime_to_tsobject(val, None)
iresult[i] = _ts.value
_check_dts_bounds(&_ts.dts)
else:
Expand Down Expand Up @@ -2046,7 +2109,7 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
seen_datetime=1
if val.tzinfo is not None:
if utc_convert:
_ts = convert_to_tsobject(val, None, 'ns', 0, 0)
_ts = convert_datetime_to_tsobject(val, None)
iresult[i] = _ts.value
try:
_check_dts_bounds(&_ts.dts)
Expand Down Expand Up @@ -2155,7 +2218,7 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
raise TypeError("invalid string coercion to datetime")

try:
_ts = convert_to_tsobject(py_dt, None, None, 0, 0)
_ts = convert_datetime_to_tsobject(py_dt, None)
iresult[i] = _ts.value
except ValueError:
if is_coerce:
Expand Down