Skip to content

ENH: Make Timestamp implementation bounds match DTA/DTI/Series #39245

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 2 commits into from
Jan 20, 2021
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
4 changes: 2 additions & 2 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,10 @@ cpdef array_to_datetime(
raise ValueError('Tz-aware datetime.datetime '
'cannot be converted to '
'datetime64 unless utc=True')
elif isinstance(val, _Timestamp):
iresult[i] = val.value
else:
iresult[i] = pydatetime_to_dt64(val, &dts)
if isinstance(val, _Timestamp):
iresult[i] += val.nanosecond
check_dts_bounds(&dts)

elif PyDate_Check(val):
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, tzinfo tz,
obj.value -= int(offset.total_seconds() * 1e9)

if isinstance(ts, ABCTimestamp):
obj.value += ts.nanosecond
obj.value += <int64_t>ts.nanosecond
obj.dts.ps = ts.nanosecond * 1000

if nanos:
Expand Down
2 changes: 1 addition & 1 deletion pandas/_libs/tslibs/src/datetime/np_datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This file is derived from NumPy 1.7. See NUMPY_LICENSE.txt
#endif // PyInt_AsLong

const npy_datetimestruct _NS_MIN_DTS = {
1677, 9, 21, 0, 12, 43, 145225, 0, 0};
1677, 9, 21, 0, 12, 43, 145224, 193000, 0};
const npy_datetimestruct _NS_MAX_DTS = {
2262, 4, 11, 23, 47, 16, 854775, 807000, 0};

Expand Down
6 changes: 1 addition & 5 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1533,11 +1533,7 @@ Timestamp.daysinmonth = Timestamp.days_in_month

# Add the min and max fields at the class level
cdef int64_t _NS_UPPER_BOUND = np.iinfo(np.int64).max
# the smallest value we could actually represent is
# INT64_MIN + 1 == -9223372036854775807
# but to allow overflow free conversion with a microsecond resolution
# use the smallest value with a 0 nanosecond unit (0s in last 3 digits)
cdef int64_t _NS_LOWER_BOUND = -9_223_372_036_854_775_000
cdef int64_t _NS_LOWER_BOUND = NPY_NAT + 1

# Resolution is in nanoseconds
Timestamp.min = Timestamp(_NS_LOWER_BOUND)
Expand Down
17 changes: 15 additions & 2 deletions pandas/tests/arrays/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pandas.compat.numpy import np_version_under1p18

import pandas as pd
from pandas import DatetimeIndex, Index, Period, PeriodIndex, TimedeltaIndex
from pandas import DatetimeIndex, Period, PeriodIndex, TimedeltaIndex
import pandas._testing as tm
from pandas.core.arrays import DatetimeArray, PandasArray, PeriodArray, TimedeltaArray

Expand Down Expand Up @@ -330,6 +330,19 @@ def test_searchsorted_castable_strings(self, arr1d, box, request):
):
arr.searchsorted([str(arr[1]), "baz"])

def test_getitem_near_implementation_bounds(self):
# We only check tz-naive for DTA bc the bounds are slightly different
# for other tzs
i8vals = np.asarray([NaT.value + n for n in range(1, 5)], dtype="i8")
arr = self.array_cls(i8vals, freq="ns")
arr[0] # should not raise OutOfBoundsDatetime

index = pd.Index(arr)
index[0] # should not raise OutOfBoundsDatetime

ser = pd.Series(arr)
ser[0] # should not raise OutOfBoundsDatetime

def test_getitem_2d(self, arr1d):
# 2d slicing on a 1D array
expected = type(arr1d)(arr1d._data[:, np.newaxis], dtype=arr1d.dtype)
Expand Down Expand Up @@ -403,7 +416,7 @@ def test_setitem(self):
@pytest.mark.parametrize(
"box",
[
Index,
pd.Index,
pd.Series,
np.array,
list,
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/scalar/timestamp/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ def test_out_of_bounds_value(self):

# By definition we can't go out of bounds in [ns], so we
# convert the datetime64s to [us] so we can go out of bounds
min_ts_us = np.datetime64(Timestamp.min).astype("M8[us]")
min_ts_us = np.datetime64(Timestamp.min).astype("M8[us]") + one_us
max_ts_us = np.datetime64(Timestamp.max).astype("M8[us]")

# No error for the min/max datetimes
Expand Down
19 changes: 11 additions & 8 deletions pandas/tests/scalar/timestamp/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,17 +529,20 @@ def test_to_datetime_bijective(self):
# by going from nanoseconds to microseconds.
exp_warning = None if Timestamp.max.nanosecond == 0 else UserWarning
with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
assert (
Timestamp(Timestamp.max.to_pydatetime()).value / 1000
== Timestamp.max.value / 1000
)
pydt_max = Timestamp.max.to_pydatetime()

assert Timestamp(pydt_max).value / 1000 == Timestamp.max.value / 1000

exp_warning = None if Timestamp.min.nanosecond == 0 else UserWarning
with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
assert (
Timestamp(Timestamp.min.to_pydatetime()).value / 1000
== Timestamp.min.value / 1000
)
pydt_min = Timestamp.min.to_pydatetime()

# The next assertion can be enabled once GH#39221 is merged
# assert pydt_min < Timestamp.min # this is bc nanos are dropped
tdus = timedelta(microseconds=1)
assert pydt_min + tdus > Timestamp.min

assert Timestamp(pydt_min + tdus).value / 1000 == Timestamp.min.value / 1000

def test_to_period_tz_warning(self):
# GH#21333 make sure a warning is issued when timezone
Expand Down