@@ -28,6 +28,160 @@ static void pandas_datetime_destructor(PyObject *op) {
2828 PyMem_Free (ptr );
2929}
3030
31+ /*
32+ *
33+ * Converts a Python datetime.datetime or datetime.date
34+ * object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
35+ * to convert to UTC time.
36+ *
37+ * The following implementation just asks for attributes, and thus
38+ * supports datetime duck typing. The tzinfo time zone conversion
39+ * requires this style of access as well.
40+ *
41+ * Returns -1 on error, 0 on success, and 1 (with no error set)
42+ * if obj doesn't have the needed date or datetime attributes.
43+ */
44+ static int convert_pydatetime_to_datetimestruct (PyObject * dtobj ,
45+ npy_datetimestruct * out ) {
46+ // Assumes that obj is a valid datetime object
47+ PyObject * tmp ;
48+ PyObject * obj = (PyObject * )dtobj ;
49+
50+ /* Initialize the output to all zeros */
51+ memset (out , 0 , sizeof (npy_datetimestruct ));
52+ out -> month = 1 ;
53+ out -> day = 1 ;
54+
55+ out -> year = PyLong_AsLong (PyObject_GetAttrString (obj , "year" ));
56+ out -> month = PyLong_AsLong (PyObject_GetAttrString (obj , "month" ));
57+ out -> day = PyLong_AsLong (PyObject_GetAttrString (obj , "day" ));
58+
59+ // TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
60+ // PyDateTime_Check here, and less verbose attribute lookups.
61+
62+ /* Check for time attributes (if not there, return success as a date) */
63+ if (!PyObject_HasAttrString (obj , "hour" ) ||
64+ !PyObject_HasAttrString (obj , "minute" ) ||
65+ !PyObject_HasAttrString (obj , "second" ) ||
66+ !PyObject_HasAttrString (obj , "microsecond" )) {
67+ return 0 ;
68+ }
69+
70+ out -> hour = PyLong_AsLong (PyObject_GetAttrString (obj , "hour" ));
71+ out -> min = PyLong_AsLong (PyObject_GetAttrString (obj , "minute" ));
72+ out -> sec = PyLong_AsLong (PyObject_GetAttrString (obj , "second" ));
73+ out -> us = PyLong_AsLong (PyObject_GetAttrString (obj , "microsecond" ));
74+
75+ if (PyObject_HasAttrString (obj , "tzinfo" )) {
76+ PyObject * offset = extract_utc_offset (obj );
77+ /* Apply the time zone offset if datetime obj is tz-aware */
78+ if (offset != NULL ) {
79+ if (offset == Py_None ) {
80+ Py_DECREF (offset );
81+ return 0 ;
82+ }
83+ PyObject * tmp_int ;
84+ int seconds_offset , minutes_offset ;
85+ /*
86+ * The timedelta should have a function "total_seconds"
87+ * which contains the value we want.
88+ */
89+ tmp = PyObject_CallMethod (offset , "total_seconds" , "" );
90+ Py_DECREF (offset );
91+ if (tmp == NULL ) {
92+ return -1 ;
93+ }
94+ tmp_int = PyNumber_Long (tmp );
95+ if (tmp_int == NULL ) {
96+ Py_DECREF (tmp );
97+ return -1 ;
98+ }
99+ seconds_offset = PyLong_AsLong (tmp_int );
100+ if (seconds_offset == -1 && PyErr_Occurred ()) {
101+ Py_DECREF (tmp_int );
102+ Py_DECREF (tmp );
103+ return -1 ;
104+ }
105+ Py_DECREF (tmp_int );
106+ Py_DECREF (tmp );
107+
108+ /* Convert to a minutes offset and apply it */
109+ minutes_offset = seconds_offset / 60 ;
110+
111+ add_minutes_to_datetimestruct (out , - minutes_offset );
112+ }
113+ }
114+
115+ return 0 ;
116+ }
117+
118+ // Converts a Python object representing a Date / Datetime to ISO format
119+ // up to precision `base` e.g. base="s" yields 2020-01-03T00:00:00Z
120+ // while base="ns" yields "2020-01-01T00:00:00.000000000Z"
121+ // len is mutated to save the length of the returned string
122+ static char * PyDateTimeToIso (PyObject * obj , NPY_DATETIMEUNIT base ,
123+ size_t * len ) {
124+ npy_datetimestruct dts ;
125+ int ret ;
126+
127+ ret = convert_pydatetime_to_datetimestruct (obj , & dts );
128+ if (ret != 0 ) {
129+ if (!PyErr_Occurred ()) {
130+ PyErr_SetString (PyExc_ValueError ,
131+ "Could not convert PyDateTime to numpy datetime" );
132+ }
133+ return NULL ;
134+ }
135+
136+ * len = (size_t )get_datetime_iso_8601_strlen (0 , base );
137+ char * result = PyObject_Malloc (* len );
138+ // Check to see if PyDateTime has a timezone.
139+ // Don't convert to UTC if it doesn't.
140+ int is_tz_aware = 0 ;
141+ if (PyObject_HasAttrString (obj , "tzinfo" )) {
142+ PyObject * offset = extract_utc_offset (obj );
143+ if (offset == NULL ) {
144+ PyObject_Free (result );
145+ return NULL ;
146+ }
147+ is_tz_aware = offset != Py_None ;
148+ Py_DECREF (offset );
149+ }
150+ ret = make_iso_8601_datetime (& dts , result , * len , is_tz_aware , base );
151+
152+ if (ret != 0 ) {
153+ PyErr_SetString (PyExc_ValueError ,
154+ "Could not convert datetime value to string" );
155+ PyObject_Free (result );
156+ return NULL ;
157+ }
158+
159+ // Note that get_datetime_iso_8601_strlen just gives a generic size
160+ // for ISO string conversion, not the actual size used
161+ * len = strlen (result );
162+ return result ;
163+ }
164+
165+ // Convert a Python Date/Datetime to Unix epoch with resolution base
166+ static npy_datetime PyDateTimeToEpoch (PyObject * dt , NPY_DATETIMEUNIT base ) {
167+ npy_datetimestruct dts ;
168+ int ret ;
169+
170+ ret = convert_pydatetime_to_datetimestruct (dt , & dts );
171+ if (ret != 0 ) {
172+ if (!PyErr_Occurred ()) {
173+ PyErr_SetString (PyExc_ValueError ,
174+ "Could not convert PyDateTime to numpy datetime" );
175+ }
176+ // TODO(username): is setting errMsg required?
177+ // ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
178+ // return NULL;
179+ }
180+
181+ npy_datetime npy_dt = npy_datetimestruct_to_datetime (NPY_FR_ns , & dts );
182+ return NpyDateTimeToEpoch (npy_dt , base );
183+ }
184+
31185static int pandas_datetime_exec (PyObject * module ) {
32186 PyDateTime_IMPORT ;
33187 PandasDateTime_CAPI * capi = PyMem_Malloc (sizeof (PandasDateTime_CAPI ));
@@ -94,5 +248,6 @@ static struct PyModuleDef pandas_datetimemodule = {
94248 .m_slots = pandas_datetime_slots };
95249
96250PyMODINIT_FUNC PyInit_pandas_datetime (void ) {
251+ PyDateTime_IMPORT ;
97252 return PyModuleDef_Init (& pandas_datetimemodule );
98253}
0 commit comments