JavaScriptSerializer UTC DateTime issues
Asked Answered
K

1

15

Our client wanted to show the date and time values in the browser exactly as they are in the database, and we are storing them as UTC in the database.

At first we had some problems with the serialization and Javascript side. The DateTime values got shifted twice - at first to match the local time zone of the machine and then to match the time zone in the browser. We fixed it by adding a custom Converter to the JavaScriptSerializer. We marked the DateTime to be of DateTimeKind.Utc in the Serialize override. It was a bit hard to feed the data back from the Serialize but we found some Uri hack which helped to return DateTime values in the same JavaScriptSerializer /Date(286769410010)/ format but without shifting to the local time. On the Javascript side we patched the KendoUI JS library to offset the constructed Date() objects so they appear as if they are UTC.

Then we started to work on the other side, deserialization. Again, we had to adjust our code to use a custom stringify instead of JSON.stringify, which again offsets the data when converting from the local time to UTC. Everything seemed good so far.

But look at this test:

    public void DeserialiseDatesTest()
    {
        var dateExpected = new DateTime(1979, 2, 2,
            2, 10, 10, 10, DateTimeKind.Utc);

        // this how the Dates look like after serializing
        // anothe issue, unrelated to the core problem, is that the "\" might get stripped out when dates come back from the browser
        // so I have to add missing "\" or else Deserialize will break
        string s = "\"\\/Date(286769410010)\\/\"";

        // this get deserialized to UTC date by default
        JavaScriptSerializer js = new JavaScriptSerializer();

        var dateActual = js.Deserialize<DateTime>(s);
        Assert.AreEqual(dateExpected, dateActual);
        Assert.AreEqual(DateTimeKind.Utc, dateActual.Kind);

        // but some Javascript components (like KendoUI) sometimes use JSON.stringify 
        // for Javascript Date() object, thus producing the following:
        s = "\"1979-02-02T02:10:10Z\"";

        dateActual = js.Deserialize<DateTime>(s);
        // If your local computer time is not UTC, this will FAIL!
        Assert.AreEqual(dateExpected, dateActual);

        // and the following fails always
        Assert.AreEqual(DateTimeKind.Utc, dateActual.Kind); 
    }

Why does JavaScriptSerializer deserialize \/Date(286769410010)\/ strings to UTC time but 1979-02-02T02:10:10Zto local time?

We tried to add a Deserialize method to our custom JavascriptConverter but the problem is that the Deserialize is never called if our JavascriptConverter has the following types:

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new List<Type>() { typeof(DateTime), typeof(DateTime?) }; }
    }

I guess, Deserialize would be called only if SupportedTypes contained types of some complex entities which have DateTime fields.

So, JavaScriptSerializer and JavascriptConverter have two inconsistencies:

  • Serialize takes into account simple types in SupportedTypes for every data item, but Deserialize ignores it for simple types
  • Deserialize deserializes some dates as UTC and some - as local time.

Is there any simple way to fix these issues? We are a bit afraid to replace JavaScriptSerializer with some other serializer because maybe some of the 3rd party libraries we are using, are relying upon some certain "features/bugs" of JavaScriptSerializer.

Keelby answered 12/6, 2013 at 15:29 Comment(3)
I tried this code: string v3 = (new System.Web.Script.Serialization.JavaScriptSerializer()).Serialize(new DateTime(1964, 11, 2)); var v4 = (new System.Web.Script.Serialization.JavaScriptSerializer()).Deserialize<DateTime>(v3); Desierializing returns 1964 11 1 My other tests return a date which is 1 day before what the same lib has sserialized.....Quack
Maybe it happens because of the time component - your date is with time 0, but the deserializer tries to adjust the date for UTC or your local time, removing the local timezone offset, whatever it is, so your date ends up becoming yesterday. Yeah, JavaScriptSerializer is old and buggy.Keelby
I replaced all occurencies with NewtonSoft NuGet package and no more issue...Quack
P
44

JavaScriptSerializer, and DataContractJsonSerializer are riddled with bugs. Use json.net instead. Even Microsoft has made this switch in ASP.Net MVC4 and other recent projects.

The /Date(286769410010)/ format is proprietary and made up by Microsoft. It has problems, and is not widely supported. You should use the 1979-02-02T02:10:10Z format everywhere. This is defined in ISO8601 and RF3339. It is both machine and human readable, lexically sortable, culture invariant, and unambiguous.

In JavaScript, if you can guarantee you will be running on newer browsers, then use:

date.toISOString()

Reference here.

If you want full cross-browser and older-browser support, use moment.js instead.

UPDATE

As an aside, if you really want to keep using JavaScriptSerializer, you could deserialize to a DateTimeOffset, which would preserve the correct time. You could then get the UTC DateTime from there, as follows:

// note, you were missing the milliseconds in your example, I added them here.
s = "\"1979-02-02T02:10:10.010Z\"";

dateActual = js.Deserialize<DateTimeOffset>(s).UtcDateTime;

Your test will now pass.

Pileate answered 12/6, 2013 at 15:57 Comment(1)
Ok - so this is all well and good and useful, but what is the bullshit excuse for Msft not just shipping the better json library and leaving the buggy JavaScriptSerializer alone?Trothplight

© 2022 - 2024 — McMap. All rights reserved.