I'm a bit mystified by various JSON implementations out there that don't seem to be dealing with dates correctly. I've been using Douglas Crockford's JSON implementation from JSON.org and originally I had to fix up the date processing so that it would work on both ends for serializing and deserializing. I noticed today that Douglas has actually updated the code recently and unlike the old code there's at least some thought given to date formatting but it still doesn't 'just' work.
I checked a couple of other client side JavaScript libraries including jQuery .ToJSON which also doesn't work - it just creates an empty object and fails to completely recognize dates. I mean are these people just not testing their converters with dates or am I missing something horribly obvious? <s>
Douglas Crockford's latest code does something like this for date encoding:
Date.prototype.toJSON = function () {
// Eventually, this method will be based on the date.toISOString method.
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
That works fine on the server end with the date format, but unfortunately on the clientside doing something like this fails:
var temp = { Name: "Rick", Entered: new Date() };
var txt = JSON.stringify( temp );
alert( txt );
var temp2 = JSON.parse(txt);
alert( temp2.Entered); // string display instead of Date
The problem is that the date essentially generates as a string into the JSON output, which doesn't really do anything when the output is is
Now in my own library (which use the old version of Douglas Crockford's JSON code) I have a short snippet of code added that generates a new date object like this: (part of the wwHoverPanel libray)
case 'object':
if (x) {
// RAS: Added Date Parsing
if (x.toUTCString)
return e('new Date(' + x.getUTCFullYear() + ',' + x.getUTCMonth() + ',' + x.getUTCDate() + ',' + x.getUTCHours() + ',' + x.getUTCMinutes() + ',' + x.getUTCSeconds() + ',' + x.getUTCMilliseconds() + ')' );
...
This generates dates like this:
new Date(2007,11,24,23,59,59,200)
right into the JSON and this code round trips just fine in JavaScript, so I can serialize() and parse() and get the same date value back as part of object parsing. When the data is sent to the server the code above can handle it.
But this is an implementation specific way of doing this I suppose, but I couldn't find anything that describes the official way to describe a date in JSON so it works for roundtripping. I noticed that MS Ajax is using Javascript Date.GetTime() style formats specify its date formatting which is based on the JavaScript GetTime() function which can be used to create a date.
To create a JavaScript compatible millisecond count since 1970:
DateTime baseDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// *** Convert to JavaScript specific milliseconds
TimeSpan tspan = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
double ticks = tspan.TotalMilliseconds;
string jsonDate = "new Date(" + ticks.ToString() + ")";
// *** Convert back to a date
DateTime now = baseDate.AddMilliseconds(ticks).ToLocalTime();
Applied that to my client and server code and it round trips nicely and it's also a little more compact - it'll save a few bytes in addition to requiring less code to parse.
So there a few date format that need to be considered. There's the comma syntax I originally used. There's GetTime() syntax that several other tools use and there's the string encoded values that might be used by 'rogue' AJAX client libraries. At this point I have the following date parsing code on the server:
private DateTime baseDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
... parsing code
else if (ValueType == typeof(DateTime))
{
if (Value == null)
return DateTime.MinValue;
try
{
// *** Parse out what's between the date parens
Value = wwUtils.ExtractString(Value, "new Date(", ")");
// new Date() should never happen - error
if (string.IsNullOrEmpty(Value))
return DateTime.MinValue;
// *** 1999,12,1,12,00,59
if (Value.Contains(","))
{
string[] DateParts = Value.Split(',');
return new DateTime(int.Parse(DateParts[0]),
int.Parse(DateParts[1]) + 1,
int.Parse(DateParts[2]),
int.Parse(DateParts[3]),
int.Parse(DateParts[4]),
int.Parse(DateParts[5]),
DateTimeKind.Utc).ToLocalTime();
}
// *** JavaScript .GetTime() style date based on milliseconds since 1970
if (!Value.Contains("-") && !Value.Contains("/"))
{
double ticks = 0;
if (double.TryParse(Value,out ticks))
return this.BaseDate.AddMilliseconds(ticks).ToLocalTime();
return DateTime.MinValue;
}
// *** Try to parse with UTC round trip format or invariant auto-detection
Value = Value.Replace("\"", "");
return DateTime.Parse(Value, CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.RoundtripKind).ToLocalTime();
}
catch
{
return DateTime.MinValue;
}
}
... serialization code
else if (val is DateTime)
{
double milliseconds = 0;
DateTime date = (DateTime)val;
if (date.CompareTo(this.BaseDate) < 0)
milliseconds = 0D;
else
{
// *** Convert to JavaScript specific milliseconds
TimeSpan tspan = ((DateTime)val).Subtract(this.BaseDate);
milliseconds = tspan.TotalMilliseconds;
}
sb.Append("new Date(");
sb.Append(milliseconds);
sb.Append(")");
}
This seems to work fairly well with various different date formats for two way formatting. This code is part of JsonSerializer.cs in the wwHoverPanel download in case you are interested in the rest of the serialization/deserialization code.
Other Posts you might also like