I have been struggling with the seemingly unbeatable UTCness of js Date.getTime in respect to dealing with dates going between javascript and wcf. I tried adding the offset/60/1000, munging local times, closing one eye and sticking out my tongue and nothing. nothing. The server was always getting a utc long and treating it as a local.
I have been following Rick's saga with wcf dates r.e. ajax and gave his solutions a go and got pleasant results with the .parseWithDate() tweak to json2.js but could not get results with the .stringifyWcf(). Got UTC with no offset no matter how much attention I gave it. Maybe I was just not getting something....
In any case...
It turns out that, after reading the source for the server side parser that simply appending '-0000' will force the parser to consider the value as UTC and parse accordingly thus giving the correct local time at the endpoint.
js dates to/from wcf
var tmp = {
dateFromWcf: function (input, throwOnInvalidInput) {
var pattern = /Date\(([^)]+)\)/;
var results = pattern.exec(input);
if (results.length != 2) {
if (!throwOnInvalidInput) {
return s;
}
throw new Error(s + " is not .net json date.");
}
return new Date(parseFloat(results[1]));
},
dateToWcf: function (input) {
var d = new Date(input);
if (isNaN(d)) {
throw new Error("input not date");
}
// here is how we force wcf to parse as UTC and give correct local time serverside
var date = '\/Date(' + d.getTime() + '-0000)\/';
return date;
}
};
Update:
Well, having to munge dates is just dirty and smelly so I went into Rick's json2.js mod and simply added the '-0000' to the replacer and bingo. It works great.
Update: In some roundtripping tests I noticed that stringifyWcf is setting the date field of the object being stringified to the wcf date string. PU! Now that's what I call an undocumented and unexpected side effect. That has been fixed so now you get your object back in the same shape you sent it. ;-)
Update 2:
Now I understand why Rick was modifying the input value, I am not sure he does though, can get no response from several comments left on his blog. In any case, Firefox 3.6 has a bug in the JSON replacer.
So, for FF you need to force json2.js as shown in the post linked above and use this addon:
(function jsonWcfAddons() {
// from https://west-wind.com/Weblog/posts/729630.aspx#262650
// with some fairly significant fixes by sky sanders
// http://skysanders.net/subtext/archive/2010/02/18/wcf-to-json-dates-and-back-again.aspx
if (this.JSON && !this.JSON.parseWithDate) {
var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;
var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
JSON.parseWithDate = function(json) {
/// <summary>
/// parses a JSON string and turns ISO or MSAJAX date strings
/// into native JS date objects
/// </summary>
/// <param name="json" type="var">json with dates to parse</param>
/// </param>
/// <returns type="value, array or object" />
try {
var res = JSON.parse(json,
function(key, value) {
if (typeof value === 'string') {
var a = reISO.exec(value);
if (a)
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
a = reMsAjax.exec(value);
if (a) {
var b = a[1].split(/[-+,.]/);
return new Date(b[0] ? +b[0] : 0 - +b[1]);
}
}
return value;
});
return res;
} catch (e) {
// orignal error thrown has no error message so rethrow with message
throw new Error("JSON content could not be parsed");
return null;
}
};
JSON.stringifyWcf = function(json) {
/// <summary>
/// Wcf specific stringify that encodes dates in the
/// a WCF compatible format ("/Date(9991231231)/")
/// Note: this format works ONLY with WCF.
/// ASMX can use ISO dates as of .NET 3.5 SP1
/// </summary>
/// <param name="key" type="var">property name</param>
/// <param name="value" type="var">value of the property</param>
// choking on FF 3.6 date Wed Feb 24 2010 14:02:17 GMT-0700 (US Mountain Standard Time)
return JSON.stringify(json, function(key, value) {
if (typeof value == "string") {
var a = reISO.exec(value);
if (a) {
// SKY: the '-0000' forces the serverside serializer into utc mode resulting in accurate dates.
var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + '-0000)/';
//SKY: rick, I don't think that asking for a JSON representation
// of an object should modify the state of the object. my 2 pesos.
//this[key] = val;
// this is the value that SHOULD be getting serialized but in the
// FF native JSON is ignoring this and serializing the member so I am guessing
// that is why rick is modifying the input object. gets the serialization job
// done properly but is a rather nasty undocumented side effect, in my opinion.
// I think it is probably better to overwrite native JSON with json2.js and
// get consistant results across platforms with out the need to modify the input
// UNLESS of course you are never going to need to use any of the objects that you
// serialize...
return val;
}
}
return value;
})
};
JSON.dateStringToDate = function(dtString) {
/// <summary>
/// Converts a JSON ISO or MSAJAX string into a date object
/// </summary>
/// <param name="" type="var">Date String</param>
/// <returns type="date or null if invalid" />
var a = reISO.exec(dtString);
if (a)
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
a = reMsAjax.exec(dtString);
if (a) {
var b = a[1].split(/[-,.]/);
return new Date(+b[0]);
}
return null;
};
}
})();
Technorati tags:
WCF,
Ajax,
Javascript ,
CodeProject-Tip