【问题标题】:JSON.Net deserialization of DateTime values failing in some instances?在某些情况下,DateTime 值的 JSON.Net 反序列化失败?
【发布时间】:2011-11-10 20:42:01
【问题描述】:

使用 JSON.Net,以下 5 次测试中,第一次和最后一次通过而其他失败:

[Test, Sequential]
public void WhyCantIDeserializeThisDateWhen2011Works(
    [Values(1980, 1980, 1980, 1980, 1980, 1980, 1980)] Int32 year,
    [Values(10,   10,   10,   10,   11,   11,   11)] Int32 month,
    [Values(26,   27,   30,   31,   1,    2,    3)] Int32 day)
{
    var obj = new {
        Title = "Will this be able to serialize the DateTime field?",
        Timestamp = new DateTime(year, month, day)
    };

    var type = obj.GetType();
    var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
    dynamic deserialized = Newtonsoft.Json.JsonConvert.DeserializeObject(serialized, type);

    Assert.AreEqual(obj.Title, deserialized.Title);
    Assert.AreEqual(obj.Timestamp, deserialized.Timestamp);
}

这是一些输出:

[snip]

Test 'Rds.Infrastructure.Serializers.Tests.JsonSerializerTests.WhyCantIDeserializeThisDateWhen2011Works(1980,11,2)' failed:
Expected: 1980-11-02 00:00:00.000
But was: 1980-11-01 23:00:00.000
at CallSite.Target(Closure , CallSite , Type , DateTime , Object )
    UnitTests\Rds.Infrastructure\Serializers\JsonSerializerTests.cs(141,0): at     Rds.Infrastructure.Serializers.Tests.JsonSerializerTests.WhyCantIDeserializeThisDateWhen2011Works(Int32 year, Int32 month, Int32 day)

2 passed, 5 failed, 0 skipped, took 627.55 seconds (NUnit 2.5.5).

该错误是所有错误的典型错误 - 当它重新加载日期而不是指定日期时,它会在前一天晚上 11 点出现。这特别奇怪,因为如果我将年份更改为 2011 年,所有这些测试都会通过。

我已经深入研究了 JSON.Net 代码 - JsonTextReader 类的 ParseDate 方法读取值。以 2011 年 10 月 27 日为例,cmets 是我的:

private void ParseDate(string text)
{
  string value = text.Substring(6, text.Length - 8);
  DateTimeKind kind = DateTimeKind.Utc;

  int index = value.IndexOf('+', 1);

  if (index == -1)
    index = value.IndexOf('-', 1);

  TimeSpan offset = TimeSpan.Zero;

  if (index != -1)
  {
    kind = DateTimeKind.Local;
    offset = ReadOffset(value.Substring(index));
    value = value.Substring(0, index);
  }

  long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);

  // The date time gets loaded here, as Oct 27 2011 3am
  DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks);

#if !NET20
  if (_readType == ReadType.ReadAsDateTimeOffset)
  {
    SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset));
  }
  else
#endif
  {
    DateTime dateTime;

    switch (kind)
    {
      case DateTimeKind.Unspecified:
        dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified);
        break;
      case DateTimeKind.Local:
        // Here, it gets converted to local time, Oct 26 2011 at 11pm!
        dateTime = utcDateTime.ToLocalTime();
        break;
      default:
        dateTime = utcDateTime;
        break;
    }

    SetToken(JsonToken.Date, dateTime);
  }
}

如上所述,该错误仅发生在 1980 年 10 月 27 日至 1980 年 11 月 2 日期间。我尚未运行测试以确定哪些年份存在问题,但如果您使用 2011 年,测试确实通过了。

我猜这与夏令时的变化有关?

有人知道这里发生了什么吗?

