Skip to content

Commit

Permalink
ENH: Allow Timestamp to accept Nanosecond argument (#19889)
Browse files Browse the repository at this point in the history
  • Loading branch information
mroeschke authored and jreback committed Mar 1, 2018
1 parent 4a27697 commit 52559f5
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 9 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ Datetimelike API Changes
- Subtraction of :class:`Series` with timezone-aware ``dtype='datetime64[ns]'`` with mis-matched timezones will raise ``TypeError`` instead of ``ValueError`` (:issue:`18817`)
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
- For :class:`DatetimeIndex` and :class:`TimedeltaIndex` with ``freq=None``, addition or subtraction of integer-dtyped array or ``Index`` will raise ``NullFrequencyError`` instead of ``TypeError`` (:issue:`19895`)
- :class:`Timestamp` constructor now accepts a `nanosecond` keyword or positional argument (:issue:`18898`)

.. _whatsnew_0230.api.other:

Expand Down
3 changes: 2 additions & 1 deletion pandas/_libs/tslibs/conversion.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ cdef class _TSObject:


cdef convert_to_tsobject(object ts, object tz, object unit,
bint dayfirst, bint yearfirst)
bint dayfirst, bint yearfirst,
int32_t nanos=*)

cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
int32_t nanos=*)
Expand Down
4 changes: 2 additions & 2 deletions pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ cpdef int64_t pydt_to_i8(object pydt) except? -1:


cdef convert_to_tsobject(object ts, object tz, object unit,
bint dayfirst, bint yearfirst):
bint dayfirst, bint yearfirst, int32_t nanos=0):
"""
Extract datetime and int64 from any of:
- np.int64 (with unit providing a possible modifier)
Expand Down Expand Up @@ -297,7 +297,7 @@ cdef convert_to_tsobject(object ts, object tz, object unit,
obj.value = ts
dt64_to_dtstruct(ts, &obj.dts)
elif PyDateTime_Check(ts):
return convert_datetime_to_tsobject(ts, tz)
return convert_datetime_to_tsobject(ts, tz, nanos)
elif PyDate_Check(ts):
# Keep the converter same as PyDateTime's
ts = datetime.combine(ts, datetime_time())
Expand Down
25 changes: 19 additions & 6 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ cdef class _Timestamp(datetime):
cdef readonly:
int64_t value, nanosecond
object freq # frequency reference
list _date_attributes

def __hash__(_Timestamp self):
if self.nanosecond:
Expand Down Expand Up @@ -425,6 +426,8 @@ class Timestamp(_Timestamp):
.. versionadded:: 0.19.0
hour, minute, second, microsecond : int, optional, default 0
.. versionadded:: 0.19.0
nanosecond : int, optional, default 0
.. versionadded:: 0.23.0
tzinfo : datetime.tzinfo, optional, default None
.. versionadded:: 0.19.0
Expand Down Expand Up @@ -556,7 +559,7 @@ class Timestamp(_Timestamp):
object freq=None, tz=None, unit=None,
year=None, month=None, day=None,
hour=None, minute=None, second=None, microsecond=None,
tzinfo=None):
nanosecond=None, tzinfo=None):
# The parameter list folds together legacy parameter names (the first
# four) and positional and keyword parameter names from pydatetime.
#
Expand All @@ -580,6 +583,9 @@ class Timestamp(_Timestamp):

cdef _TSObject ts

_date_attributes = [year, month, day, hour, minute, second,
microsecond, nanosecond]

if tzinfo is not None:
if not PyTZInfo_Check(tzinfo):
# tzinfo must be a datetime.tzinfo object, GH#17690
Expand All @@ -588,28 +594,35 @@ class Timestamp(_Timestamp):
elif tz is not None:
raise ValueError('Can provide at most one of tz, tzinfo')

if ts_input is _no_input:
if is_string_object(ts_input):
# User passed a date string to parse.
# Check that the user didn't also pass a date attribute kwarg.
if any(arg is not None for arg in _date_attributes):
raise ValueError('Cannot pass a date attribute keyword '
'argument when passing a date string')

elif ts_input is _no_input:
# User passed keyword arguments.
if tz is None:
# Handle the case where the user passes `tz` and not `tzinfo`
tz = tzinfo
return Timestamp(datetime(year, month, day, hour or 0,
minute or 0, second or 0,
microsecond or 0, tzinfo),
tz=tz)
nanosecond=nanosecond, tz=tz)
elif is_integer_object(freq):
# User passed positional arguments:
# Timestamp(year, month, day[, hour[, minute[, second[,
# microsecond[, tzinfo]]]]])
# microsecond[, nanosecond[, tzinfo]]]]]])
return Timestamp(datetime(ts_input, freq, tz, unit or 0,
year or 0, month or 0, day or 0,
hour), tz=hour)
minute), nanosecond=hour, tz=minute)

if tzinfo is not None:
# User passed tzinfo instead of tz; avoid silently ignoring
tz, tzinfo = tzinfo, None

ts = convert_to_tsobject(ts_input, tz, unit, 0, 0)
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0, nanosecond or 0)

if ts.value == NPY_NAT:
return NaT
Expand Down
21 changes: 21 additions & 0 deletions pandas/tests/scalar/timestamp/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,27 @@ def test_constructor_fromordinal(self):
ts = Timestamp.fromordinal(dt_tz.toordinal(), tz='US/Eastern')
assert ts.to_pydatetime() == dt_tz

@pytest.mark.parametrize('result', [
Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), nanosecond=1),
Timestamp(year=2000, month=1, day=2, hour=3, minute=4, second=5,
microsecond=6, nanosecond=1),
Timestamp(year=2000, month=1, day=2, hour=3, minute=4, second=5,
microsecond=6, nanosecond=1, tz='UTC'),
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, None),
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, pytz.UTC)])
def test_constructor_nanosecond(self, result):
# GH 18898
expected = Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), tz=result.tz)
expected = expected + Timedelta(nanoseconds=1)
assert result == expected

@pytest.mark.parametrize('arg', ['year', 'month', 'day', 'hour', 'minute',
'second', 'microsecond', 'nanosecond'])
def test_invalid_date_kwarg_with_string_input(self, arg):
kwarg = {arg: 1}
with pytest.raises(ValueError):
Timestamp('2010-10-10 12:59:59.999999999', **kwarg)

def test_out_of_bounds_value(self):
one_us = np.timedelta64(1).astype('timedelta64[us]')

Expand Down

0 comments on commit 52559f5

Please sign in to comment.