├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── README.rst ├── jsondatetime ├── __init__.py ├── jsondatetime.py └── tests │ ├── __init__.py │ └── test_jsondatetime.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | # SublimeText project files 30 | *.sublime-* 31 | 32 | #virtualenv 33 | Include 34 | Lib 35 | Scripts 36 | 37 | #OSX 38 | .Python 39 | .DS_Store 40 | 41 | #Pycharm 42 | .idea 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: "pip install -r requirements.txt" 5 | script: python setup.py test 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Nicola Iarocci. 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | LICENSE 3 | README.md 4 | setup.py 5 | jsondatetime/__init__.py 6 | jsondatetime/jsondatetime.py 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | JSON-datetime 2 | ============= 3 | .. image:: https://secure.travis-ci.org/nicolaiarocci/json-datetime.png?branch=master 4 | :target: https://secure.travis-ci.org/nicolaiarocci/json-datetime 5 | 6 | JSON-datetime allows for proper decoding of datetime values contained in JSON 7 | streams. 8 | 9 | The problem 10 | ----------- 11 | The JSON standard RFC 4627 does not 12 | support datetime types. These are usually represented as strings and Python 13 | decoders end up decoding them as such. Consider the following example: 14 | 15 | .. code-block:: python 16 | 17 | import simplejson as json 18 | 19 | >>> test = '{"name": "John Doe", "born": "Thu, 1 Mar 2012 10:00:49 UTC"}' 20 | >>> json.loads(test) 21 | {'born': u'Thu, 1 Mar 2012 10:00:49 UTC', 'name': u'John Doe'} 22 | 23 | As you can see, in the resulting dictionary ``born`` is still a string. 24 | 25 | The solution 26 | ------------ 27 | JSON-datetime is a very simple wrapper around Python simplejson ``loads`` 28 | method. It decodes datetime values contained in JSON strings: 29 | 30 | .. code-block:: python 31 | 32 | import jsondatetime as json 33 | 34 | >>> test = '{"name": "John Doe", "born": "Thu, 1 Mar 2012 10:00:49 UTC"}' 35 | >>> json.loads(test) 36 | {'name': 'John Doe', 'born': datetime.datetime(2012, 3, 1, 10, 0 ,49)} 37 | 38 | Strings are parsed using ``dateutil.parser.parse`` which is fairly flexible for 39 | common datetime formats 40 | 41 | Custom parsing 42 | -------------- 43 | Being just a wrapper around the ``loads`` method, you can still use all the 44 | standard ``loads`` arguments, ``object_hook`` included. This means that you can 45 | still perform custom parsing of your inbound JSON stream. 46 | 47 | Installation 48 | ------------ 49 | ``pip install json-datetime`` 50 | -------------------------------------------------------------------------------- /jsondatetime/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .jsondatetime import loads, dumps 3 | -------------------------------------------------------------------------------- /jsondatetime/jsondatetime.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | import datetime 4 | import dateutil.parser 5 | 6 | try: 7 | string_types = basestring # Python 2 8 | except NameError: 9 | string_types = str # Python 3 10 | 11 | DEFAULT_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S UTC' 12 | DEFAULT_ARGUMENT = "datetime_format" 13 | PY2 = sys.version_info[0] == 2 14 | 15 | 16 | class DatetimeJSONEncoder(json.JSONEncoder): 17 | 18 | def default(self, obj): 19 | if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date): 20 | return obj.isoformat() 21 | else: 22 | return json.JSONEncoder().default(obj) 23 | 24 | 25 | json._default_encoder = DatetimeJSONEncoder() 26 | 27 | 28 | def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, 29 | allow_nan=True, cls=None, indent=None, separators=None, 30 | encoding='utf-8', default=None, sort_keys=False, **kw): 31 | if PY2: 32 | kw.update({"encoding": encoding}) 33 | return json.dumps(obj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, 34 | check_circular=check_circular, allow_nan=allow_nan, 35 | cls=cls, indent=indent, separators=separators, 36 | default=default, sort_keys=sort_keys, **kw) 37 | 38 | 39 | def loads(s, **kwargs): 40 | source = json.loads(s, **kwargs) 41 | 42 | return iteritems(source) 43 | 44 | 45 | def iteritems(source): 46 | 47 | for k, v in source.items(): 48 | if isinstance(v, list): 49 | for a in v: 50 | iteritems(a) 51 | elif isinstance(v, dict): 52 | iteritems(v) 53 | elif isinstance(v, string_types) and not v.isdigit(): 54 | try: 55 | float(v) 56 | continue 57 | except ValueError: 58 | pass 59 | try: 60 | source[k] = dateutil.parser.parse(v, ignoretz=True) 61 | except (ValueError, OverflowError): 62 | pass 63 | 64 | return source 65 | -------------------------------------------------------------------------------- /jsondatetime/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolaiarocci/json-datetime/08b31209654445d56fe8f4aa3efca38316015a5c/jsondatetime/tests/__init__.py -------------------------------------------------------------------------------- /jsondatetime/tests/test_jsondatetime.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import jsondatetime as json 4 | import datetime 5 | 6 | class TestBase(TestCase): 7 | 8 | def setUp(self): 9 | self.test = '{"name": "John Doe", "born": "Thu, 1 Mar 2012 10:00:49 UTC"}' 10 | self.expected = datetime.datetime(2012, 3, 1, 10, 0 ,49) 11 | self.datetime_format = '%a, %d %b %Y %H:%M:%S UTC' 12 | 13 | def test_no_dates(self): 14 | test = '{"name": "John Doe"}' 15 | try: 16 | json.loads(test) 17 | except Exception as e: 18 | self.fail("Unexpected failure: %s" % e) 19 | 20 | def test_default_date_format(self): 21 | decoded = json.loads(self.test).get('born') 22 | self.assertIs(type(decoded), datetime.datetime) 23 | self.assertEqual(decoded, self.expected) 24 | 25 | def test_date_format(self): 26 | test = '{"born": "Thu, 1 Mar 2012"}' 27 | expected = datetime.datetime(2012, 3, 1) 28 | decoded = json.loads(test).get('born') 29 | self.assertIs(type(decoded), datetime.datetime) 30 | self.assertEqual(decoded, expected) 31 | 32 | def test_object_hook(self): 33 | decoded = json.loads(self.test, object_hook=self.hook) 34 | self.assertEqual(decoded.get('born'), self.expected) 35 | self.assertIn("hookjob", decoded) 36 | 37 | def test_nested_dicts(self): 38 | test = '{"updated": {"$gte": "Thu, 1 Mar 2012 10:00:49 UTC"}}' 39 | decoded = json.loads(test).get('updated').get('$gte') 40 | self.assertIs(type(decoded), datetime.datetime) 41 | self.assertEqual(decoded, self.expected) 42 | 43 | def test_numeric_value(self): 44 | decoded = json.loads('{"key": "2"}') 45 | self.assertEqual(decoded.get('key'), "2") 46 | 47 | def test_float_value(self): 48 | decoded = json.loads('{"key": "2.5"}') 49 | self.assertEqual(decoded.get('key'), "2.5") 50 | 51 | def test_json_dumps(self): 52 | json.dumps({"x": 1}) 53 | 54 | 55 | def hook(self, dct): 56 | dct["hookjob"] = "I'm hooked!" 57 | return dct 58 | 59 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dateutil -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | DESCRIPTION = ("Allows for proper decoding of datetime values contained in " 6 | "JSON streams") 7 | LONG_DESCRIPTION = open('README.rst').read() 8 | 9 | setup( 10 | name='JSON-Datetime', 11 | version='0.0.5', 12 | description=DESCRIPTION, 13 | long_description=LONG_DESCRIPTION, 14 | author='Nicola Iarocci', 15 | author_email='nicola@nicolaiarocci.com', 16 | url='http://github.com/nicolaiarocci/json-datetime', 17 | license=open('LICENSE').read(), 18 | platforms=["any"], 19 | packages=find_packages(), 20 | test_suite="jsondatetime.tests", 21 | requires=['simplejson'], 22 | install_requires=['simplejson'], 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: ISC License (ISCL)', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python :: 2.5', 29 | 'Programming Language :: Python :: 2.6', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3', 32 | ], 33 | ) 34 | --------------------------------------------------------------------------------