Malaise
Malaise

Reputation: 711

Protobuf.net and serialization of DateTimeOffset

I've been using Protobuf-net as the serializer for a thick client application that's using Service Stack to communicate over HTTP. Our first customer with a lot of volume has started seeing errors when deserializing. We are sending DateTimeOffset types in some of our models, and so we created a surrogate that serializes the value as a string. From our logs, I can see when the error occurs this is the date value it's attempting to deserialize has an extra six characters at the end where the timezone offset is repeated:

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

Here's the code for our surrogate.

[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);
        }
    }
}

Once this date error has occurred, it won't correctly serialize/deserialize until the PC has rebooted. We have not been able to reproduce this error in a way that would allow us to debug and look at the rest of the message. Is this a situation that someone is familiar with? We were using version 2.0.0.640, and because of this issue I updated to 2.0.0.668 but the issue remains.

Upvotes: 0

Views: 1503

Answers (1)

dbc
dbc

Reputation: 117096

It looks as though the CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern is somehow getting messed up on the client's machine. I can reproduce the problem by adding the "K" format to the 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

The string written is 8/9/2016 12:02:37 AM -07:00 -07:00 which is exactly what you are seeing.

It may be that there is a bug in your application which is setting the LongTimePattern somewhere. I can also reproduce the problem by doing:

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

Or it may be that the client is somehow modifying the "Long time:" string in "Region and Language" -> "Additional settings..." dialog, which looks like (Windows 7):

enter image description here

If the client is doing this somehow, and the machine is on a domain, the format may get reset back on reboot which is exactly what you are seeing.

The client may be doing this manually (although, from experimentation, trying to append K manually on Windows 7 in the UI generates an error popup and then fails), or there may be some buggy 3rd party application doing it unbeknownst to you or them via a call to SetLocaleInfo.

You could log the value of LongTimePattern to try to trace the problem, but regardless you should modify your DateTimeOffsetSurrogate so that it serializes the DateTimeOffset in a culture-invariant format, preferably as specified by 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);
        }
    }
}

Not only should this fix the bug you are seeing, it will also ensure that protocol buffers generated by your app in one region (say, the UK) can be parsed elsewhere (say, the US) with different cultural formatting for dates and times.

Upvotes: 2

Related Questions