@@ -131,6 +131,7 @@ typedef struct __PyObjectEncoder {
131
131
132
132
int datetimeIso ;
133
133
NPY_DATETIMEUNIT datetimeUnit ;
134
+ NPY_DATETIMEUNIT valueUnit ;
134
135
135
136
// output format style for pandas data types
136
137
int outputFormat ;
@@ -350,7 +351,8 @@ static char *PyUnicodeToUTF8(JSOBJ _obj, JSONTypeContext *tc,
350
351
static char * NpyDateTimeToIsoCallback (JSOBJ Py_UNUSED (unused ),
351
352
JSONTypeContext * tc , size_t * len ) {
352
353
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 );
354
356
return GET_TC (tc )-> cStr ;
355
357
}
356
358
@@ -364,8 +366,9 @@ static char *NpyTimeDeltaToIsoCallback(JSOBJ Py_UNUSED(unused),
364
366
/* JSON callback */
365
367
static char * PyDateTimeToIsoCallback (JSOBJ obj , JSONTypeContext * tc ,
366
368
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 = "" ;
369
372
return NULL ;
370
373
}
371
374
@@ -502,6 +505,10 @@ int NpyArr_iterNextItem(JSOBJ obj, JSONTypeContext *tc) {
502
505
GET_TC (tc )-> itemValue = obj ;
503
506
Py_INCREF (obj );
504
507
((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 ;
505
512
((PyObjectEncoder * )tc -> encoder )-> npyValue = npyarr -> dataptr ;
506
513
((PyObjectEncoder * )tc -> encoder )-> npyCtxtPassthru = npyarr ;
507
514
} else {
@@ -1255,6 +1262,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
1255
1262
char * * ret ;
1256
1263
char * dataptr , * cLabel ;
1257
1264
int type_num ;
1265
+ PyArray_Descr * dtype ;
1258
1266
NPY_DATETIMEUNIT base = enc -> datetimeUnit ;
1259
1267
1260
1268
if (!labels ) {
@@ -1283,6 +1291,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
1283
1291
stride = PyArray_STRIDE (labels , 0 );
1284
1292
dataptr = PyArray_DATA (labels );
1285
1293
type_num = PyArray_TYPE (labels );
1294
+ dtype = PyArray_DESCR (labels );
1286
1295
1287
1296
for (i = 0 ; i < num ; i ++ ) {
1288
1297
item = PyArray_GETITEM (labels , dataptr );
@@ -1293,7 +1302,8 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
1293
1302
}
1294
1303
1295
1304
int is_datetimelike = 0 ;
1296
- npy_int64 nanosecVal ;
1305
+ npy_int64 i8date ;
1306
+ NPY_DATETIMEUNIT dateUnit = NPY_FR_ns ;
1297
1307
if (PyTypeNum_ISDATETIME (type_num )) {
1298
1308
is_datetimelike = 1 ;
1299
1309
PyArray_VectorUnaryFunc * castfunc =
@@ -1303,35 +1313,37 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
1303
1313
"Cannot cast numpy dtype %d to long" ,
1304
1314
enc -> npyType );
1305
1315
}
1306
- castfunc (dataptr , & nanosecVal , 1 , NULL , NULL );
1316
+ castfunc (dataptr , & i8date , 1 , NULL , NULL );
1317
+ dateUnit = get_datetime_metadata_from_dtype (dtype ).base ;
1307
1318
} else if (PyDate_Check (item ) || PyDelta_Check (item )) {
1308
1319
is_datetimelike = 1 ;
1309
1320
if (PyObject_HasAttrString (item , "_value" )) {
1310
1321
// 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" );
1312
1323
} else {
1313
1324
if (PyDelta_Check (item )) {
1314
- nanosecVal = total_seconds (item ) *
1325
+ i8date = total_seconds (item ) *
1315
1326
1000000000LL ; // nanoseconds per second
1316
1327
} else {
1317
1328
// datetime.* objects don't follow above rules
1318
- nanosecVal = PyDateTimeToEpoch (item , NPY_FR_ns );
1329
+ i8date = PyDateTimeToEpoch (item , NPY_FR_ns );
1319
1330
}
1320
1331
}
1321
1332
}
1322
1333
1323
1334
if (is_datetimelike ) {
1324
- if (nanosecVal == get_nat ()) {
1335
+ if (i8date == get_nat ()) {
1325
1336
len = 4 ;
1326
1337
cLabel = PyObject_Malloc (len + 1 );
1327
1338
strncpy (cLabel , "null" , len + 1 );
1328
1339
} else {
1329
1340
if (enc -> datetimeIso ) {
1330
1341
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 );
1332
1344
} else {
1333
1345
if (type_num == NPY_DATETIME ) {
1334
- cLabel = int64ToIso (nanosecVal , base , & len );
1346
+ cLabel = int64ToIso (i8date , dateUnit , base , & len );
1335
1347
} else {
1336
1348
cLabel = PyDateTimeToIso (item , base , & len );
1337
1349
}
@@ -1346,7 +1358,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc,
1346
1358
int size_of_cLabel = 21 ; // 21 chars for int 64
1347
1359
cLabel = PyObject_Malloc (size_of_cLabel );
1348
1360
snprintf (cLabel , size_of_cLabel , "%" NPY_DATETIME_FMT ,
1349
- NpyDateTimeToEpoch (nanosecVal , base ));
1361
+ NpyDateTimeToEpoch (i8date , base ));
1350
1362
len = strlen (cLabel );
1351
1363
}
1352
1364
}
@@ -1538,13 +1550,25 @@ void Object_beginTypeContext(JSOBJ _obj, JSONTypeContext *tc) {
1538
1550
tc -> type = JT_UTF8 ;
1539
1551
return ;
1540
1552
} else if (PyArray_IsScalar (obj , Datetime )) {
1553
+ npy_int64 longVal ;
1541
1554
if (((PyDatetimeScalarObject * )obj )-> obval == get_nat ()) {
1542
1555
tc -> type = JT_NULL ;
1543
1556
return ;
1544
1557
}
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 );
1545
1567
1546
1568
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 ;
1548
1572
tc -> type = JT_UTF8 ;
1549
1573
} else {
1550
1574
NPY_DATETIMEUNIT base =
0 commit comments