On the nightmare that is JSON Dates. Plus, JSON.NET and ASP.NET Web API

March 6, 2012 12:15 AM Comments [63] Posted in ASP.NET | Javascript | Open Source

Ints are easy. Strings are mostly easy. Dates? A nightmare. They always will be. There's different calendars, different formats. Did you know it's 2004 in the Ethiopian Calendar? Yakatit 26, 2004, in fact. I spoke to a German friend once about how much 9/11 affected me and he said, "yes, November 9th was an amazing day in Germany, also."

Dates are hard.

If I take a simple model:

public class Post
{
public int ID { get; set; }

[StringLength(60)][Required]
public string Title { get; set; }

[StringLength(500)]
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }

public DateTime PublishedAt { get; set; }
}

And I make a quick ASP.NET Web API controller from VS11 Beta (snipped some stuff for simplicity):

public class PostAPIController : ApiController
{
private BlogContext db = new BlogContext();

// GET /api/post
public IEnumerable<Post> Get()
{
return db.Posts.ToList();
}

// GET /api/post/5
public Post Get(int id)
{
return db.Posts.Where(p => p.ID == id).Single();
}
...snip...
}

And hit /api/post with this Knockout View Model and jQuery.

$(function () {
$("#getPosts").click(function () {
// We're using a Knockout model. This clears out the existing posts.
viewModel.posts([]);

$.get('/api/PostAPI', function (data) {
// Update the Knockout model (and thus the UI)
// with the posts received back
// from the Web API call.
viewModel.posts(data);
});
});

viewModel = {
posts: ko.observableArray([])
};

ko.applyBindings(viewModel);
});

And this super basic template:

<li class="comment">
<header>
<div class="info">
<strong><span data-bind="text: Title"></span></strong>
</div>
</header>
<div class="body">
<p data-bind="date: PublishedAt"></p>
<p data-bind="text: Text"></p>
</div>
</li>

I am saddened as the date binding doesn't work, because the date was serialized by default like this. Here's the JSON on the wire.

[{
"ID": 1,
"PublishedAt": "\/Date(1330848000000-0800)\/",
"Text": "Best blog post ever",
"Title": "Magical Title"
}, {
"ID": 2,
"PublishedAt": "\/Date(1320825600000-0800)\/",
"Text": "No, really",
"Title": "You rock"
}]

Eek! My eyes! That's milliseconds since the beginning of the Unix Epoch WITH a TimeZone. So, converting in PowerShell looks like:

PS C:\> (new-object DateTime(1970,1,1,0,0,0,0)).AddMilliseconds(1330848000000).AddHours(-8)

Sunday, March 04, 2012 12:00:00 AM

Yuck. Regardless,  it doesn't bind with KnockoutJS either. I could add a bindingHandler for dates like this:

ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var jsonDate = valueAccessor();
var value = new Date(parseInt(jsonDate.substr(6)));
var ret = value.getMonth() + 1 + "/" + value.getDate() + "/" + value.getFullYear();
element.innerHTML = ret;
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
}
};

That works, but it's horrible and I hate myself. It's lousy parsing and it doesn't even take the TimeZone into consideration. This is a silly format for a date to be in on the wire.

spacer

I was talking to some folks on Twitter in the last few days and said that all this is silly and JSON dates should be ISO 8601, and we should all move on. James Newton-King the author of JSON.NET answered by making ISO 8601 the default in his library. We on the web team will be including JSON.NET as the default JSON Serializer in Web API when it releases, so that'll be nice.

I mentioned this to Raffi from Twitter a few weeks back and he agreeds. He tweeted back to me

@shanselman if (when) we ship a v2 API, you can almost bet its going to be 8601 /cc @twitterAPI @johnsheehan

— Raffi Krikorian (@raffi) March 4, 2012

He also added "please don't do what the @twitterAPI does (ruby strings)." What does that look like? Well, see for yourself: https://www.twitter.com/statuses/public_timeline.json in a random public timeline tweet...snipped out the boring stuff...

{
"id_str": "176815815037952000",
"user": {
"id": 455349633,
...snip...
"time_zone": null
},
"id": 176815815037952000,
"created_at": "Mon Mar 05 23:45:50 +0000 2012"
}

Yes, so DON'T do it that way. Let's just do it the JavaScript 1.8.5/ECMASCript 5th way and stop talking about it. Here's Firefox, Chrome and IE.

spacer

We're going to do this by default in ASP.NET Web API when it releases. (We aren't doing this now in Beta) You can see how to swap out the serializer to JSON.NET on Henrik's blog. You can also check out the Thinktecture.Web.Http convenience methods that bundles some useful methods for ASP.NET Web API.

