Skip to content

Commit 07be4dc

Browse files
committed
Fix timedeltas to work with to_json
1 parent 7d13fdd commit 07be4dc

File tree

4 files changed

+79
-17
lines changed

4 files changed

+79
-17
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: 34 additions & 2 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
@@ -1447,7 +1448,7 @@ void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
14471448
pc->PyTypeToJSON = NpyDateTimeToJSON;
14481449
if (enc->datetimeIso)
14491450
{
1450-
tc->type = JT_UTF8;
1451+
tc->type = JT_UTF8;
14511452
}
14521453
else
14531454
{
@@ -1456,6 +1457,37 @@ void Object_beginTypeContext (JSOBJ _obj, JSONTypeContext *tc)
14561457
return;
14571458
}
14581459
else
1460+
if (PyDelta_Check(obj))
1461+
{
1462+
long value;
1463+
1464+
if (PyObject_HasAttrString(obj, "value"))
1465+
value = get_long_attr(obj, "value");
1466+
else
1467+
value = total_seconds(obj) * 1000000000; // nanoseconds per second
1468+
1469+
exc = PyErr_Occurred();
1470+
1471+
if (exc && PyErr_ExceptionMatches(PyExc_OverflowError))
1472+
{
1473+
PRINTMARK();
1474+
goto INVALID;
1475+
}
1476+
1477+
if (value == get_nat()) {
1478+
PRINTMARK();
1479+
tc->type = JT_NULL;
1480+
return;
1481+
}
1482+
1483+
GET_TC(tc)->longValue = value;
1484+
1485+
PRINTMARK();
1486+
pc->PyTypeToJSON = PyLongToINT64;
1487+
tc->type = JT_LONG;
1488+
return;
1489+
}
1490+
else
14591491
if (PyArray_IsScalar(obj, Integer))
14601492
{
14611493
PRINTMARK();

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)