Protobuf.net 和 DateTimeOffset 的序列化

Protobuf.net and serialization of DateTimeOffset

我一直在使用 Protobuf-net 作为胖客户端应用程序的序列化程序,该应用程序使用服务堆栈通过 HTTP 进行通信。我们的第一个拥有大量数据的客户在反序列化时开始看到错误。我们在一些模型中发送 DateTimeOffset 类型,因此我们创建了一个将值序列化为字符串的代理项。从我们的日志中,我可以看到错误发生的时间,这是它试图反序列化的日期值,在重复时区偏移的末尾有一个额外的六个字符:

8/9/2016 12:02:37 AM-7:00 -7:00

这是我们代理人的代码。

[ProtoContract]
public class DateTimeOffsetSurrogate
{
    [ProtoMember(1)]
    public string DateTimeString { get; set; }

    public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
    {
        return new DateTimeOffsetSurrogate { DateTimeString = value.ToString() };
    }

    public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
    {
        try
        {
            return DateTimeOffset.Parse(value.DateTimeString);
        }
        catch (Exception ex)
        {
            throw new Exception("Unable to parse date time value: " + value.DateTimeString, ex);
        }
    }
}

一旦发生此日期错误,它将无法正确 serialize/deserialize 直到 PC 重新启动。我们无法以允许我们调试和查看消息其余部分的方式重现此错误。这是有人熟悉的情况吗?我们使用的是 2.0.0.640 版本,由于这个问题,我更新到 2.0.0.668,但问题仍然存在。

看起来好像 CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern 在客户端的机器上不知何故变得混乱。我可以通过将 "K" 格式添加到 LongTimePattern:

来重现该问题
var dateTime = DateTimeOffset.Parse(@"8/9/2016 12:02:37 AM-7:00");

var myCI = new CultureInfo("en-US");
myCI.DateTimeFormat.LongTimePattern = myCI.DateTimeFormat.LongTimePattern + " K";
Console.WriteLine(dateTime.ToString(myCI)); // Prints 8/9/2016 12:02:37 AM -07:00 -07:00

写入的字符串是8/9/2016 12:02:37 AM -07:00 -07:00,这正是您所看到的。

可能是您的应用程序中存在错误,它在某处设置了 LongTimePattern。我也可以重现这个问题:

Thread.CurrentThread.CurrentCulture = myCI;
Console.WriteLine(dateTime.ToString());     // Prints 8/9/2016 12:02:37 AM -07:00 -07:00

或者可能是客户端以某种方式修改了 "Region and Language" -> "Additional settings..." 对话框中的 "Long time:" 字符串,看起来像 (Windows 7):

如果客户端以某种方式执行此操作,并且计算机位于域中,则格式可能会 reset back on reboot 这正是您所看到的。

客户端可能手动执行此操作(尽管根据实验,尝试在 UI 中的 Windows 7 上手动附加 K 会生成错误弹出窗口然后失败),或者可能有一些有问题的第 3 方应用程序通过调用 SetLocaleInfo.

在您或他们不知道的情况下执行此操作

您可以记录 LongTimePattern 的值以尝试跟踪问题,但无论如何您都应该修改 DateTimeOffsetSurrogate 以便它以文化不变的格式序列化 DateTimeOffset , 最好由 How to: Round-trip Date and Time Values: To round-trip a DateTimeOffset value:

指定
[ProtoContract]
public class DateTimeOffsetSurrogate
{
    [ProtoMember(1)]
    public string DateTimeString { get; set; }

    public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
    {
        return new DateTimeOffsetSurrogate { DateTimeString = value.ToString("o") };
    }

    public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
    {
        try
        {
            return DateTimeOffset.Parse(value.DateTimeString, null, DateTimeStyles.RoundtripKind);
        }
        catch (Exception ex)
        {
            throw new Exception("Unable to parse date time value: " + value.DateTimeString, ex);
        }
    }
}

这不仅可以修复您看到的错误,还可以确保您的应用程序在一个地区(例如英国)生成的协议缓冲区可以在其他地方(例如美国)以不同的文化格式进行解析日期和时间。