Skip to content

Commit c0a79e1

Browse files
committed
Merge pull request #9028 from cpcloud/timedelta-json
Implement timedeltas for to_json
2 parents 7d13fdd + 575a884 commit c0a79e1

File tree

4 files changed

+76
-21
lines changed

4 files changed

+76
-21
lines changed

pandas/io/tests/test_json/test_pandas.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import os
55

66
import numpy as np
7-
import nose
87
from pandas import Series, DataFrame, DatetimeIndex, Timestamp
8+
from datetime import timedelta
99
import pandas as pd
1010
read_json = pd.read_json
1111

@@ -601,7 +601,6 @@ def test_url(self):
601601
self.assertEqual(result[c].dtype, 'datetime64[ns]')
602602

603603
def test_timedelta(self):
604-
from datetime import timedelta
605604
converter = lambda x: pd.to_timedelta(x,unit='ms')
606605

607606
s = Series([timedelta(23), timedelta(seconds=5)])
@@ -613,17 +612,32 @@ def test_timedelta(self):
613612
assert_frame_equal(
614613
frame, pd.read_json(frame.to_json()).apply(converter))
615614

616-
def test_default_handler(self):
617-
from datetime import timedelta
618-
619-
frame = DataFrame([timedelta(23), timedelta(seconds=5), 42])
620-
self.assertRaises(OverflowError, frame.to_json)
615+
frame = DataFrame({'a': [timedelta(23), timedelta(seconds=5)],
616+
'b': [1, 2],
617+
'c': pd.date_range(start='20130101', periods=2)})
618+
result = pd.read_json(frame.to_json(date_unit='ns'))
619+
result['a'] = pd.to_timedelta(result.a, unit='ns')
620+
result['c'] = pd.to_datetime(result.c)
621+
assert_frame_equal(frame, result)
622+
623+
def test_mixed_timedelta_datetime(self):
624+
frame = DataFrame({'a': [timedelta(23), pd.Timestamp('20130101')]},
625+
dtype=object)
626+
expected = pd.read_json(frame.to_json(date_unit='ns'),
627+
dtype={'a': 'int64'})
628+
assert_frame_equal(DataFrame({'a': [pd.Timedelta(frame.a[0]).value,
629+
pd.Timestamp(frame.a[1]).value]}),
630+
expected)
621631

622-
expected = DataFrame([str(timedelta(23)), str(timedelta(seconds=5)), 42])
623-
assert_frame_equal(
624-
expected, pd.read_json(frame.to_json(default_handler=str)))
632+
def test_default_handler(self):
633+
value = object()
634+
frame = DataFrame({'a': ['a', value]})
635+
expected = frame.applymap(str)
636+
result = pd.read_json(frame.to_json(default_handler=str))
637+
assert_frame_equal(expected, result)
625638

639+
def test_default_handler_raises(self):
626640
def my_handler_raises(obj):
627641
raise TypeError("raisin")
628-
self.assertRaises(TypeError, frame.to_json,
642+
self.assertRaises(TypeError, DataFrame({'a': [1, 2, object()]}).to_json,
629643
default_handler=my_handler_raises)

pandas/src/datetime_helper.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
#include "datetime.h"
22

3+
#if PY_MAJOR_VERSION >= 3
4+
#define PyInt_AS_LONG PyLong_AsLong
5+
#endif
6+
37
void mangle_nat(PyObject *val) {
48
PyDateTime_GET_MONTH(val) = -1;
59
PyDateTime_GET_DAY(val) = -1;
610
}
11+
12+
long get_long_attr(PyObject *o, const char *attr) {
13+
return PyInt_AS_LONG(PyObject_GetAttrString(o, attr));
14+
}
15+
16+
double total_seconds(PyObject *td) {
17+
// Python 2.6 compat
18+
long microseconds = get_long_attr(td, "microseconds");
19+
long seconds = get_long_attr(td, "seconds");
20+
long days = get_long_attr(td, "days");
21+
long days_in_seconds = days * 24 * 3600;
22+
return (microseconds + (seconds + days_in_seconds) * 1000000.0) / 1000000.0;
23+
}

pandas/src/ujson/python/objToJSON.c

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ Numeric decoder derived from from TCL library
4141
#include <numpy/arrayscalars.h>
4242
#include <np_datetime.h>
4343
#include <np_datetime_strings.h>
44+
#include <datetime_helper.h>
4445
#include <numpy_helper.h>
4546
#include <numpy/npy_math.h>
4647
#include <math.h>
4748
#include <stdio.h>
48-
#include <datetime.h>
4949
#include <ultrajson.h>
5050

5151
static PyObject* type_decimal;
@@ -154,6 +154,7 @@ enum PANDAS_FORMAT
154154
// import_array() compat
155155
#if (PY_VERSION_HEX >= 0x03000000)
156156
void *initObjToJSON(void)
157+
157158
#else
158159
void initObjToJSON(void)
159160
#endif
@@ -1445,14 +1446,38 @@ void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
14451446

14461447
PRINTMARK();
14471448
pc->PyTypeToJSON = NpyDateTimeToJSON;
1448-
if (enc->datetimeIso)
1449-
{
1450-
tc->type = JT_UTF8;
1451-
}
1449+
tc->type = enc->datetimeIso ? JT_UTF8 : JT_LONG;
1450+
return;
1451+
}
1452+
else
1453+
if (PyDelta_Check(obj))
1454+
{
1455+
long value;
1456+
1457+
if (PyObject_HasAttrString(obj, "value"))
1458+
value = get_long_attr(obj, "value");
14521459
else
1460+
value = total_seconds(obj) * 1000000000; // nanoseconds per second
1461+
1462+
exc = PyErr_Occurred();
1463+
1464+
if (exc && PyErr_ExceptionMatches(PyExc_OverflowError))
14531465
{
1454-
tc->type = JT_LONG;
1466+
PRINTMARK();
1467+
goto INVALID;
14551468
}
1469+
1470+
if (value == get_nat()) {
1471+
PRINTMARK();
1472+
tc->type = JT_NULL;
1473+
return;
1474+
}
1475+
1476+
GET_TC(tc)->longValue = value;
1477+
1478+
PRINTMARK();
1479+
pc->PyTypeToJSON = PyLongToINT64;
1480+
tc->type = JT_LONG;
14561481
return;
14571482
}
14581483
else

pandas/tslib.pyx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ cdef extern from "Python.h":
2020
cdef PyTypeObject *Py_TYPE(object)
2121
int PySlice_Check(object)
2222

23+
cdef extern from "datetime_helper.h":
24+
double total_seconds(object)
25+
2326
# this is our datetime.pxd
2427
from datetime cimport *
2528
from util cimport is_integer_object, is_float_object, is_datetime64_object, is_timedelta64_object
@@ -2753,10 +2756,6 @@ cdef object _get_deltas(object tz):
27532756

27542757
return utc_offset_cache[cache_key]
27552758

2756-
cdef double total_seconds(object td): # Python 2.6 compat
2757-
return ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) //
2758-
10**6)
2759-
27602759
def tot_seconds(td):
27612760
return total_seconds(td)
27622761

0 commit comments

Comments
 (0)