@@ -28,6 +28,160 @@ static void pandas_datetime_destructor(PyObject *op) {
28
28
PyMem_Free (ptr );
29
29
}
30
30
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
+
31
185
static int pandas_datetime_exec (PyObject * module ) {
32
186
PyDateTime_IMPORT ;
33
187
PandasDateTime_CAPI * capi = PyMem_Malloc (sizeof (PandasDateTime_CAPI ));
@@ -94,5 +248,6 @@ static struct PyModuleDef pandas_datetimemodule = {
94
248
.m_slots = pandas_datetime_slots };
95
249
96
250
PyMODINIT_FUNC PyInit_pandas_datetime (void ) {
251
+ PyDateTime_IMPORT ;
97
252
return PyModuleDef_Init (& pandas_datetimemodule );
98
253
}
0 commit comments