@@ -907,17 +907,154 @@ STRING_TO_FLOAT_RESOLVE_DESCRIPTORS(float16, HALF)
907
907
STRING_TO_FLOAT_CAST (float16 , f16 , npy_half_isinf , npy_double_to_half )
908
908
FLOAT_TO_STRING_CAST (float16 , f16 , npy_half_to_double )
909
909
910
+ // string to datetime
911
+
912
+ static NPY_CASTING
913
+ string_to_datetime_resolve_descriptors (
914
+ PyObject * NPY_UNUSED (self ), PyArray_DTypeMeta * NPY_UNUSED (dtypes [2 ]),
915
+ PyArray_Descr * given_descrs [2 ], PyArray_Descr * loop_descrs [2 ],
916
+ npy_intp * NPY_UNUSED (view_offset ))
917
+ {
918
+ if (given_descrs [1 ] == NULL ) {
919
+ PyErr_SetString (PyExc_TypeError ,
920
+ "Casting from StringDType to datetimes without a unit "
921
+ "is not currently supported" );
922
+ return (NPY_CASTING )- 1 ;
923
+ }
924
+ else {
925
+ Py_INCREF (given_descrs [1 ]);
926
+ loop_descrs [1 ] = given_descrs [1 ];
927
+ }
928
+
929
+ Py_INCREF (given_descrs [0 ]);
930
+ loop_descrs [0 ] = given_descrs [0 ];
931
+
932
+ return NPY_UNSAFE_CASTING ;
933
+ }
934
+
935
+ static int
936
+ string_to_datetime (PyArrayMethod_Context * context , char * const data [],
937
+ npy_intp const dimensions [], npy_intp const strides [],
938
+ NpyAuxData * NPY_UNUSED (auxdata ))
939
+ {
940
+ npy_intp N = dimensions [0 ];
941
+ char * in = data [0 ];
942
+ npy_datetime * out = (npy_datetime * )data [1 ];
943
+
944
+ npy_intp in_stride = strides [0 ];
945
+ npy_intp out_stride = strides [1 ] / sizeof (npy_datetime );
946
+
947
+ ss * s = NULL ;
948
+ npy_datetimestruct dts ;
949
+ NPY_DATETIMEUNIT in_unit = -1 ;
950
+ PyArray_DatetimeMetaData in_meta = {0 , 1 };
951
+ npy_bool out_special ;
952
+
953
+ PyArray_Descr * dt_descr = context -> descriptors [1 ];
954
+ PyArray_DatetimeMetaData * dt_meta =
955
+ & (((PyArray_DatetimeDTypeMetaData * )dt_descr -> c_metadata )-> meta );
956
+
957
+ while (N -- ) {
958
+ s = (ss * )in ;
959
+ if (ss_isnull (s )) {
960
+ * out = NPY_DATETIME_NAT ;
961
+ }
962
+ if (NpyDatetime_ParseISO8601Datetime (
963
+ (const char * )s -> buf , s -> len , in_unit , NPY_UNSAFE_CASTING ,
964
+ & dts , & in_meta .base , & out_special ) < 0 ) {
965
+ return -1 ;
966
+ }
967
+ if (NpyDatetime_ConvertDatetimeStructToDatetime64 (dt_meta , & dts , out ) <
968
+ 0 ) {
969
+ return -1 ;
970
+ }
971
+
972
+ in += in_stride ;
973
+ out += out_stride ;
974
+ }
975
+
976
+ return 0 ;
977
+ }
978
+
979
+ static PyType_Slot s2dt_slots [] = {
980
+ {NPY_METH_resolve_descriptors ,
981
+ & string_to_datetime_resolve_descriptors },
982
+ {NPY_METH_strided_loop , & string_to_datetime },
983
+ {0 , NULL }};
984
+
985
+ static char * s2dt_name = "cast_StringDType_to_Datetime" ;
986
+
987
+ // datetime to string
988
+
989
+ static int
990
+ datetime_to_string (PyArrayMethod_Context * context , char * const data [],
991
+ npy_intp const dimensions [], npy_intp const strides [],
992
+ NpyAuxData * NPY_UNUSED (auxdata ))
993
+ {
994
+ npy_intp N = dimensions [0 ];
995
+ npy_datetime * in = (npy_datetime * )data [0 ];
996
+ char * out = data [1 ];
997
+
998
+ npy_intp in_stride = strides [0 ] / sizeof (npy_datetime );
999
+ npy_intp out_stride = strides [1 ];
1000
+
1001
+ npy_datetimestruct dts ;
1002
+ PyArray_Descr * dt_descr = context -> descriptors [0 ];
1003
+ PyArray_DatetimeMetaData * dt_meta =
1004
+ & (((PyArray_DatetimeDTypeMetaData * )dt_descr -> c_metadata )-> meta );
1005
+ // buffer passed to numpy to build datetime string
1006
+ char datetime_buf [NPY_DATETIME_MAX_ISO8601_STRLEN ];
1007
+
1008
+ while (N -- ) {
1009
+ ss * out_ss = (ss * )out ;
1010
+ ssfree (out_ss );
1011
+ if (* in == NPY_DATETIME_NAT ) {
1012
+ /* convert to NA */
1013
+ out_ss = NULL ;
1014
+ }
1015
+ else {
1016
+ if (NpyDatetime_ConvertDatetime64ToDatetimeStruct (dt_meta , * in ,
1017
+ & dts ) < 0 ) {
1018
+ return -1 ;
1019
+ }
1020
+
1021
+ // zero out buffer
1022
+ memset (datetime_buf , 0 , NPY_DATETIME_MAX_ISO8601_STRLEN );
1023
+
1024
+ if (NpyDatetime_MakeISO8601Datetime (
1025
+ & dts , datetime_buf , NPY_DATETIME_MAX_ISO8601_STRLEN , 0 ,
1026
+ 0 , dt_meta -> base , -1 , NPY_UNSAFE_CASTING ) < 0 ) {
1027
+ return -1 ;
1028
+ }
1029
+
1030
+ if (ssnewlen (datetime_buf , strlen (datetime_buf ), out_ss ) < 0 ) {
1031
+ PyErr_SetString (PyExc_MemoryError , "ssnewlen failed" );
1032
+ return -1 ;
1033
+ }
1034
+ }
1035
+
1036
+ in += in_stride ;
1037
+ out += out_stride ;
1038
+ }
1039
+
1040
+ return 0 ;
1041
+ }
1042
+
1043
+ static PyType_Slot dt2s_slots [] = {
1044
+ {NPY_METH_resolve_descriptors ,
1045
+ & any_to_string_UNSAFE_resolve_descriptors },
1046
+ {NPY_METH_strided_loop , & datetime_to_string },
1047
+ {0 , NULL }};
1048
+
1049
+ static char * dt2s_name = "cast_Datetime_to_StringDType" ;
1050
+
910
1051
// TODO: longdouble
911
1052
// punting on this one because numpy's C routines for handling
912
1053
// longdouble are not public (specifically NumPyOS_ascii_strtold)
913
1054
// also this type is kinda niche and is not needed by pandas
914
1055
//
915
1056
// cfloat, cdouble, and clongdouble
916
1057
// not hard to do in principle but not needed by pandas.
917
- //
918
- // datetime
919
- // numpy's utilities for parsing a string into a datetime
920
- // are not public (specifically parse_iso_8601_datetime).
921
1058
922
1059
PyArrayMethod_Spec *
923
1060
get_cast_spec (const char * name , NPY_CASTING casting ,
@@ -961,7 +1098,7 @@ get_casts()
961
1098
get_cast_spec (t2t_name , NPY_NO_CASTING ,
962
1099
NPY_METH_SUPPORTS_UNALIGNED , t2t_dtypes , s2s_slots );
963
1100
964
- int num_casts = 27 ;
1101
+ int num_casts = 29 ;
965
1102
966
1103
#if NPY_SIZEOF_BYTE == NPY_SIZEOF_SHORT
967
1104
num_casts += 4 ;
@@ -1033,6 +1170,22 @@ get_casts()
1033
1170
DTYPES_AND_CAST_SPEC (f32 , Float )
1034
1171
DTYPES_AND_CAST_SPEC (f16 , Half )
1035
1172
1173
+ PyArray_DTypeMeta * * s2dt_dtypes = get_dtypes (
1174
+ (PyArray_DTypeMeta * )& StringDType , & PyArray_DatetimeDType );
1175
+
1176
+ PyArrayMethod_Spec * StringToDatetimeCastSpec = get_cast_spec (
1177
+ s2dt_name , NPY_UNSAFE_CASTING ,
1178
+ NPY_METH_NO_FLOATINGPOINT_ERRORS | NPY_METH_REQUIRES_PYAPI ,
1179
+ s2dt_dtypes , s2dt_slots );
1180
+
1181
+ PyArray_DTypeMeta * * dt2s_dtypes = get_dtypes (
1182
+ & PyArray_DatetimeDType , (PyArray_DTypeMeta * )& StringDType );
1183
+
1184
+ PyArrayMethod_Spec * DatetimeToStringCastSpec = get_cast_spec (
1185
+ dt2s_name , NPY_UNSAFE_CASTING ,
1186
+ NPY_METH_NO_FLOATINGPOINT_ERRORS | NPY_METH_REQUIRES_PYAPI ,
1187
+ dt2s_dtypes , dt2s_slots );
1188
+
1036
1189
PyArrayMethod_Spec * * casts =
1037
1190
malloc ((num_casts + 1 ) * sizeof (PyArrayMethod_Spec * ));
1038
1191
@@ -1089,6 +1242,8 @@ get_casts()
1089
1242
casts [cast_i ++ ] = FloatToStringCastSpec ;
1090
1243
casts [cast_i ++ ] = StringToHalfCastSpec ;
1091
1244
casts [cast_i ++ ] = HalfToStringCastSpec ;
1245
+ casts [cast_i ++ ] = StringToDatetimeCastSpec ;
1246
+ casts [cast_i ++ ] = DatetimeToStringCastSpec ;
1092
1247
casts [cast_i ++ ] = NULL ;
1093
1248
1094
1249
assert (casts [num_casts ] == NULL );
0 commit comments