Salient Solutions

wrasslin ones and nones for fun and profit - Sky Sanders' Blog
posts - 96, comments - 70, trackbacks - 0

WCF to JSON Dates and back again

 

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: , , ,

Print | posted on Thursday, February 18, 2010 5:33 PM | Filed Under [ CodeProject-Tip ]

Feedback

Gravatar

# re: WCF to JSON Dates and back again

I downloaded your code here, and ended up having to modify the asmx parsing part of it. If you want, shoot me an email, and I can ship you the changes I made.
3/25/2010 8:12 AM | Tim Cartwright
Gravatar

# re: WCF to JSON Dates and back again

I don't know who you are, but I love you man. Thanks for this. Saved my ass!
4/1/2010 10:38 AM | Lee Dumond

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 7 and 4 and type the answer here:

Powered by: