@@ -22,7 +22,10 @@ import_datetime()
22
22
cimport numpy as cnp
23
23
24
24
cnp.import_array()
25
- from numpy cimport int64_t
25
+ from numpy cimport (
26
+ int64_t,
27
+ ndarray,
28
+ )
26
29
27
30
from pandas._libs.tslibs.util cimport get_c_string_buf_and_size
28
31
@@ -36,7 +39,12 @@ cdef extern from "src/datetime/np_datetime.h":
36
39
pandas_timedeltastruct * result
37
40
) nogil
38
41
42
+ # AS, FS, PS versions exist but are not imported because they are not used.
39
43
npy_datetimestruct _NS_MIN_DTS, _NS_MAX_DTS
44
+ npy_datetimestruct _US_MIN_DTS, _US_MAX_DTS
45
+ npy_datetimestruct _MS_MIN_DTS, _MS_MAX_DTS
46
+ npy_datetimestruct _S_MIN_DTS, _S_MAX_DTS
47
+ npy_datetimestruct _M_MIN_DTS, _M_MAX_DTS
40
48
41
49
PyArray_DatetimeMetaData get_datetime_metadata_from_dtype(cnp.PyArray_Descr * dtype);
42
50
@@ -119,22 +127,40 @@ class OutOfBoundsDatetime(ValueError):
119
127
pass
120
128
121
129
122
- cdef inline check_dts_bounds(npy_datetimestruct * dts):
130
+ cdef check_dts_bounds(npy_datetimestruct * dts, NPY_DATETIMEUNIT unit = NPY_FR_ns ):
123
131
""" Raises OutOfBoundsDatetime if the given date is outside the range that
124
132
can be represented by nanosecond-resolution 64-bit integers."""
125
133
cdef:
126
134
bint error = False
127
-
128
- if (dts.year <= 1677 and
129
- cmp_npy_datetimestruct(dts, & _NS_MIN_DTS) == - 1 ):
135
+ npy_datetimestruct cmp_upper, cmp_lower
136
+
137
+ if unit == NPY_FR_ns:
138
+ cmp_upper = _NS_MAX_DTS
139
+ cmp_lower = _NS_MIN_DTS
140
+ elif unit == NPY_FR_us:
141
+ cmp_upper = _US_MAX_DTS
142
+ cmp_lower = _US_MIN_DTS
143
+ elif unit == NPY_FR_ms:
144
+ cmp_upper = _MS_MAX_DTS
145
+ cmp_lower = _MS_MIN_DTS
146
+ elif unit == NPY_FR_s:
147
+ cmp_upper = _S_MAX_DTS
148
+ cmp_lower = _S_MIN_DTS
149
+ elif unit == NPY_FR_m:
150
+ cmp_upper = _M_MAX_DTS
151
+ cmp_lower = _M_MIN_DTS
152
+ else :
153
+ raise NotImplementedError (unit)
154
+
155
+ if cmp_npy_datetimestruct(dts, & cmp_lower) == - 1 :
130
156
error = True
131
- elif (dts.year >= 2262 and
132
- cmp_npy_datetimestruct(dts, & _NS_MAX_DTS) == 1 ):
157
+ elif cmp_npy_datetimestruct(dts, & cmp_upper) == 1 :
133
158
error = True
134
159
135
160
if error:
136
161
fmt = (f' {dts.year}-{dts.month:02d}-{dts.day:02d} '
137
162
f' {dts.hour:02d}:{dts.min:02d}:{dts.sec:02d}' )
163
+ # TODO: "nanosecond" in the message assumes NPY_FR_ns
138
164
raise OutOfBoundsDatetime(f' Out of bounds nanosecond timestamp: {fmt}' )
139
165
140
166
@@ -202,3 +228,68 @@ cdef inline int _string_to_dts(str val, npy_datetimestruct* dts,
202
228
buf = get_c_string_buf_and_size(val, & length)
203
229
return parse_iso_8601_datetime(buf, length, want_exc,
204
230
dts, out_local, out_tzoffset)
231
+
232
+
233
+ cpdef ndarray astype_overflowsafe(
234
+ ndarray values,
235
+ cnp.dtype dtype,
236
+ bint copy = True ,
237
+ ):
238
+ """
239
+ Convert an ndarray with datetime64[X] to datetime64[Y], raising on overflow.
240
+ """
241
+ if values.descr.type_num != cnp.NPY_DATETIME:
242
+ # aka values.dtype.kind != "M"
243
+ raise TypeError (" astype_overflowsafe values must have datetime64 dtype" )
244
+ if dtype.type_num != cnp.NPY_DATETIME:
245
+ raise TypeError (" astype_overflowsafe dtype must be datetime64" )
246
+
247
+ cdef:
248
+ NPY_DATETIMEUNIT from_unit = get_unit_from_dtype(values.dtype)
249
+ NPY_DATETIMEUNIT to_unit = get_unit_from_dtype(dtype)
250
+
251
+ if (
252
+ from_unit == NPY_DATETIMEUNIT.NPY_FR_GENERIC
253
+ or to_unit == NPY_DATETIMEUNIT.NPY_FR_GENERIC
254
+ ):
255
+ # without raising explicitly here, we end up with a SystemError
256
+ # built-in function [...] returned a result with an error
257
+ raise ValueError (" datetime64 values and dtype must have a unit specified" )
258
+
259
+ if from_unit == to_unit:
260
+ # Check this before allocating result for perf, might save some memory
261
+ if copy:
262
+ return values.copy()
263
+ return values
264
+
265
+ cdef:
266
+ ndarray i8values = values.view(" i8" )
267
+
268
+ # equiv: result = np.empty((<object>values).shape, dtype="i8")
269
+ ndarray iresult = cnp.PyArray_EMPTY(
270
+ values.ndim, values.shape, cnp.NPY_INT64, 0
271
+ )
272
+
273
+ cnp.broadcast mi = cnp.PyArray_MultiIterNew2(iresult, i8values)
274
+ cnp.flatiter it
275
+ Py_ssize_t i, N = values.size
276
+ int64_t value, new_value
277
+ npy_datetimestruct dts
278
+
279
+ for i in range (N):
280
+ # Analogous to: item = values[i]
281
+ value = (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 1 ))[0 ]
282
+
283
+ if value == NPY_DATETIME_NAT:
284
+ new_value = NPY_DATETIME_NAT
285
+ else :
286
+ pandas_datetime_to_datetimestruct(value, from_unit, & dts)
287
+ check_dts_bounds(& dts, to_unit)
288
+ new_value = npy_datetimestruct_to_datetime(to_unit, & dts)
289
+
290
+ # Analogous to: iresult[i] = new_value
291
+ (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = new_value
292
+
293
+ cnp.PyArray_MultiIter_NEXT(mi)
294
+
295
+ return iresult.view(dtype)
0 commit comments