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:

Version Considerations

Prior to the .NET Framework version 2.0, the DateTime structure contains a 64-bit field composed of an unused 2-bit field concatenated with a private Ticks field, which is a 62-bit unsigned field that contains the number of ticks that represent the date and time. The value of the Ticks field can be obtained with the Ticks property.

Starting with the .NET Framework 2.0, the DateTime structure contains a 64-bit field composed of a private Kind field concatenated with the Ticks field. The Kind field is a 2-bit field that indicates whether the DateTime structure represents a local time, a Coordinated Universal Time (UTC), or whether UTC or local time is not specified. The Kind field is used to handle conversions between local and UTC time, but not time comparisons or arithmetic. The value of the Kind field can be obtained with the Kind property.

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:
  • Mono CLR 1.1 is not concerned by my patch (it could be) and remains not compatible with MS .NET 1.1 and 2.x
  • Mono CLR 2.x not patched
    • is not compatible with MS .NET 2.x (in both ways)
    • is not able to deserialize data serialized with Mono CLR 2.x patched (since the latter serializes its data by using MS .NET 2.x format)
  • Mono for CLR 2.x patched
    • serializes its data by using MS .NET 2.x format
    • is able to read DateTime serialized with MS .NET 1.1 and 2.x formats (MS .NET 2.x has the same backward compatibility)
    • is able to read DateTime serialized with Mono for CLR 1.x not patched and 2.x not patched


- 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

Reply via email to