JavaScriptSerializer UTC DateTime问题

15

我们的客户希望在浏览器中显示与数据库中完全相同的日期和时间值,而我们在数据库中将它们存储为UTC。

起初,我们在序列化和JavaScript方面遇到了一些问题。DateTime值被移动了两次-首先匹配机器的本地时区,然后匹配浏览器中的时区。我们通过向JavaScriptSerializer添加自定义转换器来解决此问题。我们在Serialize重写中将DateTime标记为DateTimeKind.Utc。尽管从Serialize反馈数据有点困难,但我们找到了一些URI技巧,有助于以相同的JavaScriptSerializer /Date(286769410010)/格式返回DateTime值,但不会转换为本地时间。在JavaScript方面,我们修补了KendoUI JS库以偏移构造的Date()对象,使其看起来像是UTC。

然后,我们开始处理反序列化的另一方面。同样,我们不得不调整我们的代码以使用自定义stringify而不是JSON.stringify,这会在从本地时间转换为UTC时再次偏移数据。到目前为止,一切都很顺利。

但是,请看这个测试:

    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); 
    }
为什么JavaScriptSerializer将\/Date(286769410010)\/字符串反序列化为UTC时间,但将1979-02-02T02:10:10Z反序列化为本地时间?
我们尝试向自定义JavascriptConverter添加反序列化方法,但问题是如果我们的JavascriptConverter具有以下类型,则不会调用Deserialize方法:
    public override IEnumerable<Type> SupportedTypes
    {
        get { return new List<Type>() { typeof(DateTime), typeof(DateTime?) }; }
    }

我猜想,只有当SupportedTypes包含某些具有DateTime字段的复杂实体类型时,才会调用Deserialize。

因此,JavaScriptSerializerJavascriptConverter存在两个不一致之处:

  • Serialize针对每个数据项在SupportedTypes中考虑简单类型,但Deserialize对于简单类型则忽略它
  • Deserialize将某些日期反序列化为UTC时间,而将另一些日期反序列化为本地时间。

有没有简单的方法来解决这些问题? 我们有点担心用其他序列化程序替代JavaScriptSerializer,因为我们使用的某些第三方库可能依赖于JavaScriptSerializer的某些“功能/缺陷”。


我尝试了这段代码: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);反序列化返回的是1964年11月1日。我的其他测试返回的日期比同一库序列化的日期早了1天... - A.D.
也许是因为时间组件的原因——您的日期带有时间0,但反序列化程序尝试调整日期以适应UTC或本地时间,并删除本地时区偏移量(无论它是什么),因此您的日期最终变成了昨天。是的,JavaScriptSerializer过时且存在缺陷。 - JustAMartin
我使用了NewtonSoft NuGet包来替换所有出现的问题,现在没有问题了... - A.D.
1个回答

44

JavaScriptSerializerDataContractJsonSerializer存在大量的漏洞。建议改用json.net。甚至Microsoft在ASP.Net MVC4和其他最近的项目中也已经转向使用它。

/Date(286769410010)/格式是由Microsoft所独有并创制的。它存在问题,而且不被广泛支持。应该在任何地方都使用1979-02-02T02:10:10Z格式。这个格式被定义在ISO8601RF3339中。它既可以被机器读取,也可以被人类读取,可以按字典序排序,与文化无关,并且不会产生歧义。

在JavaScript中,如果你可以保证你的代码运行在新版本的浏览器上,那么使用:

date.toISOString()

这里提供参考。

如果想要完全支持跨浏览器和旧浏览器,请使用moment.js

更新

另外,如果您真的想继续使用JavaScriptSerializer,可以反序列化为DateTimeOffset,以保留正确的时间。然后,您可以从那里获取UTC DateTime,如下所示:

// 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;

你的测试现在将会通过。


3
好的 - 这些都很好、有用,但是微软为什么不直接发布更好的 JSON 库并放弃有缺陷的 JavaScriptSerializer,这个谬论的借口是什么? - nothingisnecessary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接