Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jsonpickle datetime to readable json format

Is it possible to convert datetime into a readable JSON format (which could be used from javascript)? Currently jsonpickle provides only a binary encoded value for datetime.

like image 372
Mihai H Avatar asked Oct 16 '13 21:10

Mihai H


1 Answers

There are several gotchas here:

First, please don't traffic in timezone unaware datetime objects. You will feel pain, not today, maybe not tomorrow, but someday. You can learn from others' mistakes (mine), or you can learn the hard way. As far as I'm concerned the fact that Python allows you to make datetime objects without a timezone at all is a bug.

Second, you can't reliably roundtrip strftime() and strptime() for timezone-aware datetime objects. This was fixed for UTC in Python 3.6, but still fails for other timezones.

Instead use datetime.isoformat() and datetime.fromisoformat(). These were added to the datetime class in Python 3.7 (and backported to earlier versions).

Third, jsonpickle does not clearly document how to roll your own DatetimeHandler. So yeah, you just want something legible that you send to Javascript or whatever? The solutions above will be fine. You want something that's legible but you also want to pull it back into Python at some point? Um, trickier.

Here's a hint: when you are subclassing a library to extend its capability, look carefully at the superclass you are extending.

I would have written DatetimeHandler somewhat differently. But the following works, and contains all my hard won wisdom on the subject. Ouch.

import pytz
import jsonpickle
from datetime import datetime

class Blah(object):

    def __init__(self, blah):
        self.datetime = datetime.now(pytz.utc)
        self.blah = blah

    def to_json(self):
        return jsonpickle.encode(self)

    @classmethod
    def from_json(cls, json_str):
        return jsonpickle.decode(json_str)

class DatePickleISO8601(jsonpickle.handlers.DatetimeHandler):
    def flatten(self, obj, data):
        pickler = self.context
        if not pickler.unpicklable:
            return str(obj)
        cls, args = obj.__reduce__()
        flatten = pickler.flatten
        payload = obj.isoformat()
        args = [payload] + [flatten(i, reset=False) for i in args[1:]]
        data['__reduce__'] = (flatten(cls, reset=False), args)
        return data

    def restore(self, data):
        cls, args = data['__reduce__']
        unpickler = self.context
        restore = unpickler.restore
        cls = restore(cls, reset=False)
        value = datetime.fromisoformat(args[0])
        return value

jsonpickle.handlers.registry.register(datetime, DatePickleISO8601)
like image 109
John Stoner Avatar answered Sep 28 '22 04:09

John Stoner