为什么在使用DateTimeStyles.AssumeUniversal时,JsonConvert用DateTimeKind.Unspecified更改DateTimes的时间?

Lur*_*xel 5 datetime json.net

我正在构建一个Web API,并且对的JSON序列化遇到了麻烦DateTimes。经过一些测试之后,我只能得出结论,Newtonsoft.Json.JsonConvert和/或Newtonsoft 的行为IsoDateTimeConverter不是我所期望的。

考虑一下:

// Arrange
var noonUtc = new DateTime(2016, 05, 12, 12, 0, 0, DateTimeKind.Utc);
var noon = new DateTime(2016, 05, 12, 12, 0, 0, DateTimeKind.Unspecified);

var settings = new JsonSerializerSettings();

settings.Converters.Add(new IsoDateTimeConverter
{    
    Culture = CultureInfo.InvariantCulture,    
    DateTimeStyles = DateTimeStyles.AdjustToUniversal
});

// Act
var utcJson = JsonConvert.SerializeObject(noonUtc, settings); // "\"2016-05-12T12:00:00Z\""
var json = JsonConvert.SerializeObject(noon, settings);       // "\"2016-05-12T10:00:00Z\""

... // Assertions
Run Code Online (Sandbox Code Playgroud)

好的,所以DateTimewith 的时间DateTimeKind.Unspecified已从12点调整为10点。我现在在斯德哥尔摩,比世界协调时间早两个小时,真是太公平了。

但是,让我们将序列化程序设置更改为use DateTimeStyles.AssumeUniversal,如下所示:

settings.Converters.Add(new IsoDateTimeConverter
{    
    Culture = CultureInfo.InvariantCulture,    
    DateTimeStyles = DateTimeStyles.AssumeUniversal
});
Run Code Online (Sandbox Code Playgroud)

这样就产生了完全相同的字符串,因此也将DateTimewith 调整了DateTimeKind.Unspecified两个小时!是否不应该假设日期时间已经是UTC时间,而将时间保持不变?我在这里想念什么?

Bri*_*ers 6

我不认为你错过了什么;这看起来可能是IsoDateTimeConverter. 这是来源中的相关代码:

if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
   || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
{
    dateTime = dateTime.ToUniversalTime();
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它只在调用之前查看是否_dateTimeStyles设置为AdjustToUniversal或之前;它从不检查日期的属性。AssumeUniversalToUniversalTime()Kind

文档是DateTime.ToUniversalTime()这样说的:

从 .NET Framework 2.0 版开始,方法返回的值由当前对象的属性ToUniversalTime确定。下表描述了可能的结果。KindDateTime

Kind        | Results
----------- | ----------------------------------------------------------
Utc         | No conversion is performed.
Local       | The current DateTime object is converted to UTC.
Unspecified | The current DateTime object is assumed to be a local time, 
            | and the conversion is performed as if Kind were Local.
Run Code Online (Sandbox Code Playgroud)

所以是的,看起来转换器绝对不应该ToUniversalTime在这种情况下调用。您可能想要报告问题

目前,您可以通过实现具有正确行为的替换转换器(从原始转换器派生)来解决此问题。这可能更接近您想要的:

public class CorrectedIsoDateTimeConverter : IsoDateTimeConverter
{
    private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is DateTime)
        {
            DateTime dateTime = (DateTime)value;

            if (dateTime.Kind == DateTimeKind.Unspecified)
            {
                if (DateTimeStyles.HasFlag(DateTimeStyles.AssumeUniversal))
                {
                    dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
                }
                else if (DateTimeStyles.HasFlag(DateTimeStyles.AssumeLocal))
                {
                    dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Local);
                }
            }

            if (DateTimeStyles.HasFlag(DateTimeStyles.AdjustToUniversal))
            {
                dateTime = dateTime.ToUniversalTime();
            }

            string format = string.IsNullOrEmpty(DateTimeFormat) ? DefaultDateTimeFormat : DateTimeFormat;
            writer.WriteValue(dateTime.ToString(format, Culture));
        }
        else
        {
            base.WriteJson(writer, value, serializer);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)