@@ -131,6 +131,7 @@ typedef struct __PyObjectEncoder {
131131
132132 int datetimeIso ;
133133 NPY_DATETIMEUNIT datetimeUnit ;
134+ NPY_DATETIMEUNIT valueUnit ;
134135
135136 // output format style for pandas data types
136137 int outputFormat ;
@@ -350,7 +351,8 @@ static char *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc,
350351static char * NpyDateTimeToIsoCallback (JSOBJ Py_UNUSED (unused ),
351352 JSONTypeContext * tc , size_t * len ) {
352353 NPY_DATETIMEUNIT base = ((PyObjectEncoder * )tc -> encoder )-> datetimeUnit ;
353- GET_TC (tc )-> cStr = int64ToIso (GET_TC (tc )-> longValue , base , len );
354+ NPY_DATETIMEUNIT valueUnit = ((PyObjectEncoder * )tc -> encoder )-> valueUnit ;
355+ GET_TC (tc )-> cStr = int64ToIso (GET_TC (tc )-> longValue , valueUnit , base , len );
354356 return GET_TC (tc )-> cStr ;
355357}
356358
@@ -364,8 +366,9 @@ static char *NpyTimeDeltaToIsoCallback(JSOBJ Py_UNUSED(unused),
364366/* JSON callback */
365367static char * PyDateTimeToIsoCallback (JSOBJ obj , JSONTypeContext * tc ,
366368 size_t * len ) {
367- if (!PyDate_Check (obj )) {
368- PyErr_SetString (PyExc_TypeError , "Expected date object" );
369+ if (!PyDate_Check (obj ) && !PyDateTime_Check (obj )) {
370+ PyErr_SetString (PyExc_TypeError , "Expected date or datetime object" );
371+ ((JSONObjectEncoder * )tc -> encoder )-> errorMsg = "" ;
369372 return NULL ;
370373 }
371374
@@ -502,6 +505,10 @@ int NpyArr_iterNextItem(JSOBJ obj, JSONTypeContext *tc) {
502505 GET_TC (tc )-> itemValue = obj ;
503506 Py_INCREF (obj );
504507 ((PyObjectEncoder * )tc -> encoder )-> npyType = PyArray_TYPE (npyarr -> array );
508+ // Also write the resolution (unit) of the ndarray
509+ PyArray_Descr * dtype = PyArray_DESCR (npyarr -> array );
510+ ((PyObjectEncoder * )tc -> encoder )-> valueUnit =
511+ get_datetime_metadata_from_dtype (dtype ).base ;
505512 ((PyObjectEncoder * )tc -> encoder )-> npyValue = npyarr -> dataptr ;
506513 ((PyObjectEncoder * )tc -> encoder )-> npyCtxtPassthru = npyarr ;
507514 } else {
@@ -1255,6 +1262,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
12551262 char * * ret ;
12561263 char * dataptr , * cLabel ;
12571264 int type_num ;
1265+ PyArray_Descr * dtype ;
12581266 NPY_DATETIMEUNIT base = enc -> datetimeUnit ;
12591267
12601268 if (!labels ) {
@@ -1283,6 +1291,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
12831291 stride = PyArray_STRIDE (labels , 0 );
12841292 dataptr = PyArray_DATA (labels );
12851293 type_num = PyArray_TYPE (labels );
1294+ dtype = PyArray_DESCR (labels );
12861295
12871296 for (i = 0 ; i < num ; i ++ ) {
12881297 item = PyArray_GETITEM (labels , dataptr );
@@ -1293,7 +1302,8 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
12931302 }
12941303
12951304 int is_datetimelike = 0 ;
1296- npy_int64 nanosecVal ;
1305+ npy_int64 i8date ;
1306+ NPY_DATETIMEUNIT dateUnit = NPY_FR_ns ;
12971307 if (PyTypeNum_ISDATETIME (type_num )) {
12981308 is_datetimelike = 1 ;
12991309 PyArray_VectorUnaryFunc * castfunc =
@@ -1303,35 +1313,37 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
13031313 "Cannot cast numpy dtype %d to long" ,
13041314 enc -> npyType );
13051315 }
1306- castfunc (dataptr , & nanosecVal , 1 , NULL , NULL );
1316+ castfunc (dataptr , & i8date , 1 , NULL , NULL );
1317+ dateUnit = get_datetime_metadata_from_dtype (dtype ).base ;
13071318 } else if (PyDate_Check (item ) || PyDelta_Check (item )) {
13081319 is_datetimelike = 1 ;
13091320 if (PyObject_HasAttrString (item , "_value" )) {
13101321 // see test_date_index_and_values for case with non-nano
1311- nanosecVal = get_long_attr (item , "_value" );
1322+ i8date = get_long_attr (item , "_value" );
13121323 } else {
13131324 if (PyDelta_Check (item )) {
1314- nanosecVal = total_seconds (item ) *
1325+ i8date = total_seconds (item ) *
13151326 1000000000LL ; // nanoseconds per second
13161327 } else {
13171328 // datetime.* objects don't follow above rules
1318- nanosecVal = PyDateTimeToEpoch (item , NPY_FR_ns );
1329+ i8date = PyDateTimeToEpoch (item , NPY_FR_ns );
13191330 }
13201331 }
13211332 }
13221333
13231334 if (is_datetimelike ) {
1324- if (nanosecVal == get_nat ()) {
1335+ if (i8date == get_nat ()) {
13251336 len = 4 ;
13261337 cLabel = PyObject_Malloc (len + 1 );
13271338 strncpy (cLabel , "null" , len + 1 );
13281339 } else {
13291340 if (enc -> datetimeIso ) {
13301341 if ((type_num == NPY_TIMEDELTA ) || (PyDelta_Check (item ))) {
1331- cLabel = int64ToIsoDuration (nanosecVal , & len );
1342+ // TODO(username): non-nano timedelta support?
1343+ cLabel = int64ToIsoDuration (i8date , & len );
13321344 } else {
13331345 if (type_num == NPY_DATETIME ) {
1334- cLabel = int64ToIso (nanosecVal , base , & len );
1346+ cLabel = int64ToIso (i8date , dateUnit , base , & len );
13351347 } else {
13361348 cLabel = PyDateTimeToIso (item , base , & len );
13371349 }
@@ -1346,7 +1358,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
13461358 int size_of_cLabel = 21 ; // 21 chars for int 64
13471359 cLabel = PyObject_Malloc (size_of_cLabel );
13481360 snprintf (cLabel , size_of_cLabel , "%" NPY_DATETIME_FMT ,
1349- NpyDateTimeToEpoch (nanosecVal , base ));
1361+ NpyDateTimeToEpoch (i8date , base ));
13501362 len = strlen (cLabel );
13511363 }
13521364 }
@@ -1538,13 +1550,25 @@ void Object_beginTypeContext(JSOBJ _obj, JSONTypeContext *tc) {
15381550 tc -> type = JT_UTF8 ;
15391551 return ;
15401552 } else if (PyArray_IsScalar (obj , Datetime )) {
1553+ npy_int64 longVal ;
15411554 if (((PyDatetimeScalarObject * )obj )-> obval == get_nat ()) {
15421555 tc -> type = JT_NULL ;
15431556 return ;
15441557 }
1558+ PyArray_Descr * dtype = PyArray_DescrFromScalar (obj );
1559+ if (!PyTypeNum_ISDATETIME (dtype -> type_num )) {
1560+ PyErr_Format (PyExc_ValueError , "Could not get resolution of datetime" );
1561+ return ;
1562+ }
1563+
1564+ PyArray_Descr * outcode = PyArray_DescrFromType (NPY_INT64 );
1565+ PyArray_CastScalarToCtype (obj , & longVal , outcode );
1566+ Py_DECREF (outcode );
15451567
15461568 if (enc -> datetimeIso ) {
1547- pc -> PyTypeToUTF8 = PyDateTimeToIsoCallback ;
1569+ GET_TC (tc )-> longValue = longVal ;
1570+ pc -> PyTypeToUTF8 = NpyDateTimeToIsoCallback ;
1571+ enc -> valueUnit = get_datetime_metadata_from_dtype (dtype ).base ;
15481572 tc -> type = JT_UTF8 ;
15491573 } else {
15501574 NPY_DATETIMEUNIT base =
0 commit comments