@@ -993,6 +993,98 @@ def cast2():
993993 numpy .datetime64 ("2014" ).astype ("<M8[fs]" )
994994 assert_raises (OverflowError , cast2 )
995995
996+ def test_cast_overflow_safe_unit_conversion (self ):
997+ # Overflow when converting datetime64 between linear units
998+ # (the fast-path cast), e.g. seconds -> nanoseconds.
999+ # INT64_MAX / 1e9 ≈ 9.2e9 seconds ≈ 292 years from epoch,
1000+ # so dates beyond ~2262 overflow when cast to ns.
1001+
1002+ # gh-16352: upconversion to finer units overflows
1003+ arr = np .array (["2367-12-31 12:00:00" ], dtype = "datetime64[h]" )
1004+ with pytest .raises (OverflowError , match = "Overflow" ):
1005+ arr .astype ("datetime64[ns]" )
1006+
1007+ # gh-16352: scalar case
1008+ val = np .datetime64 ("3000-01-01" , "s" )
1009+ with pytest .raises (OverflowError , match = "Overflow" ):
1010+ val .astype ("datetime64[ns]" )
1011+
1012+ # gh-22346: downconversion to coarser units overflows near INT64_MIN
1013+ dt = np .datetime64 (np .iinfo (np .int64 ).min + 1 , "s" )
1014+ with pytest .raises (OverflowError , match = "Overflow" ):
1015+ dt .astype ("M8[m]" )
1016+
1017+ # negative overflow (far in the past)
1018+ val_neg = np .datetime64 ("0001-01-01" , "s" )
1019+ with pytest .raises (OverflowError , match = "Overflow" ):
1020+ val_neg .astype ("datetime64[ns]" )
1021+
1022+ # timedelta overflow (strided cast path in dtype_transfer.c)
1023+ td = np .timedelta64 (2 ** 62 , "s" )
1024+ with pytest .raises (OverflowError , match = "Overflow" ):
1025+ td .astype ("timedelta64[ns]" )
1026+
1027+ # timedelta overflow (scalar cast path in datetime.c via
1028+ # cast_timedelta_to_timedelta)
1029+ td_big = np .timedelta64 (2 ** 62 , "s" )
1030+ with pytest .raises (OverflowError , match = "Overflow" ):
1031+ np .array (td_big , dtype = "timedelta64[ns]" )
1032+
1033+ # timedelta exact boundary: INT64_MAX // 1e9 = 9223372036
1034+ td_ok = np .timedelta64 (9223372036 , "s" )
1035+ result_td = td_ok .astype ("timedelta64[ns]" )
1036+ assert result_td == np .timedelta64 (9223372036000000000 , "ns" )
1037+
1038+ td_bad = np .timedelta64 (9223372037 , "s" )
1039+ with pytest .raises (OverflowError , match = "Overflow" ):
1040+ td_bad .astype ("timedelta64[ns]" )
1041+
1042+ # negative timedelta overflow
1043+ td_neg = np .timedelta64 (- 9223372037 , "s" )
1044+ with pytest .raises (OverflowError , match = "Overflow" ):
1045+ td_neg .astype ("timedelta64[ns]" )
1046+
1047+ # timedelta NaT passthrough
1048+ td_nat = np .timedelta64 ("NaT" , "s" )
1049+ result_td_nat = td_nat .astype ("timedelta64[ns]" )
1050+ assert np .isnat (result_td_nat )
1051+
1052+ # valid conversions near the boundary should still work
1053+ val_ok = np .datetime64 ("2020-01-01" , "s" )
1054+ result = val_ok .astype ("datetime64[ns]" )
1055+ assert result == np .datetime64 ("2020-01-01" , "ns" )
1056+
1057+ arr_ok = np .array (["2000-01-01" , "2020-06-15" ], dtype = "datetime64[s]" )
1058+ result_arr = arr_ok .astype ("datetime64[ns]" )
1059+ expected = np .array (["2000-01-01" , "2020-06-15" ], dtype = "datetime64[ns]" )
1060+ assert_equal (result_arr , expected )
1061+
1062+ # NaT should pass through without raising
1063+ arr_nat = np .array (["NaT" , "2020-01-01" ], dtype = "datetime64[s]" )
1064+ result_nat = arr_nat .astype ("datetime64[ns]" )
1065+ assert np .isnat (result_nat [0 ])
1066+ assert result_nat [1 ] == np .datetime64 ("2020-01-01" , "ns" )
1067+
1068+ # Exact boundary: INT64_MAX // 1e9 = 9223372036 seconds is OK,
1069+ # 9223372037 seconds overflows when cast to ns.
1070+ ok_boundary = np .datetime64 (9223372036 , "s" )
1071+ result_boundary = ok_boundary .astype ("datetime64[ns]" )
1072+ assert result_boundary == np .datetime64 (9223372036 , "s" )
1073+
1074+ bad_boundary = np .datetime64 (9223372037 , "s" )
1075+ with pytest .raises (OverflowError , match = "Overflow" ):
1076+ bad_boundary .astype ("datetime64[ns]" )
1077+
1078+ # Exercise the num != 1 code path (e.g. "2s" metadata)
1079+ arr_2s = np .array ([3 ], dtype = "datetime64[2s]" )
1080+ result_2s = arr_2s .astype ("datetime64[s]" )
1081+ assert result_2s [0 ] == np .datetime64 (6 , "s" )
1082+
1083+ # Overflow with num != 1
1084+ arr_2s_big = np .array ([np .iinfo (np .int64 ).max // 2 ], dtype = "datetime64[2s]" )
1085+ with pytest .raises (OverflowError , match = "Overflow" ):
1086+ arr_2s_big .astype ("datetime64[ns]" )
1087+
9961088 def test_pyobject_roundtrip (self ):
9971089 # All datetime types should be able to roundtrip through object
9981090 a = np .array ([0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 commit comments