Hello all, I've taken some time to dig again into this issue and tried to answer some points mentioned in the thread (please correct me if I'm wrong!). Attached are the patch for DateTime.cs (nothing really changed since March) and the unit test. - DateTime managed as a PrimitiveType by the BinaryFormatter? I've not seen any indication on the fact .NET2.0's BinaryFormatter would not manage DateTime as a ISerializable but as a PrimitiveType. When you look at the binary array generated by the BinaryFormatter, you do have a two int64: the ticks and a int64 in which you have both the ticks and the kind (as used in my patch). - How .NET 2.0 stores DateTime internally? To quote MSDN's page on DateTime:
If you want more details, see the #region ISerializable at the bottom of DateTime.cs - it's commented there. - Version compatibility "Will this break with older Mono implementations, say with data that was stored with old versions? It seems like you put some effort on not breaking." No that won't break. My patch ensure backward compatibility, namely:
- Unit test "Would you mind providing NUnit test cases that would exercise the various binary formats? You could bundle the stuff as a binary blob in an array and test the various code paths." Done. See DataTimeTest.cs - Soap issue My hack works but it should be considered as temporary, as long as the issue in the Soap Serializer itself has not been solved. I'm missing both time and knowledge of the Soap serialization's internal mechanism to handle this... Regards, Lionel |
--- DateTime 20070821.cs 2007-08-10 07:08:10.000000000 +0200 +++ DateTime 20070821 patched.cs 2007-08-29 14:56:36.000000000 +0200 @@ -1,3 +1,5 @@ +#define HACK_FOR_SOAP_FORMATTER_BUG + // // System.DateTime.cs // @@ -33,6 +35,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Serialization; using System.Text; namespace System @@ -46,7 +49,7 @@ [StructLayout (LayoutKind.Auto)] public struct DateTime : IFormattable, IConvertible, IComparable #if NET_2_0 - , IComparable<DateTime>, IEquatable <DateTime> + , IComparable<DateTime>, IEquatable<DateTime>, ISerializable #endif { private TimeSpan ticks; @@ -2259,5 +2262,166 @@ { throw new InvalidCastException(); } + +#if NET_2_0 // ISerializable Members + + private DateTime(SerializationInfo info, StreamingContext context) + { + this.kind = DateTimeKind.Unspecified; + this.ticks = TimeSpan.MinValue; + + if (info == null) + throw new ArgumentNullException("info"); + + /* + * Mono and .Net have different 'signatures' for their SerializationInfo's content: + * + * Mono uses: + * - ticks, a TimeSpan + * - kind, a DateTimeKind (only on .NET 2.0) + * + * And .Net uses: + * - in v1.0: ???? + * - in v1.1: ticks, a long + * - in v2.0: ticks (a long) and dateData (a ulong) with the first end being the kind and the right end the ticks + * + * So we first enumerate the available field names & types, and then decide what to do on that basis. + * If the kind is not present, it shoud be considered as equal to 0 (Unspecified). + */ + + SerializationInfoEnumerator serializedFieldEnum = info.GetEnumerator(); + int fieldCount = info.MemberCount; + string serializedFieldName; + Type serializedFieldType; + + if (fieldCount == 1) + { + #region This DateTime should have been serialized under the .NET framework 1.x or with Mono for framework v1.x + serializedFieldName = serializedFieldEnum.Name; + serializedFieldType = serializedFieldEnum.ObjectType; + if (serializedFieldName != "ticks") + throw new SerializationException("Invalid serialized data for this DataTime - 1 single unknown member."); + + if (serializedFieldType == typeof(TimeSpan)) + { + // This is a DateTime serialized under Mono for framework 1.x + this.ticks = (TimeSpan)serializedFieldEnum.Value; + } + else if(serializedFieldType == typeof(long)) + { + // This is a DateTime serialized under .NET 1.x + this.ticks = new TimeSpan((long)serializedFieldEnum.Value); + } + else + throw new SerializationException("Invalid serialized data for this DataTime - member 'ticks' not of the expected types."); + #endregion This DateTime should have been serialized under the .NET framework 1.x or with Mono for framework v1.x + } + else if (fieldCount == 2) + { + #region This DateTime should have been serialized under .Net 2.x or under Mono for the framework v2.x. + bool isMono = false; + + serializedFieldEnum.MoveNext(); + serializedFieldName = serializedFieldEnum.Name; + serializedFieldType = serializedFieldEnum.ObjectType; + + if (serializedFieldName != "ticks") + throw new SerializationException("Missing ticks field in a DateTime serialized with .NET or Mono for the version 2.x but it isn't"); + + if (serializedFieldType == typeof(long)) + { + isMono = false; + this.ticks = new TimeSpan((long)serializedFieldEnum.Value); + } + else if (serializedFieldType == typeof(TimeSpan)) + { + isMono = true; + this.ticks = (TimeSpan)serializedFieldEnum.Value; + } +#if HACK_FOR_SOAP_FORMATTER_BUG + else + { + // With soap formatter, the long has been changed into an object... + try + { + this.ticks = new TimeSpan(Convert.ToInt64(serializedFieldEnum.Value)); + isMono = false; + } + catch + { + throw new SerializationException(serializedFieldType.ToString() + " is not expected for the ticks field in a DateTime serialized with .NET or Mono for the version 2.x. Value is " + serializedFieldEnum.Value.ToString()); + } + } +#else + else + throw new SerializationException(serializedFieldType.ToString() + " is not expected for the ticks field in a DateTime serialized with .NET or Mono for the version 2.x."); +#endif + + + serializedFieldEnum.MoveNext(); + serializedFieldName = serializedFieldEnum.Name; + serializedFieldType = serializedFieldEnum.ObjectType; +#if HACK_FOR_SOAP_FORMATTER_BUG + if (!isMono && serializedFieldName == "dateData") + { + try + { + ulong dateData = Convert.ToUInt64(serializedFieldEnum.Value); + ulong leftpart = dateData & 0xc000000000000000; + // If we do nothing, we let kind = 0 (ie Unspecified) + if (leftpart == 0) + this.kind = DateTimeKind.Unspecified; + else if (leftpart == 0x4000000000000000) + this.kind = DateTimeKind.Utc; + else + this.kind = DateTimeKind.Local; + } + catch + { + throw new SerializationException("This should be a DateTime serialized with .NET or Mono for the version 2.x but it isn't. Internal dateData is not of type ulong but of type " + serializedFieldType.ToString()); + } + } +#else + if (!isMono && serializedFieldName == "dateData" && serializedFieldType == typeof(ulong)) + { + ulong dateData = (ulong)serializedFieldEnum.Value; + ulong leftpart = dateData & 0xc000000000000000; + // If we do nothing, we let kind = 0 (ie Unspecified) + if (leftpart == 0) + this.kind = DateTimeKind.Unspecified; + else if (leftpart == 0x4000000000000000) + this.kind = DateTimeKind.Utc; + else + this.kind = DateTimeKind.Local; + } +#endif + else if(isMono && serializedFieldName == "kind" && serializedFieldType == typeof(DateTimeKind)) + { + this.kind = (DateTimeKind)serializedFieldEnum.Value; + } + else + throw new SerializationException("This should be a DateTime serialized with .NET or Mono for the version 2.x but it isn't."); + + #endregion This DateTime should have been serialized under .Net 2.x or under Mono for the framework v2.x. + } + else + throw new SerializationException("Invalid serialized data for this DataTime - (3 members or more)."); + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new System.ArgumentNullException("info"); + + info.AddValue("ticks", this.ticks.Ticks); + + // We serialize the DateTime the .Net 2.x way. + // Let's store the kind into the left part of dateData, and the ticks into the right part. + ulong dateData = (ulong) ((((long) kind) << 0x3e) | ticks.Ticks); + + info.AddValue("dateData", dateData); + } + +#endif } }
--- DateTimeTest 20070821.cs 2007-08-10 07:08:12.000000000 +0200 +++ DateTimeTest 20070821 patched.cs 2007-08-29 16:20:57.000000000 +0200 @@ -9,7 +9,8 @@ // using System; -using System.IO; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Globalization; @@ -1846,5 +1847,74 @@ new DateTime (2000, 1, 1), DateTimeKind.Local).ToString ("o").Length, "#3"); } #endif + +#if NET_2_0 + [Test] + public void DeserializeDateTimeSerializedWithDotNet20() + { + DateTime date = new DateTime(2007, 7, 5, 14, 0, 0, DateTimeKind.Local); + byte[] serializedDate = new byte[] + { 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x02, 0x00, 0x00, 0x00, 0x05, 0x74, 0x69, 0x63, 0x6B, 0x73, 0x08, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x00, 0x00, 0x09, 0x10, 0x00, 0x70, 0x50, 0xE9, 0x23, 0x8D, 0xC9, 0x08, 0x00, 0x70, 0x50, 0xE9, 0x23, 0x8D, 0xC9, 0x88, 0x0B }; + + using (MemoryStream ms = new MemoryStream(serializedDate)) + { + BinaryFormatter bf = new BinaryFormatter(); + DateTime test = (DateTime)bf.Deserialize(ms); + Assert.AreEqual(test.Ticks, date.Ticks, "ticks"); + Assert.AreEqual(test.Kind, date.Kind, "kind"); + } + } + + //[Test] + //public void DeserializeDateTimeSerializedWithDotNet11() + //{ // Test commented as I don't have .net 1.1 anymore... + // DateTime date = new DateTime(2007, 7, 5, 14, 0, 0, DateTimeKind.Local); + // byte[] serializedDate = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x02, 0x00, 0x00, 0x00, 0x05, 0x74, 0x69, 0x63, 0x6B, 0x73, 0x08, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x00, 0x00, 0x09, 0x10, 0x00, 0x70, 0x50, 0xE9, 0x23, 0x8D, 0xC9, 0x08, 0x00, 0x70, 0x50, 0xE9, 0x23, 0x8D, 0xC9, 0x88, 0x0B }; + + // using (MemoryStream ms = new MemoryStream(serializedDate)) + // { + // BinaryFormatter bf = new BinaryFormatter(); + // DateTime test = (DateTime)bf.Deserialize(ms); + // Assert.AreEqual(test.Ticks, date.Ticks, "ticks"); + // Assert.AreEqual(test.Kind, date.Kind, "kind"); + // } + //} + + [Test] + public void DeserializeDateTimeSerializedWithMonoCLR20NOTpatched() + { + DateTime date = new DateTime(2007, 7, 5, 14, 0, 0, DateTimeKind.Local); + byte[] serializedDate = new byte[] { 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x02, 0x00, 0x00, 0x00, 0x05, 0x74, 0x69, 0x63, 0x6B, 0x73, 0x04, 0x6B, 0x69, 0x6E, 0x64, 0x00, 0x03, 0x0C, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x4B, 0x69, 0x6E, 0x64, 0x00, 0x70, 0x50, 0xE9, 0x23, 0x8D, 0xC9, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00, 0x13, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6D, 0x65, 0x4B, 0x69, 0x6E, 0x64, 0x01, 0x00, 0x00, 0x00, 0x07, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x5F, 0x5F, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x0B }; + + using (MemoryStream ms = new MemoryStream(serializedDate)) + { + BinaryFormatter bf = new BinaryFormatter(); + DateTime test = (DateTime)bf.Deserialize(ms); + Assert.AreEqual(test.Ticks, date.Ticks, "ticks"); + Assert.AreEqual(test.Kind, date.Kind, "kind"); + } + } + + [Test] + public void SerializeThenDeserializeDateTimeWithMonoCLR20patched() + { + DateTime date = new DateTime(2007, 7, 5, 14, 0, 0, DateTimeKind.Local); + byte[] serializedDate; + + BinaryFormatter bf = new BinaryFormatter(); + using (MemoryStream ms = new MemoryStream()) + { + bf.Serialize(ms, date); + serializedDate = ms.ToArray(); + } + + using (MemoryStream ms = new MemoryStream(serializedDate)) + { + DateTime test = (DateTime)bf.Deserialize(ms); + Assert.AreEqual(test.Ticks, date.Ticks, "ticks"); + Assert.AreEqual(test.Kind, date.Kind, "kind"); + } + } +#endif } }
_______________________________________________ Mono-devel-list mailing list Mono-devel-list@lists.ximian.com http://lists.ximian.com/mailman/listinfo/mono-devel-list