Today with the Beta, I just need to update my global.asax and swap out the JSON Formatter like this (see Henrik's blog for the full code):

// Create Json.Net formatter serializing DateTime using the ISO 8601 format
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
GlobalConfiguration.Configuration.Formatters[0] = new JsonNetFormatter(serializerSettings);

When we ship, none of this will be needed as it should be the default which is much nicer. JSON.NET will be the default serializer AND Web API will use ISO 8601 on the wire as the default date format for JSON APIs.

spacer

Hope this helps.


Sponsor: Big thanks to DevExpress for sponsoring this last week's feed. There is no better time to discover DevExpress. Visual Studio 11 beta is here and DevExpress tools are ready! Experience next generation tools, today.

Comments [63] Tweet
Permalink
« How To Guide to Installing and Booting W... | Home | Dark Matter Developers: The Unseen 99% »
Tuesday, 06 March 2012 00:21:02 UTC
hmmm, my 1-liner to parse WCF JSON Dates :)

function date(s) { return new Date(parseFloat(/Date\(([^)]+)\)/.exec(s)[1])); }


Taken from my JSON prettifier: www.ajaxstack.com/jsonreport/
spacer spacer Demis Bellot
Tuesday, 06 March 2012 00:24:01 UTC
Boy am I glad I saw this post! I just put together a simple (or so I thought) app to try this out and was about to google the goofy date that was returned. You just saved me a lot of time and effort!
spacer spacer Eric Miller
Tuesday, 06 March 2012 00:25:43 UTC
Actually crap:

new Date(parseInt(jsonDate.substr(6)));

Is much nicer, surprised that works... testing...
spacer spacer Demis Bellot
Tuesday, 06 March 2012 00:27:24 UTC
Great job, Scott. That's the one thing about JSON that I could just never understand how it got that way...
spacer PRMan
Tuesday, 06 March 2012 00:29:56 UTC
@PRMan this provides a good explanation of why JSON dates are the way they are:

msdn.microsoft.com/en-us/library/bb299886.aspx
spacer spacer Demis Bellot
Tuesday, 06 March 2012 00:31:36 UTC
Ironically, the posting date for this entry says this hasn't happened for another eight hours.

"March 6, 2012 12:15 AM"

UTC conversion issue? :P
Kevin Clayborn
Tuesday, 06 March 2012 00:34:03 UTC
Dates are such a common data type that it seems absurd that JSON didn't bother including this in the standard.

"A value can be a string in double quotes, or a number, or true or false or null, or an object or an array. These structures can be nested." JSON.org


The latest version of Sugar handles the ugly date format from the default ASP.Net JSON serializer, but I really like using a standard date format, especially 8601. Hopefully all the JSON libraries will recognize this format.
spacer spacer Brian Brewder
Tuesday, 06 March 2012 00:39:35 UTC
So, you are introducing a BREAKING CHANGE with this. Just GREAT!!!

I know you may hate the existing datetime format (I do to), but existing sites really rely on that, and this artical on MSDN Stand-Alone JSON Serialization.

Basically, I can't just update to use this instead of the existing api for a few reasons:
1) Breaking the contract - We have told our users that the date has to be in this format
2) We have a strict restriction on using 3rd party libraries, so now you are DEFAULTING to it?? Our legal department will have a fit!
3) This also means you won't be using the DataContractJsonSeralizer anymore? What about WCF? Is it also switching, or do we have issues within our own app (WCF expecting one format, and this giving another).

PLEASE DO NOT set JSON.NET as the default, but make it available, and fix the DataContractJsonSerializer to support more date formats than the \/Date(xxx)\/ one.
spacer Chad
Tuesday, 06 March 2012 00:45:27 UTC
This is lousy:

new Date(parseInt(jsonDate.substr(6)));

Don't do this, you are losing your timezone information.

Instead, just remove the \\ and / from the ASPNET string:

"\\/Date(1320825600000-0800)\\/".replace( /[\\/]/g, "")

This gives you:

Date(1320825600000-0800)

To convert this back into a JSON date, you can "new" the object in an eval:

var d = eval ("new " + "\\/Date(1320825600000-0800)\\/".replace( /[\\/]/g, ""))

spacer spacer John Liu
Tuesday, 06 March 2012 00:50:12 UTC
Great news! Sometimes standards really do make things easier, this one especially. Newton-King's JSON.Net really kicks butt too.

Also, I just finally setup a W8 box and played a little with VS11 Beta and the WebAPI. The OS experience is a little disheartening yet, but I'm digging VS and WebAPI a lot... anxious to work with it more.
spacer spacer Trey White
Tuesday, 06 March 2012 00:51:14 UTC
Although the default date format of Json.NET is changing in Json.NET 4.5 (the next major release), there is a an option to continue using the Microsoft format.
spacer spacer James Newton-King
Tuesday, 06 March 2012 01:03:22 UTC
My concern is the same. If I just upgrade, without making any code changes, it will change the format. This release was SUPPOSED to be an inline update that DID NOT have any breaking changes - THIS IS ONE.

And then, what about, like us, a large corp that can't update everything at once, we have some using the old and some using the new format.

