3

I am migrating a library from .net framework 4.7 to .net core 2.2 and found an issue with deep object cloning that I narrowed down to a short reproducible code snippet below.

Try this yourself:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // any zone here, don't care what it is
            var zone = TimeZoneInfo.GetSystemTimeZones()[0];            
            var formatter = new BinaryFormatter();

            using (MemoryStream stream = new MemoryStream())
            {
                formatter.Serialize(stream, zone);
                stream.Seek(0, SeekOrigin.Begin);
                var result = formatter.Deserialize(stream);
                Console.WriteLine("all ok");
            }
        }
    }
}

On Windows platforms using .net core 2.2 this works fine, but I get an exception on Linux platforms:

Unhandled Exception: System.Runtime.Serialization.SerializationException: An error occurred while deserializing the object.  The serialized data is corrupt. ---> System.ArgumentOutOfRangeException: The Month parameter must be in the range 1 through 12.
Parameter name: month
   at System.TimeZoneInfo.TransitionTime.ValidateTransitionTime(DateTime timeOfDay, Int32 month, Int32 week, Int32 day, DayOfWeek dayOfWeek)
   at System.TimeZoneInfo.TransitionTime.System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(Object sender)
   --- End of inner exception stack trace ---
   at System.TimeZoneInfo.TransitionTime.System.Runtime.Serialization.IDeserializationCallback.OnDeserialization(Object sender)
   at System.Runtime.Serialization.ObjectManager.RaiseDeserializationEvent()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(BinaryParser serParser, Boolean fCheck)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, Boolean check)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
   at Test.Program.Main(String[] args)

Question: how can I use binary serialization with TimeZoneInfo in .net core 2.2 in Linux?

oleksii
  • 35,458
  • 16
  • 93
  • 163
  • This works in .Net Core 2.1 when I try it (I don't have 2.2 installed). Does it work in 2.1 for you? – Matthew Watson Aug 26 '19 at 10:02
  • Just tried this and it works in .Net Core 2.2 for me... – Matthew Watson Aug 26 '19 at 10:19
  • @MatthewWatson - does this work within a Linux based environment for you? As the OP posted, it would work fine on Windows but not for Linux based systems – Darren Aug 26 '19 at 10:29
  • Did you try doing a clean build? Code may not a got rebuilt after upgrade. Check the datetime of the executables in the project bin folders (release and debug). – jdweng Aug 26 '19 at 10:36
  • @Darren Ah right, I missed the Linux bit. – Matthew Watson Aug 26 '19 at 10:49
  • 1
    For anyone who is interested, I've reported this as an issue to .net core repo https://github.com/dotnet/core/issues/3254 – oleksii Aug 26 '19 at 12:23
  • Can you isolate issue to Serialize or Deserialize method? It is failing on Deserialize bu may be due to the serialize method being wrong. If you serialize on Net Core and deserialize on Net core does it fail. Is this an issue only between windows and linux? I'm wondering if it is the constructor. Linux dates start at 1/1/1970 and windows start at 1/1/1. It does look like you are serializing a time (just timezone) so you may have issue with dates before 1970. – jdweng Aug 26 '19 at 12:39
  • As a workaround, can you use `ToSerializedString` and `FromSerializedString` instead of the binary format? Or, you could do what most of us do and just use the `Id`. Run it through [TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter) if you need to be cross-plat. – Matt Johnson-Pint Aug 26 '19 at 14:39
  • Don't assume anything. If the serialize method does not match the deserialize method than you do not know which is wrong. What happens if you serialize on linux and then deserialize on linux and it works where is the error? – jdweng Aug 26 '19 at 16:55
  • @jdweng serialization - deserialization on Linux doesn't work as per my example and links to .net fiddle. However, it works fine on Windows. This seems to be a .net core issue which is being fixed now. – oleksii Aug 27 '19 at 11:17
  • You are serializing/deserializing in same operating system. Did you try saving the serialization to a file so you can test windows against linux. this issue could be a serialization issue and not a deserialize issue. – jdweng Aug 27 '19 at 12:21

1 Answers1

1

Indeed it seems like a bug. Thanks for raising the issue.

However, from your comments it seems like there is a simple workaround. Instead of serializing the TimeZoneInfo, change your object to serialize just the ID. You can put a property with get/set accessors around it for convenience if you like.

For example, instead of:

public class Foo
{
    public TimeZoneInfo TimeZone { get; set; }
}

You can do this:

public class Foo
{
    public string TimeZoneId { get; set; }

    public TimeZoneInfo TimeZone
    {
        get => TimeZoneInfo.FindSystemTimeZoneById(TimeZoneId);
        set => TimeZoneId = value.Id;
    }
}

The BinaryFormatter only serializes fields, so only the string hiding behind the TimeZoneId auto-property will be serialized/deserialized. The TimeZoneInfo will be ignored during serialization/deserialization, and just used when accessing the object in your own code.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575