Skip to content

Commit 9dc03e1

Browse files
committed
BUG: underflow on Timestamp creation
1 parent 4d7c6e7 commit 9dc03e1

File tree

4 files changed

+32
-12
lines changed

4 files changed

+32
-12
lines changed

doc/source/whatsnew/v0.19.1.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Bug Fixes
3939

4040

4141

42-
42+
- Bug in ``Timestamp`` where dates very near the minimum (1677-09) could underflow on creation (:issue:`14415`)
4343

4444

4545

pandas/src/datetime/np_datetime.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
846846
dt = dt % perday;
847847
}
848848
else {
849-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
849+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
850+
out);
850851
dt = (perday-1) + (dt + 1) % perday;
851852
}
852853
out->hour = dt;
@@ -860,7 +861,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
860861
dt = dt % perday;
861862
}
862863
else {
863-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
864+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
865+
out);
864866
dt = (perday-1) + (dt + 1) % perday;
865867
}
866868
out->hour = dt / 60;
@@ -875,7 +877,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
875877
dt = dt % perday;
876878
}
877879
else {
878-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
880+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
881+
out);
879882
dt = (perday-1) + (dt + 1) % perday;
880883
}
881884
out->hour = dt / (60*60);
@@ -891,7 +894,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
891894
dt = dt % perday;
892895
}
893896
else {
894-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
897+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
898+
out);
895899
dt = (perday-1) + (dt + 1) % perday;
896900
}
897901
out->hour = dt / (60*60*1000LL);
@@ -908,7 +912,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
908912
dt = dt % perday;
909913
}
910914
else {
911-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
915+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
916+
out);
912917
dt = (perday-1) + (dt + 1) % perday;
913918
}
914919
out->hour = dt / (60*60*1000000LL);
@@ -925,7 +930,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
925930
dt = dt % perday;
926931
}
927932
else {
928-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
933+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
934+
out);
929935
dt = (perday-1) + (dt + 1) % perday;
930936
}
931937
out->hour = dt / (60*60*1000000000LL);
@@ -943,7 +949,8 @@ convert_datetime_to_datetimestruct(pandas_datetime_metadata *meta,
943949
dt = dt % perday;
944950
}
945951
else {
946-
set_datetimestruct_days((dt - (perday-1)) / perday, out);
952+
set_datetimestruct_days(dt / perday - (dt % perday == 0 ? 0 : 1),
953+
out);
947954
dt = (perday-1) + (dt + 1) % perday;
948955
}
949956
out->hour = dt / (60*60*1000000000000LL);

pandas/tseries/tests/test_timeseries.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4463,6 +4463,15 @@ def test_basics_nanos(self):
44634463
self.assertEqual(stamp.microsecond, 0)
44644464
self.assertEqual(stamp.nanosecond, 500)
44654465

4466+
# GH 14415
4467+
val = np.iinfo(np.int64).min + 80000000000000
4468+
stamp = Timestamp(val)
4469+
self.assertEqual(stamp.year, 1677)
4470+
self.assertEqual(stamp.month, 9)
4471+
self.assertEqual(stamp.day, 21)
4472+
self.assertEqual(stamp.microsecond, 145224)
4473+
self.assertEqual(stamp.nanosecond, 192)
4474+
44664475
def test_unit(self):
44674476

44684477
def check(val, unit=None, h=1, s=1, us=0):

pandas/tslib.pyx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ from cpython cimport (
2424
PyUnicode_AsUTF8String,
2525
)
2626

27+
28+
cdef extern from "headers/stdint.h":
29+
enum: INT64_MAX
30+
enum: INT64_MIN
31+
2732
# Cython < 0.17 doesn't have this in cpython
2833
cdef extern from "Python.h":
2934
cdef PyTypeObject *Py_TYPE(object)
@@ -904,10 +909,9 @@ cpdef object get_value_box(ndarray arr, object loc):
904909

905910

906911
# Add the min and max fields at the class level
907-
# These are defined as magic numbers due to strange
908-
# wraparound behavior when using the true int64 lower boundary
909-
cdef int64_t _NS_LOWER_BOUND = -9223285636854775000LL
910-
cdef int64_t _NS_UPPER_BOUND = 9223372036854775807LL
912+
# INT64_MIN is reserved for NaT
913+
cdef int64_t _NS_LOWER_BOUND = INT64_MIN + 1
914+
cdef int64_t _NS_UPPER_BOUND = INT64_MAX
911915

912916
cdef pandas_datetimestruct _NS_MIN_DTS, _NS_MAX_DTS
913917
pandas_datetime_to_datetimestruct(_NS_LOWER_BOUND, PANDAS_FR_ns, &_NS_MIN_DTS)

0 commit comments

Comments
 (0)