This means, even if we could get by our legal department (they look really hard at the license agreement) that we can NEVER use the new format, as it won't work with the systems that still require the old, unless they update DataContractJsonSerializer with support for more dates.
spacer Chad
Tuesday, 06 March 2012 01:22:23 UTC
Normal WCF JSON serialization isn't changing, and Web API is still in beta.

If you want to continue using DataContractJsonSerializer then why not create a MediaTypeFormatter that uses DataContractJsonSerializer? If Microsoft doesn't include one out of the box then I'm sure someone else will make one.

On licensing, Json.NET is the standard MIT license.
spacer spacer James Newton-King
Tuesday, 06 March 2012 01:22:39 UTC
Chad - You don't use modernizr, Jquery, jqueryUI or any of the other 3rd party libs? This will be supported Open Source just like our existing uses I open source. Want to use a different default? Easy to switch back. Remember that when we include open source, we back it with support. If you call and have a problem with ASP.NET, we will support all of it, including the open source we ship. Does that help? We have been doing that for 3+ years now.

Also, Chad - we are changing only ASP.NET Web API...A product that isn't released. We aren't changing asp.net proper or WCF. Better? There is no breaking change as this is a new product, only now in beta.
spacer Scott Hanselman
Tuesday, 06 March 2012 01:28:28 UTC
Presumably, only code that is using the new Web API framework will be using the new JSON.NET and therefore the updated date format.

All of your old code is most likely fine as it wasn't written to target the ApiController.

I don't see how this is a breaking change.
spacer Ken Stone
Tuesday, 06 March 2012 01:34:34 UTC
@Chad,

I don't think you read the post correctly. This change to the default serializer is happening in the ASP.NET Web API, not .NET. The standalone serializer isn't being removed. This change is only happening between the Web API beta and the final RTM. If your corporation is as large as you say it is, I seriously doubt you have production code deployed on a Web API beta that was only released a couple weeks ago.

TLDR: Nobody moved your cheese. Everything will be okay.
spacer spacer Scott Anderson
Tuesday, 06 March 2012 01:41:29 UTC
OK, so now only the webapi is updated, that is better, but we still won't be able to use it.

I was more making a point about the library (as we already use JSON.NET in some situations.

The problem I see now is one of incompatibility. If you aren't updating WCF, or ASP.NET, dates creating in one won't work in the other. So how do you suggest calling an existing service when you have a DateTime as a property in the serialized object? How do you know what format to put it in?

I know that JSON.NET can handle it, but it requires a special date handler, and DataContractJsonSerializer won't handle the new format. Can there at least be updates to make it EASILY work between services, without having to know what date format it is in.

DateTime.Parse handles several, but not the existing DataContractJsonSerailizer format, and then there is JavascriptSerializer - which is different.

I really like the simplicity of the DataContractJsonSerializer, and have heard several of my coworkers complain about JSON.NET.
spacer Chad
Tuesday, 06 March 2012 01:45:18 UTC
@Chad,

In those cases, you can force JSON.NET to use the Microsoft form instead of the 8601 standard as pointed out by James in a comment above.
spacer spacer Scott Anderson
Tuesday, 06 March 2012 01:45:19 UTC
'Bout time- all it takes is a decent critical mass of libraries doing it the same way to get a de facto standard. This has been biting me in the a*s for at least the last 5 years- I'll be more than happy to throw away all that conversion code I've been patching in at various layers.
spacer Matt Davis
Tuesday, 06 March 2012 01:45:53 UTC
Or you could override the default serializer in the Web API to use the same one you use everywhere else. (Sorry to double post!)
spacer spacer Scott Anderson
Tuesday, 06 March 2012 02:01:35 UTC
Sure, we could, but again, can't we make all of the MS serializers consistant, or at least deserialize both the existing format and 8601 standard?
spacer Chad
Tuesday, 06 March 2012 02:33:57 UTC
How can I swap out for JSON.NET in all of ASP.NET?:
- JsonValueProvider for MVC model binding
- JsonResult from an MVC method
- Web API
- asmx services
- etc
Is there a voodoo initialize method(s) that says "just go do it for everything" I can call from Global.asax.cs?
spacer Rob
Tuesday, 06 March 2012 02:46:45 UTC
So glad to see this! I just recently ran into this issue with MVC3 JsonResult. Parsing it correctly client-side isn't the end of the world, but it's the little things that make all the difference. I get more excited for VS2011 with the 4.5 Framework and all that's coming with them everyday. Keep up the good work sir!
spacer spacer Alexander Kahoun
Tuesday, 06 March 2012 02:51:23 UTC
yes, it's a good feature. the format "\/Date(1320825600000-0800)\/" is weird.
spacer spacer Qijun Feng
Tuesday, 06 March 2012 03:11:10 UTC
This is beyond fantastic news. I've been on a couple of projects recently where we were using vanilla MVC as an API endpoint, and the first thing I did was write a model binder to use JSON.NET for all of our serialization needs (because of the way it handles dates a
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.