Skip to content

Commit

Permalink
Add datetime mode that ignores time zones. Change iso datetime mode t…
Browse files Browse the repository at this point in the history
…o include time zone. Fixes #23.

Add docstring constants in separate file.
Add datetime mode constants to module.
  • Loading branch information
kenrobbins committed Sep 21, 2015
1 parent e33ea73 commit de1c558
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 19 deletions.
13 changes: 13 additions & 0 deletions python-rapidjson/docstrings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef DOCSTRINGS_H_
#define DOCSTRINGS_H_

static const char* rapidjson_module_docstring =
"Fast, simple JSON encoder and decoder. Based on RapidJSON C++ library.";

static const char* rapidjson_loads_docstring =
"loads(s, object_hook=None, use_decimal=False, precise_float=True, allow_nan=True)\n\nDecodes a JSON string into Python object.";

static const char* rapidjson_dumps_docstring =
"dumps(obj, skipkeys=False, ensure_ascii=True, allow_nan=True, indent=None, default=None, sort_keys=False, use_decimal=False, max_recursion_depth=2048, datetime_mode=None)\n\nEncodes Python object into a JSON string.";

#endif
85 changes: 68 additions & 17 deletions python-rapidjson/rapidjson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/error/en.h"
#include "docstrings.h"

using namespace rapidjson;

Expand Down Expand Up @@ -330,7 +331,7 @@ rapidjson_loads(PyObject* self, PyObject* args, PyObject* kwargs)
int allowNan = 1;

static char* kwlist[] = {
"obj",
"s",
"object_hook",
"use_decimal",
"precise_float",
Expand Down Expand Up @@ -438,7 +439,8 @@ struct DictItem {

enum DatetimeMode {
DATETIME_MODE_NONE = 0,
DATETIME_MODE_ISO8601 = 1
DATETIME_MODE_ISO8601 = 1,
DATETIME_MODE_ISO8601_IGNORE_TZ = 2
};

static const int MAX_RECURSION_DEPTH = 2048;
Expand Down Expand Up @@ -633,7 +635,9 @@ rapidjson_dumps_internal(
}
}
}
else if (PyDateTime_Check(object) && datetimeMode == DATETIME_MODE_ISO8601) {
else if (PyDateTime_Check(object) &&
(datetimeMode == DATETIME_MODE_ISO8601 || datetimeMode == DATETIME_MODE_ISO8601_IGNORE_TZ))
{
int year = PyDateTime_GET_YEAR(object);
int month = PyDateTime_GET_MONTH(object);
int day = PyDateTime_GET_DAY(object);
Expand All @@ -642,22 +646,61 @@ rapidjson_dumps_internal(
int sec = PyDateTime_DATE_GET_SECOND(object);
int microsec = PyDateTime_DATE_GET_MICROSECOND(object);

const int ISOFORMAT_LEN = 32;
const int ISOFORMAT_LEN = 40;
char isoformat[ISOFORMAT_LEN];
memset(isoformat, 0, ISOFORMAT_LEN);

const int TIMEZONE_LEN = 10;
char timezone[TIMEZONE_LEN];
memset(timezone, 0, TIMEZONE_LEN);

if (datetimeMode == DATETIME_MODE_ISO8601 && PyObject_HasAttrString(object, "utcoffset")) {
PyObject* utcOffset = PyObject_CallMethod(object, "utcoffset", NULL);
if (!utcOffset)
goto error;

if (utcOffset != Py_None) {
PyObject* daysObj = PyObject_GetAttrString(utcOffset, "days");
PyObject* secondsObj = PyObject_GetAttrString(utcOffset, "seconds");

if (daysObj && secondsObj) {
int days = PyLong_AsLong(daysObj);
int seconds = PyLong_AsLong(secondsObj);

int total_seconds = days * 24 * 3600 + seconds;

char sign = '+';
if (total_seconds < 0) {
sign = '-';
total_seconds = -total_seconds;
}

int tz_hour = total_seconds / 3600;
int tz_min = (total_seconds % 3600) / 60;

snprintf(timezone, TIMEZONE_LEN-1, "%c%02d:%02d", sign, tz_hour, tz_min);
}

Py_XDECREF(daysObj);
Py_XDECREF(secondsObj);
}
Py_XDECREF(utcOffset);
}

if (microsec > 0) {
snprintf(isoformat,
ISOFORMAT_LEN,
"%04d-%02d-%02dT%02d:%02d:%02d.%06d",
ISOFORMAT_LEN-1,
"%04d-%02d-%02dT%02d:%02d:%02d.%06d%s",
year, month, day,
hour, min, sec, microsec);
hour, min, sec, microsec,
timezone);
} else {
snprintf(isoformat,
ISOFORMAT_LEN,
"%04d-%02d-%02dT%02d:%02d:%02d",
ISOFORMAT_LEN-1,
"%04d-%02d-%02dT%02d:%02d:%02d%s",
year, month, day,
hour, min, sec);
hour, min, sec,
timezone);
}

writer->String(isoformat);
Expand Down Expand Up @@ -714,14 +757,15 @@ rapidjson_dumps(PyObject* self, PyObject* args, PyObject* kwargs)
int sortKeys = 0;
int useDecimal = 0;
unsigned maxRecursionDepth = MAX_RECURSION_DEPTH;
PyObject* datetimeModeObj = NULL;
DatetimeMode datetimeMode = DATETIME_MODE_NONE;

bool prettyPrint = false;
const char indentChar = ' ';
unsigned char indentCharCount = 4;

static char* kwlist[] = {
"s",
"obj",
"skipkeys",
"ensure_ascii",
"allow_nan",
Expand All @@ -733,7 +777,7 @@ rapidjson_dumps(PyObject* self, PyObject* args, PyObject* kwargs)
"datetime_mode",
NULL
};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|pppOOppIi:rapidjson.dumps",
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|pppOOppIO:rapidjson.dumps",
kwlist,
&value,
&skipKeys,
Expand All @@ -744,7 +788,7 @@ rapidjson_dumps(PyObject* self, PyObject* args, PyObject* kwargs)
&sortKeys,
&useDecimal,
&maxRecursionDepth,
&datetimeMode))
&datetimeModeObj))
return NULL;

