Skip to content

Commit 52559f5

Browse files
mroeschkejreback
authored andcommitted
ENH: Allow Timestamp to accept Nanosecond argument (#19889)
1 parent 4a27697 commit 52559f5

File tree

5 files changed

+45
-9
lines changed

5 files changed

+45
-9
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ Datetimelike API Changes
618618
- Subtraction of :class:`Series` with timezone-aware ``dtype='datetime64[ns]'`` with mis-matched timezones will raise ``TypeError`` instead of ``ValueError`` (:issue:`18817`)
619619
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
620620
- 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`)
621+
- :class:`Timestamp` constructor now accepts a `nanosecond` keyword or positional argument (:issue:`18898`)
621622

622623
.. _whatsnew_0230.api.other:
623624

pandas/_libs/tslibs/conversion.pxd

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ cdef class _TSObject:
1616

1717

1818
cdef convert_to_tsobject(object ts, object tz, object unit,
19-
bint dayfirst, bint yearfirst)
19+
bint dayfirst, bint yearfirst,
20+
int32_t nanos=*)
2021

2122
cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
2223
int32_t nanos=*)

pandas/_libs/tslibs/conversion.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ cpdef int64_t pydt_to_i8(object pydt) except? -1:
252252

253253

254254
cdef convert_to_tsobject(object ts, object tz, object unit,
255-
bint dayfirst, bint yearfirst):
255+
bint dayfirst, bint yearfirst, int32_t nanos=0):
256256
"""
257257
Extract datetime and int64 from any of:
258258
- np.int64 (with unit providing a possible modifier)
@@ -297,7 +297,7 @@ cdef convert_to_tsobject(object ts, object tz, object unit,
297297
obj.value = ts
298298
dt64_to_dtstruct(ts, &obj.dts)
299299
elif PyDateTime_Check(ts):
300-
return convert_datetime_to_tsobject(ts, tz)
300+
return convert_datetime_to_tsobject(ts, tz, nanos)
301301
elif PyDate_Check(ts):
302302
# Keep the converter same as PyDateTime's
303303
ts = datetime.combine(ts, datetime_time())

pandas/_libs/tslibs/timestamps.pyx

+19-6
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ cdef class _Timestamp(datetime):
107107
cdef readonly:
108108
int64_t value, nanosecond
109109
object freq # frequency reference
110+
list _date_attributes
110111

111112
def __hash__(_Timestamp self):
112113
if self.nanosecond:
@@ -425,6 +426,8 @@ class Timestamp(_Timestamp):
425426
.. versionadded:: 0.19.0
426427
hour, minute, second, microsecond : int, optional, default 0
427428
.. versionadded:: 0.19.0
429+
nanosecond : int, optional, default 0
430+
.. versionadded:: 0.23.0
428431
tzinfo : datetime.tzinfo, optional, default None
429432
.. versionadded:: 0.19.0
430433
@@ -556,7 +559,7 @@ class Timestamp(_Timestamp):
556559
object freq=None, tz=None, unit=None,
557560
year=None, month=None, day=None,
558561
hour=None, minute=None, second=None, microsecond=None,
559-
tzinfo=None):
562+
nanosecond=None, tzinfo=None):
560563
# The parameter list folds together legacy parameter names (the first
561564
# four) and positional and keyword parameter names from pydatetime.
562565
#
@@ -580,6 +583,9 @@ class Timestamp(_Timestamp):
580583

581584
cdef _TSObject ts
582585

586+
_date_attributes = [year, month, day, hour, minute, second,
587+
microsecond, nanosecond]
588+
583589
if tzinfo is not None:
584590
if not PyTZInfo_Check(tzinfo):
585591
# tzinfo must be a datetime.tzinfo object, GH#17690
@@ -588,28 +594,35 @@ class Timestamp(_Timestamp):
588594
elif tz is not None:
589595
raise ValueError('Can provide at most one of tz, tzinfo')
590596

591-
if ts_input is _no_input:
597+
if is_string_object(ts_input):
598+
# User passed a date string to parse.
599+
# Check that the user didn't also pass a date attribute kwarg.
600+
if any(arg is not None for arg in _date_attributes):
601+
raise ValueError('Cannot pass a date attribute keyword '
602+
'argument when passing a date string')
603+
604+
elif ts_input is _no_input:
592605
# User passed keyword arguments.
593606
if tz is None:
594607
# Handle the case where the user passes `tz` and not `tzinfo`
595608
tz = tzinfo
596609
return Timestamp(datetime(year, month, day, hour or 0,
597610
minute or 0, second or 0,
598611
microsecond or 0, tzinfo),
599-
tz=tz)
612+
nanosecond=nanosecond, tz=tz)
600613
elif is_integer_object(freq):
601614
# User passed positional arguments:
602615
# Timestamp(year, month, day[, hour[, minute[, second[,
603-
# microsecond[, tzinfo]]]]])
616+
# microsecond[, nanosecond[, tzinfo]]]]]])
604617
return Timestamp(datetime(ts_input, freq, tz, unit or 0,
605618
year or 0, month or 0, day or 0,
606-
hour), tz=hour)
619+
minute), nanosecond=hour, tz=minute)
607620

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

612-
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0)
625+
ts = convert_to_tsobject(ts_input, tz, unit, 0, 0, nanosecond or 0)
613626

614627
if ts.value == NPY_NAT:
615628
return NaT

pandas/tests/scalar/timestamp/test_timestamp.py

+21
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,27 @@ def test_constructor_fromordinal(self):
385385
ts = Timestamp.fromordinal(dt_tz.toordinal(), tz='US/Eastern')
386386
assert ts.to_pydatetime() == dt_tz
387387

388+
@pytest.mark.parametrize('result', [
389+
Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), nanosecond=1),
390+
Timestamp(year=2000, month=1, day=2, hour=3, minute=4, second=5,
391+
microsecond=6, nanosecond=1),
392+
Timestamp(year=2000, month=1, day=2, hour=3, minute=4, second=5,
393+
microsecond=6, nanosecond=1, tz='UTC'),
394+
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, None),
395+
Timestamp(2000, 1, 2, 3, 4, 5, 6, 1, pytz.UTC)])
396+
def test_constructor_nanosecond(self, result):
397+
# GH 18898
398+
expected = Timestamp(datetime(2000, 1, 2, 3, 4, 5, 6), tz=result.tz)
399+
expected = expected + Timedelta(nanoseconds=1)
400+
assert result == expected
401+
402+
@pytest.mark.parametrize('arg', ['year', 'month', 'day', 'hour', 'minute',
403+
'second', 'microsecond', 'nanosecond'])
404+
def test_invalid_date_kwarg_with_string_input(self, arg):
405+
kwarg = {arg: 1}
406+
with pytest.raises(ValueError):
407+
Timestamp('2010-10-10 12:59:59.999999999', **kwarg)
408+
388409
def test_out_of_bounds_value(self):
389410
one_us = np.timedelta64(1).astype('timedelta64[us]')
390411

0 commit comments

Comments
 (0)