Skip to content

Commit 794e32b

Browse files
jbrockmendelnofarmish
authored andcommitted
ENH: Make Timestamp implementation bounds match DatetimeArray/DatetimeIndex/Series (pandas-dev#39245)
1 parent d61a518 commit 794e32b

File tree

7 files changed

+32
-20
lines changed

7 files changed

+32
-20
lines changed

pandas/_libs/tslib.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -447,10 +447,10 @@ cpdef array_to_datetime(
447447
raise ValueError('Tz-aware datetime.datetime '
448448
'cannot be converted to '
449449
'datetime64 unless utc=True')
450+
elif isinstance(val, _Timestamp):
451+
iresult[i] = val.value
450452
else:
451453
iresult[i] = pydatetime_to_dt64(val, &dts)
452-
if isinstance(val, _Timestamp):
453-
iresult[i] += val.nanosecond
454454
check_dts_bounds(&dts)
455455

456456
elif PyDate_Check(val):

pandas/_libs/tslibs/conversion.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, tzinfo tz,
497497
obj.value -= int(offset.total_seconds() * 1e9)
498498

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

503503
if nanos:

pandas/_libs/tslibs/src/datetime/np_datetime.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ This file is derived from NumPy 1.7. See NUMPY_LICENSE.txt
3232
#endif // PyInt_AsLong
3333

3434
const npy_datetimestruct _NS_MIN_DTS = {
35-
1677, 9, 21, 0, 12, 43, 145225, 0, 0};
35+
1677, 9, 21, 0, 12, 43, 145224, 193000, 0};
3636
const npy_datetimestruct _NS_MAX_DTS = {
3737
2262, 4, 11, 23, 47, 16, 854775, 807000, 0};
3838

pandas/_libs/tslibs/timestamps.pyx

+1-5
Original file line numberDiff line numberDiff line change
@@ -1531,11 +1531,7 @@ Timestamp.daysinmonth = Timestamp.days_in_month
15311531

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

15401536
# Resolution is in nanoseconds
15411537
Timestamp.min = Timestamp(_NS_LOWER_BOUND)

pandas/tests/arrays/test_datetimelike.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pandas.compat.numpy import np_version_under1p18
99

1010
import pandas as pd
11-
from pandas import DatetimeIndex, Index, Period, PeriodIndex, TimedeltaIndex
11+
from pandas import DatetimeIndex, Period, PeriodIndex, TimedeltaIndex
1212
import pandas._testing as tm
1313
from pandas.core.arrays import DatetimeArray, PandasArray, PeriodArray, TimedeltaArray
1414

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

333+
def test_getitem_near_implementation_bounds(self):
334+
# We only check tz-naive for DTA bc the bounds are slightly different
335+
# for other tzs
336+
i8vals = np.asarray([NaT.value + n for n in range(1, 5)], dtype="i8")
337+
arr = self.array_cls(i8vals, freq="ns")
338+
arr[0] # should not raise OutOfBoundsDatetime
339+
340+
index = pd.Index(arr)
341+
index[0] # should not raise OutOfBoundsDatetime
342+
343+
ser = pd.Series(arr)
344+
ser[0] # should not raise OutOfBoundsDatetime
345+
333346
def test_getitem_2d(self, arr1d):
334347
# 2d slicing on a 1D array
335348
expected = type(arr1d)(arr1d._data[:, np.newaxis], dtype=arr1d.dtype)
@@ -403,7 +416,7 @@ def test_setitem(self):
403416
@pytest.mark.parametrize(
404417
"box",
405418
[
406-
Index,
419+
pd.Index,
407420
pd.Series,
408421
np.array,
409422
list,

pandas/tests/scalar/timestamp/test_constructors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def test_out_of_bounds_value(self):
372372

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

378378
# No error for the min/max datetimes

pandas/tests/scalar/timestamp/test_timestamp.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -529,17 +529,20 @@ def test_to_datetime_bijective(self):
529529
# by going from nanoseconds to microseconds.
530530
exp_warning = None if Timestamp.max.nanosecond == 0 else UserWarning
531531
with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
532-
assert (
533-
Timestamp(Timestamp.max.to_pydatetime()).value / 1000
534-
== Timestamp.max.value / 1000
535-
)
532+
pydt_max = Timestamp.max.to_pydatetime()
533+
534+
assert Timestamp(pydt_max).value / 1000 == Timestamp.max.value / 1000
536535

537536
exp_warning = None if Timestamp.min.nanosecond == 0 else UserWarning
538537
with tm.assert_produces_warning(exp_warning, check_stacklevel=False):
539-
assert (
540-
Timestamp(Timestamp.min.to_pydatetime()).value / 1000
541-
== Timestamp.min.value / 1000
542-
)
538+
pydt_min = Timestamp.min.to_pydatetime()
539+
540+
# The next assertion can be enabled once GH#39221 is merged
541+
# assert pydt_min < Timestamp.min # this is bc nanos are dropped
542+
tdus = timedelta(microseconds=1)
543+
assert pydt_min + tdus > Timestamp.min
544+
545+
assert Timestamp(pydt_min + tdus).value / 1000 == Timestamp.min.value / 1000
543546

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

0 commit comments

Comments
 (0)