Skip to content

Mark Embling

Blog

JSON and Dates/Times

Lately I've noticed that JSON and dates/times do not go together well. JSON allows integers, fractional numbers, strings, arrays and so on, but no way to natively specify a point in time. It turns out this relatively minor-sounding point can cause a fair bit more pain than it probably should.

Current Options

It seems there are at least three relatively common methods of representation of a point in time in JSON.

// Serialise to a string representing the date & time
{"point_in_time": "2011-07-14 19:43:37 +0100"}    // Ruby does this
{"point_in_time": "2011-07-14T19:43:37+0100"}     // ISO 8601

// Javascript Date object
{"point_in_time": new Date(1310669017000)}

// Common in the .NET world (again, a string)
{"point_in_time": "\/Date(1310669017000)\/"}

It's clear that with each of these there are some difficulties.

String Representation (Human-readable/ISO 8601)

Serialising to a string as in the first two samples above is a particularly difficult one since there is no way to know at time of deserialisation whether it is actually a date or a string which just happens to have the content of a date in it. What normally happens here is the deserialiser will just assume it is a string.

Since we just get a string back, it'd be up to the deserialiser - or the application - to convert this string back into a useful Date/time representation. Not really ideal. It's worth mentioning though that it is by far the best option if human-readability is a concern.

JavaScript Date Object

The JavaScript date object option on the face of it seems sensible and also seems suited to situations where the resultant JSON is going to be consumed by a browser. However again, all is not what it seems.

When the JSON is consumed by a browser which does not have native JSON parsing built in, it'll generally be executed to reconstruct the data into a native JavaScript object. For example, jQuery uses the following method.

var data = (new Function( "return " + json))();

If we use the JavaScript date object approach here, it works perfectly. The call for a new Date is simply executed and it ends up in the final result.

The problem is when it is given to a browser which does have native JSON parsing, it will not work. Native parsing ought to be faster and jQuery will always use it if it's available. I tried the following and this is what happened:

$(function(){
    var json = '{"point_in_time": new Date(1310669017000)}';
    var data = $.parseJSON(json);
    console.log(data);
});

JSON error in Chrome
Chrome (version 12)

JSON error in Firefox 5
Firefox 5 (with Firebug)

JSON error in IE9
Internet Explorer 9

Definitely not something which is going to work for browsers then. The problem is that it is not actually valid JSON at all according to the spec. Choking on it is a perfectly acceptable reaction for a parser to have. Just to round off, I tried it in Ruby as well:

ruby-1.9.2-p180 :003 > JSON.parse('{"timestamp": new Date(1310679502)}')
JSON::ParserError: 706: unexpected token at '{"timestamp": new Date(1310679502)}'
    from /home/mark/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/json/common.rb:146:in `parse'
    from /home/mark/.rvm/rubies/ruby-1.9.2-p180/lib/ruby/1.9.1/json/common.rb:146:in `parse'
    from (irb):3
    from /home/mark/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'

Don't think that approach is up to much then.

Crazy .NET String Representation

Ok so it's actually not that crazy. This is the format Microsoft decided to use and despite how it looks, it is actually a pretty good workaround. Bertrand Le Roy posted a great explanation of it on his blog.

James Newton-King also adopted this option in his much-loved Json.NET project, but leaves the option open to use various different formats. I think this sort of open-ended customisable approach is probably the best way around the problem for now.

Unix Timestamps

A different take might be to forget about trying to store the actual date itself and store a unix timestamp instead. Since a unix timestamp is just the number of seconds since 1 Jan 1970 00:00:00 (UTC), all we need to store is a simple integer.

{"point_in_time": 1310669017}

Doing things this way is nice and simple, but it does require knowledge on either end - that is, the serialiser and deserialiser both need to know explicitly which numbers represent dates. This is almost the same problem as we had originally with the string-based approach - that means the smarts to convert to and from this representation will probably end up being pushed up a level into the applications themselves, and nobody wants that.

MongoDB's Answer

MongoDB stores data in BSON and also speaks BSON to clients. The language/platform-specific drivers are responsible for serialising and deserialising this to the platform's native types. BSON also defines extra types including dates in addition to those supported by JSON.

The reason I bring this up is because MongoDB can also expose a REST interface which uses none other than our good friend JSON. Because of this, they've also had to contend with the question of how these various extras ought to be represented. The answer they came up with is to have the ability to have several different modes which are discussed in more detail in the documentation:

  • Strict - nothing is generated which is not legal according to the JSON spec
  • JS - uses some JavaScript types to represent some things
  • 10Gen - like the JS option, but also uses some 10Gen-specific types

When we are talking about dates, this means we end up with the following:

// Strict
{"point_in_time": { $date: <ms> }}  // ms = milliseconds since unix epoch

// JS and 10Gen
{"point_in_time": Date( <ms> )}     // ms = same as above

The thing here is that we find again we have a couple of new options. Even the JS option actually produces something different to the previous JS solution we had before - but still just as incompatible.

Working with the MongoDB shell (which itself is JS-based) gives us yet another representation:

MongoDB shell version: 1.8.2
connecting to: test
> db.tests.insert({'ts':new Date()})
> db.tests.find()
{ "_id" : ObjectId("4e1f671d671bd7812369551e"), "ts" : ISODate("2011-07-14T22:01:01.947Z") }

Not sure what to really make of that.

Solution

Unfortunately there is no good answer. It is more a case of picking an approach which best fits what you are trying to do and just go with that. At this point we are very much working around the limitations of JSON and the crazy oversight in the spec. Any of the available approaches are going to need a bit of work by either the parser or your own application in order to get everything back to how you want it. Having various parser libraries make it easy to change the format is definitely helpful.

If I had to pick the best of the bunch from the solutions we currently have that work, I guess it would have to be the Microsoft approach. Whilst it looks rather peculiar and makes use of a strange quirk of the spec, it is probably the best compromise.

Given the power to change the spec, I would definitely make it a priority to get some sort of sensible representation of time into it. To me, the most correct of all the options is the MongoDB JS/10Gen option. That said, I don't think it really matters. What does matter is that if and when there was a common representation agreed upon in the spec, authors of the various browsers and JSON libraries could converge on that solution. And that would make my life, and I suspect quite a lot of other people's lives, much easier.

Return to blog