DataContractJsonSerializer日期时间隐式时区转换

7

我在数据库中有一个日期时间,使用Entity Framework从数据库中检索它,然后通过DataContractJsonSerializer通过JSON API传递数据。

在DataContractJsonSerializer处理过程中,日期时间字段中的时间似乎已经根据服务器的本地时区进行了调整。表达的时代时间比预期的时间早1小时。DateTime Kind是UTC,但以前它是未指定的,我遇到了相同的问题。

在我的应用程序中,我希望明确地在客户端而不是服务器上在时区之间转换。我对这种隐式功能感到惊讶,因为我的日期时间值应该像整数一样简单。

谢谢


这个问题的解决方案实际上比已接受的答案简单得多,未来的读者可以在此处查看:https://dev59.com/6mox5IYBdhLWcg3wVS8J#76186056 - user21139719
1个回答

5

DataContractJsonSerializer 如果你的 DateTime.Kind 等于 Local 或 Unspecified,则会输出时区部分(+zzzz)。这种行为与 XmlSerializer 不同,后者仅在 Kind 等于 Unspecified 时输出时区部分。

如果您感到好奇,请查看 JsonWriterDelegator 的源代码,其中包含以下方法:

 internal override void WriteDateTime(DateTime value) 
    {
        // ToUniversalTime() truncates dates to DateTime.MaxValue or DateTime.MinValue instead of throwing 
        // This will break round-tripping of these dates (see bug 9690 in CSD Developer Framework)
        if (value.Kind != DateTimeKind.Utc)
        {
            long tickCount = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks; 
            if ((tickCount > DateTime.MaxValue.Ticks) || (tickCount < DateTime.MinValue.Ticks))
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( 
                    XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.JsonDateTimeOutOfRange), new ArgumentOutOfRangeException("value")));
            } 
        }

        writer.WriteString(JsonGlobals.DateTimeStartGuardReader);
        writer.WriteValue((value.ToUniversalTime().Ticks - JsonGlobals.unixEpochTicks) / 10000); 

        switch (value.Kind) 
        { 
            case DateTimeKind.Unspecified:
            case DateTimeKind.Local: 
                // +"zzzz";
                TimeSpan ts = TimeZone.CurrentTimeZone.GetUtcOffset(value.ToLocalTime());
                if (ts.Ticks < 0)
                { 
                    writer.WriteString("-");
                } 
                else 
                {
                    writer.WriteString("+"); 
                }
                int hours = Math.Abs(ts.Hours);
                writer.WriteString((hours < 10) ? "0" + hours : hours.ToString(CultureInfo.InvariantCulture));
                int minutes = Math.Abs(ts.Minutes); 
                writer.WriteString((minutes < 10) ? "0" + minutes : minutes.ToString(CultureInfo.InvariantCulture));
                break; 
            case DateTimeKind.Utc: 
                break;
        } 
        writer.WriteString(JsonGlobals.DateTimeEndGuardReader);
    }

我在我的电脑上运行了以下测试

var jsonSerializer = new DataContractJsonSerializer(typeof(DateTime));
var date = DateTime.UtcNow;
        Console.WriteLine("original date = " + date.ToString("s"));
        using (var stream = new MemoryStream())
        {
            jsonSerializer.WriteObject(stream, date);

            stream.Position = 0;
            var deserializedDate = (DateTime)jsonSerializer.ReadObject(stream);
            Console.WriteLine("deserialized date = " + deserializedDate.ToString("s"));

        }

产生预期输出的代码如下:
original date = 2011-04-19T10:24:39
deserialized date = 2011-04-19T10:24:39

因此,在某些时候,您的日期必须是未指定或本地时间。

从数据库中提取后,通过调用函数将其类型从未指定更改为UTC。

 entity.Date = DateTime.SpecifyKind(entity.Date, DateTimeKind.Utc);

不要忘记像我一样将 SpecifyKind 的返回值重新分配给您的对象。


我的日期时间被序列化为1303500600000+0000。 - krisdyson
你的样本序列化为 "/Date(1303220156217)/",而我的序列化为 "/Date(1303500600000+0000)/"。我不知道为什么它包括了 +0000。 - krisdyson
1
你确定DateTimeKind=UTC吗?我刚在我的电脑上再次尝试了一下,当DateTimeKind不等于UTC(本地或未指定)时,时区会被输出。我也查看了源代码,如果DateTimeKind==UTC,则不会输出时区。 - wal
你能否在你的端上运行我的样例,以检查是否会出现+zzzzz输出? - wal
哦,是的,抱歉,我以为它会自动处理。这是因为我的DateTime的Kind未指定,所以序列化程序决定加上时区指示符,然后使后续反序列化DataContractJsonSerializer认为需要调整时间值为本地时间。实际上,我希望我的datetime值是与时区无关的。我不知道为什么微软要把它搞得如此复杂;如果微软省略了这种隐式行为,它就会变得简单。 - krisdyson

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