if (defaultFn && !PyCallable_Check(defaultFn)) {
Expand All @@ -764,6 +808,10 @@ rapidjson_dumps(PyObject* self, PyObject* args, PyObject* kwargs)
}
}

if (datetimeModeObj && PyLong_Check(datetimeModeObj)) {
datetimeMode = (DatetimeMode) PyLong_AsLong(datetimeModeObj);
}

if (!prettyPrint) {
if (ensureAscii) {
GenericStringBuffer<ASCII<> > buf;
Expand Down Expand Up @@ -794,15 +842,15 @@ rapidjson_dumps(PyObject* self, PyObject* args, PyObject* kwargs)

static PyMethodDef
rapidjson_functions[] = {
{"loads", (PyCFunction) rapidjson_loads, METH_VARARGS | METH_KEYWORDS, "load object from json string"},
{"dumps", (PyCFunction) rapidjson_dumps, METH_VARARGS | METH_KEYWORDS, "dump object as json string"},
{"loads", (PyCFunction) rapidjson_loads, METH_VARARGS | METH_KEYWORDS, rapidjson_loads_docstring},
{"dumps", (PyCFunction) rapidjson_dumps, METH_VARARGS | METH_KEYWORDS, rapidjson_dumps_docstring},
{NULL, NULL, 0, NULL} /* sentinel */
};

static PyModuleDef rapidjson_module = {
PyModuleDef_HEAD_INIT,
"rapidjson",
"Python wrapper around rapidjson",
rapidjson_module_docstring,
-1,
rapidjson_functions
};
Expand All @@ -817,7 +865,6 @@ PyInit_rapidjson()
return NULL;

rapidjson_decimal_type = PyObject_GetAttrString(decimalModule, "Decimal");
Py_INCREF(rapidjson_decimal_type);
Py_DECREF(decimalModule);

PyObject* module;
Expand All @@ -826,5 +873,9 @@ PyInit_rapidjson()
if (module == NULL)
return NULL;

PyModule_AddIntConstant(module, "DATETIME_MODE_NONE", DATETIME_MODE_NONE);
PyModule_AddIntConstant(module, "DATETIME_MODE_ISO8601", DATETIME_MODE_ISO8601);
PyModule_AddIntConstant(module, "DATETIME_MODE_ISO8601_IGNORE_TZ", DATETIME_MODE_ISO8601_IGNORE_TZ);

return module;
}
34 changes: 32 additions & 2 deletions tests/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ def test_max_recursion_depth():
@pytest.mark.unit
def test_datetime_mode():
from datetime import datetime
import pytz

assert rapidjson.DATETIME_MODE_NONE == 0
assert rapidjson.DATETIME_MODE_ISO8601 == 1
assert rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ == 2

d = datetime.utcnow()
dstr = d.isoformat()
Expand All @@ -158,9 +163,34 @@ def test_datetime_mode():
rapidjson.dumps(d)

with pytest.raises(TypeError):
rapidjson.dumps(d, datetime_mode=0)
rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_NONE)

assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ) == '"%s"' % dstr

d = d.replace(tzinfo=pytz.utc)
dstr = d.isoformat()

assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ) == '"%s"' % dstr[:-6]

d = d.astimezone(pytz.timezone('Pacific/Chatham'))
dstr = d.isoformat()

assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ) == '"%s"' % dstr[:-6]

d = d.astimezone(pytz.timezone('Asia/Kathmandu'))
dstr = d.isoformat()

assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ) == '"%s"' % dstr[:-6]

d = d.astimezone(pytz.timezone('America/New_York'))
dstr = d.isoformat()

assert rapidjson.dumps(d, datetime_mode=1) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601) == '"%s"' % dstr
assert rapidjson.dumps(d, datetime_mode=rapidjson.DATETIME_MODE_ISO8601_IGNORE_TZ) == '"%s"' % dstr[:-6]


@pytest.mark.unit
Expand Down

0 comments on commit de1c558

Please sign in to comment.