【问题讨论】:

  • 发生在 1981 年吗? 1985 年?你在哪个时区?
  • 我在大西洋时区。我回去尝试了不同的年份,在我尝试的其他年份中,我得到了相同的错误结果:1981、1985、2001 和 2006。从 2007 年到 2011 年,测试都通过了。
  • 想想看,2007 年美国和加拿大的夏令时延长了 - 从 3 月的第二个星期日到 11 月的第一个星期日。从 1987 年到 2006 年,它在 10 月的最后一个星期日结束。 (参考:timeanddate.com/news/time/us-daylight-saving-extended.html)这可能是与此相关的错误吗?
  • 你是否有机会在 XP 上运行?
  • 不。 .Net 4,Visual Studio 2010 SP1,在 Win7 虚拟机上。

标签: c# datetime json.net


【解决方案1】:

我已经深入研究了。

当 JSON.Net 序列化 DateTime 实例时,它会调用 TimeZone.CurrentTimeZone.GetUtcOffset(dt)。当它重新加载 DateTime 时,它​​假定一个 UTC 日期并转换为本地日期调用 utcDateTime.ToLocalTime() 以转换为本地时间。看来这两种方法可能并不总是使用相同的偏移量:

(注意:我在大西洋时区。)

[Test, Sequential]
public void AnotherTest(
    [Values(2006, 2006, 2006, 2006, 2006, 2006, 2006)] Int32 year,
    [Values(10, 10, 10, 10, 11, 11, 11)] Int32 month,
    [Values(26, 27, 30, 31, 1, 2, 3)] Int32 day)
{
    var dt = new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Local);

    var utcOffset1 = TimeZone.CurrentTimeZone.GetUtcOffset(dt);
    var utcOffset2 = dt.Subtract(dt.ToUniversalTime());

    Assert.AreEqual(utcOffset1, utcOffset2);
}

这些在 10 月 26 日和 11 月 3 日通过,并且在这两个日期之间失败。我还对早些年进行了测试,结果相同。从 2007 年到 2011 年,这些都过去了。对于我发现的所有故障,utcOffset1 为 -3 小时,而 utcOffset2 为 -4 小时。 (根据http://www.timeanddate.com/library/abbreviations/timezones/,大西洋标准时间应该是UTC-4,大西洋夏令时间应该是UTC-3。)一些快速测试表明,在夏令时开始时也存在问题,在 2007 年之前。

(我在 https://connect.microsoft.com/VisualStudio/feedback/details/699491/timezone-getutcoffset-and-datetime-touniversaltime-not-consistent-for-brief-period-for-atlantic-time-zone 上打开了一个问题。)

同时,为了解决这个问题,只需要在序列化和反序列化日期时使UTC偏移量保持一致,这意味着摆脱对TimeZone.CurrentTimeZone.GetUtcOffset的调用。

更新

JamesNK 于 2011 年 11 月 1 日更新了 JSON.Net,以便时区转换使用 TimeZoneInfo 而不是 TimeZone,这似乎已经解决了问题。

更新 2

感谢@derekhat 提供以下附加信息:

今晚有几分钟的空闲时间。我使用 Windows 7 64 位通过了所有测试,并使用 .NET 2.0 SDK 在命令行编译(必须将 var 更改为显式类型声明)。

7 个测试中有 5 个在 Visual Studio 2010 和 .NET 4 中失败。

然后我找到了following documentation

GetUtcOffset 方法只识别当前的夏令时 当地时区的调整规则。结果,它 保证仅准确返回本地时间的 UTC 偏移量 在最新调整规则生效期间。它 如果 Time 是历史日期和时间,可能会返回不准确的结果 受先前调整规则约束的值。

另一份文档使事情变得更加复杂:“在 Windows XP 系统上,ToUniversalTime 方法在从本地时间转换为 UTC 时仅识别当前的调整规则。”并且评论说 WinXP 行为也存在于 Windows Server 2003 上。这意味着 ToUniversalTime 在较新版本的 Windows 上与历史日期一起正常工作,这似乎与您的结果相符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-08
    • 2012-08-05
    • 1970-01-01
    • 2018-01-11
    • 2013-12-12
    相关资源
    最近更新 更多