Add TimeZoneInfo Serialization
authorAndreas Auerswald <andreas.auerswald@etit.tu-chemnitz.de>
Tue, 18 Jun 2013 07:23:31 +0000 (09:23 +0200)
committerAndreas Auerswald <andreas.auerswald@etit.tu-chemnitz.de>
Mon, 8 Jul 2013 08:39:51 +0000 (10:39 +0200)
Add serialization for the TimeZoneInfo, TimeZoneInfo.AdjustmentRule and TimeZoneInfo.TransitionTime class. It is compatible with the serialization in .Net for cross serialization/deserialization from Mono to .Net and vice versa.

The code in this commit is released under the terms of the MIT/X11 license.

mcs/class/System.Core/System/TimeZoneInfo.AdjustmentRule.cs
mcs/class/System.Core/System/TimeZoneInfo.TransitionTime.cs
mcs/class/System.Core/System/TimeZoneInfo.cs
mcs/class/System.Core/Test/System/TimeZoneInfo.AdjustmentRuleTest.cs
mcs/class/System.Core/Test/System/TimeZoneInfo.TransitionTimeTest.cs
mcs/class/System.Core/Test/System/TimeZoneInfoTest.cs

index d3c16f4fdef29d289cf79bfbd500806458d9f9c6..637d0c400dffd8384193b9dd29ed72f867654d6c 100644 (file)
@@ -74,6 +74,17 @@ namespace System
                                return new AdjustmentRule (dateStart, dateEnd, daylightDelta, daylightTransitionStart, daylightTransitionEnd);
                        }
 
+                       private AdjustmentRule (SerializationInfo info, StreamingContext context)
+                       {
+                               if (info == null)
+                                       throw new ArgumentNullException ("info");
+                               dateStart = (DateTime) info.GetValue ("DateStart", typeof (DateTime));
+                               dateEnd = (DateTime) info.GetValue ("DateEnd", typeof (DateTime));
+                               daylightDelta = (TimeSpan) info.GetValue ("DaylightDelta", typeof (TimeSpan));
+                               daylightTransitionStart = (TimeZoneInfo.TransitionTime) info.GetValue ("DaylightTransitionStart", typeof (TimeZoneInfo.TransitionTime));
+                               daylightTransitionEnd = (TimeZoneInfo.TransitionTime) info.GetValue ("DaylightTransitionEnd", typeof (TimeZoneInfo.TransitionTime));
+                       }
+                       
                        private AdjustmentRule (
                                DateTime dateStart,
                                DateTime dateEnd,
@@ -130,7 +141,13 @@ namespace System
                        public void GetObjectData (SerializationInfo info, StreamingContext context)
 #endif
                        {
-                               throw new NotImplementedException ();
+                               if (info == null)
+                                       throw new ArgumentNullException ("info");
+                               info.AddValue ("DateStart", DateStart);
+                               info.AddValue ("DateEnd", DateEnd);
+                               info.AddValue ("DaylightDelta", DaylightDelta);
+                               info.AddValue ("DaylightTransitionStart", DaylightTransitionStart);
+                               info.AddValue ("DaylightTransitionEnd", DaylightTransitionEnd);
                        }
 #if NET_4_0
                        void IDeserializationCallback.OnDeserialization (object sender)
@@ -138,7 +155,38 @@ namespace System
                        public void OnDeserialization (object sender)
 #endif
                        {
-                               throw new NotImplementedException ();
+                               try {
+                                       TimeZoneInfo.AdjustmentRule.Validate (dateStart, dateEnd, daylightDelta, 
+                                                                             daylightTransitionStart, daylightTransitionEnd);
+                               } catch (ArgumentException ex) {
+                                       throw new SerializationException ("invalid serialization data", ex);
+                               }
+                       }
+
+                       private static void Validate (
+                               DateTime dateStart,
+                               DateTime dateEnd,
+                               TimeSpan daylightDelta,
+                               TransitionTime daylightTransitionStart,
+                               TransitionTime daylightTransitionEnd)
+                       {
+                               if (dateStart.Kind != DateTimeKind.Unspecified || dateEnd.Kind != DateTimeKind.Unspecified)
+                                       throw new ArgumentException ("the Kind property of dateStart or dateEnd parameter does not equal DateTimeKind.Unspecified");
+
+                               if (daylightTransitionStart == daylightTransitionEnd)
+                                       throw new ArgumentException ("daylightTransitionStart parameter cannot equal daylightTransitionEnd parameter");
+
+                               if (dateStart.Ticks % TimeSpan.TicksPerDay != 0 || dateEnd.Ticks % TimeSpan.TicksPerDay != 0)
+                                       throw new ArgumentException ("dateStart or dateEnd parameter includes a time of day value");
+
+                               if (dateEnd < dateStart)
+                                       throw new ArgumentOutOfRangeException ("dateEnd is earlier than dateStart");
+
+                               if (daylightDelta > new TimeSpan (14, 0, 0) || daylightDelta < new TimeSpan (-14, 0, 0))
+                                       throw new ArgumentOutOfRangeException ("daylightDelta is less than -14 or greater than 14 hours");
+
+                               if (daylightDelta.Ticks % TimeSpan.TicksPerSecond != 0)
+                                       throw new ArgumentOutOfRangeException ("daylightDelta parameter does not represent a whole number of seconds");
                        }
                }
        }
index a914ed19b3d2649b69b83215207bdf3fc1d842a0..d57df150f0554bb1d318dd787d2b71225cc726f6 100644 (file)
@@ -108,6 +108,26 @@ namespace System
                                return new TransitionTime (timeOfDay, month, week, dayOfWeek);
                        }
 
+                       private TransitionTime (SerializationInfo info, StreamingContext context)
+                       {
+                               if (info == null)
+                                       throw new ArgumentNullException ("info");
+                               timeOfDay = (DateTime) info.GetValue ("TimeOfDay", typeof (DateTime));
+                               month = (byte) info.GetValue ("Month", typeof (byte));
+                               week = (byte) info.GetValue ("Week", typeof (byte));
+                               day = (byte) info.GetValue ("Day", typeof (byte));
+                               dayOfWeek = (DayOfWeek) info.GetValue ("DayOfWeek", typeof (DayOfWeek));
+                               isFixedDateRule = (bool) info.GetValue ("IsFixedDateRule", typeof (bool));
+
+                               if (isFixedDateRule)
+                               {
+                                       week = -1;
+                                       dayOfWeek = (DayOfWeek) (-1);
+                               }
+                               if (!isFixedDateRule)                   
+                                       day = -1;
+                       }
+
                        private TransitionTime (
                                DateTime timeOfDay,
                                int month,
@@ -190,8 +210,24 @@ namespace System
                        public void GetObjectData (SerializationInfo info, StreamingContext context)
 #endif
                        {
-                               throw new NotImplementedException ();
-                       }
+                               if (info == null)
+                                       throw new ArgumentNullException ("info");
+                               info.AddValue ("TimeOfDay", TimeOfDay);
+                               info.AddValue ("Month", System.Convert.ToByte(Month));
+                               if (week > -1)
+                                       info.AddValue ("Week", System.Convert.ToByte(week));
+                               else 
+                                       info.AddValue ("Week", (byte) 1);
+                               if (day > -1)
+                                       info.AddValue ("Day", System.Convert.ToByte(day));
+                               else
+                                       info.AddValue ("Day", (byte) 1);
+                               if (dayOfWeek !=  ((System.DayOfWeek) (-1)))
+                                       info.AddValue ("DayOfWeek", dayOfWeek);
+                               else
+                                       info.AddValue ("DayOfWeek", DayOfWeek.Sunday);
+                               info.AddValue ("IsFixedDateRule", IsFixedDateRule);
+                       }       
        
                        public override bool Equals (object obj)
                        {
@@ -216,7 +252,47 @@ namespace System
                        public void OnDeserialization (object sender)
 #endif
                        {
-                               throw new NotImplementedException ();
+                               try {
+                                       TimeZoneInfo.TransitionTime.Validate (timeOfDay, month, week, day, dayOfWeek, isFixedDateRule);
+                               } catch (ArgumentException ex) {
+                                       throw new SerializationException ("invalid serialization data", ex);
+                               }
+                       }
+
+                       private static void Validate (DateTime timeOfDay, int month,int week, int day, DayOfWeek dayOfWeek, bool isFixedDateRule)
+                       {
+                               if (timeOfDay.Year != 1 || timeOfDay.Month != 1 || timeOfDay.Day != 1)
+                                       throw new ArgumentException ("timeOfDay parameter has a non-default date component");
+
+                               if (timeOfDay.Kind != DateTimeKind.Unspecified)
+                                       throw new ArgumentException ("timeOfDay parameter Kind's property is not DateTimeKind.Unspecified");
+
+                               if (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0)
+                                       throw new ArgumentException ("timeOfDay parameter does not represent a whole number of milliseconds");
+
+                               if (day < 1 || day > 31) {
+                                       if (!(!isFixedDateRule && day == -1))
+                                               throw new ArgumentOutOfRangeException ("day parameter is less than 1 or greater than 31");
+                               }
+
+                               if (week < 1 || week > 5) {
+                                       if (!(isFixedDateRule && week == -1))
+                                               throw new ArgumentOutOfRangeException ("week parameter is less than 1 or greater than 5");
+                               }
+
+                               if (month < 1 || month > 12)
+                                       throw new ArgumentOutOfRangeException ("month parameter is less than 1 or greater than 12");
+
+                               if (dayOfWeek != DayOfWeek.Sunday &&
+                                               dayOfWeek != DayOfWeek.Monday &&
+                                               dayOfWeek != DayOfWeek.Tuesday &&
+                                               dayOfWeek != DayOfWeek.Wednesday &&
+                                               dayOfWeek != DayOfWeek.Thursday &&
+                                               dayOfWeek != DayOfWeek.Friday &&
+                                               dayOfWeek != DayOfWeek.Saturday) {
+                                       if (!(isFixedDateRule && dayOfWeek == (DayOfWeek) (-1)))
+                                               throw new ArgumentOutOfRangeException ("dayOfWeek parameter is not a member od DayOfWeek enumeration");
+                               }
                        }
                }
        }
index ee440a4bd51daa99e02153f2e065e79b31adc04c..feb1c3e27ce758deb4b70c784e038bfe2f58f8c6 100644 (file)
@@ -598,7 +598,15 @@ namespace System
                public void GetObjectData (SerializationInfo info, StreamingContext context)
 #endif
                {
-                       throw new NotImplementedException ();
+                       if (info == null)
+                               throw new ArgumentNullException ("info");
+                       info.AddValue ("Id", id);
+                       info.AddValue ("DisplayName", displayName);
+                       info.AddValue ("StandardName", standardDisplayName);
+                       info.AddValue ("DaylightName", daylightDisplayName);
+                       info.AddValue ("BaseUtcOffset", baseUtcOffset);
+                       info.AddValue ("AdjustmentRules", adjustmentRules);
+                       info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
                }
 
                //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
@@ -786,7 +794,54 @@ namespace System
                public void OnDeserialization (object sender)
 #endif
                {
-                       throw new NotImplementedException ();
+                       try {
+                                       TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
+                               } catch (ArgumentException ex) {
+                                       throw new SerializationException ("invalid serialization data", ex);
+                               }
+               }
+
+               private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
+               {
+                       if (id == null)
+                               throw new ArgumentNullException ("id");
+
+                       if (id == String.Empty)
+                               throw new ArgumentException ("id parameter is an empty string");
+
+                       if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
+                               throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
+
+                       if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
+                               throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
+
+#if STRICT
+                       if (id.Length > 32)
+                               throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
+#endif
+
+                       if (adjustmentRules != null && adjustmentRules.Length != 0) {
+                               AdjustmentRule prev = null;
+                               foreach (AdjustmentRule current in adjustmentRules) {
+                                       if (current == null)
+                                               throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
+
+                                       if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
+                                                       (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
+                                               throw new InvalidTimeZoneException ("Sum of baseUtcOffset and DaylightDelta of one or more object in adjustmentRules array is greater than 14 or less than -14 hours;");
+
+                                       if (prev != null && prev.DateStart > current.DateStart)
+                                               throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
+                                       
+                                       if (prev != null && prev.DateEnd > current.DateStart)
+                                               throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
+
+                                       if (prev != null && prev.DateEnd == current.DateStart)
+                                               throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
+
+                                       prev = current;
+                               }
+                       }
                }
                
                public string ToSerializedString ()
@@ -799,6 +854,19 @@ namespace System
                        return DisplayName;
                }
 
+               private TimeZoneInfo (SerializationInfo info, StreamingContext context)
+               {
+                       if (info == null)
+                               throw new ArgumentNullException ("info");
+                       id = (string) info.GetValue ("Id", typeof (string));
+                       displayName = (string) info.GetValue ("DisplayName", typeof (string));
+                       standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
+                       daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
+                       baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
+                       adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
+                       supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
+               }
+
                private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
                {
                        if (id == null)
index 6fd2412f9678039dbe10a9fd17437b2989abae87..0ebcad0b26fc708ceab16451d8b5d06923f4634a 100644 (file)
@@ -1,4 +1,6 @@
 using System;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
 using NUnit.Framework;
 
 #if NET_2_0
@@ -89,7 +91,27 @@ namespace MonoTests.System
                                TimeZoneInfo.TransitionTime daylightTransitionEnd = TimeZoneInfo.TransitionTime.CreateFixedDateRule (new DateTime (1,1,1,2,0,0), 10, 11);
                                TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, new TimeSpan (55), daylightTransitionStart, daylightTransitionEnd);
                        }
-               }       
+               }
+       
+               [TestFixture]
+               public class NonExceptional
+               {
+                       [Test]
+                       public void Serialization_Deserialization ()
+                       {
+                               TimeZoneInfo.TransitionTime start = TimeZoneInfo.TransitionTime.CreateFloatingDateRule (new DateTime (1,1,1,1,0,0), 3, 5, DayOfWeek.Sunday);
+                               TimeZoneInfo.TransitionTime end = TimeZoneInfo.TransitionTime.CreateFloatingDateRule (new DateTime (1,1,1,2,0,0), 10, 5, DayOfWeek.Sunday);
+                               TimeZoneInfo.AdjustmentRule rule = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule (DateTime.MinValue.Date, DateTime.MaxValue.Date, new TimeSpan (1,0,0), start, end);
+                               MemoryStream stream = new MemoryStream ();
+                               BinaryFormatter formatter = new BinaryFormatter ();
+                               formatter.Serialize (stream, rule);
+                               stream.Position = 0;
+                               TimeZoneInfo.AdjustmentRule deserialized = (TimeZoneInfo.AdjustmentRule) formatter.Deserialize (stream);
+                               stream.Close ();
+                               stream.Dispose ();
+                               Assert.AreEqual (rule, deserialized);
+                       }
+               }
        }       
 }
 #endif
index 8dd8263fd223fb82fdb01679173c6c948460538e..38fdac701451664a1f9a7aa9408ab7a82c9d8c51 100644 (file)
@@ -1,5 +1,7 @@
 
 using System;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
 using NUnit.Framework;
 
 #if NET_2_0
@@ -106,6 +108,34 @@ namespace MonoTests.System
                                Assert.IsFalse (tt2.Equals (tt1), "1!=2");
                                Assert.IsFalse (tt1.Equals (tt2), "2!=1");
                        }
+                       
+                       [Test]
+                       public void Serialize_Deserialize_FloatingDateRule ()
+                       {
+                               TimeZoneInfo.TransitionTime floatingDateRule = TimeZoneInfo.TransitionTime.CreateFloatingDateRule(new DateTime(1, 1, 1, 1, 0, 0), 3, 5, DayOfWeek.Sunday);
+                               MemoryStream stream = new MemoryStream ();
+                               BinaryFormatter formatter = new BinaryFormatter ();
+                               formatter.Serialize (stream, floatingDateRule);
+                               stream.Position = 0;
+                               TimeZoneInfo.TransitionTime deserialized = (TimeZoneInfo.TransitionTime) formatter.Deserialize (stream);
+                               stream.Close ();
+                               stream.Dispose ();
+                               Assert.AreEqual (floatingDateRule, deserialized);
+                       }
+
+                       [Test]
+                       public void Serialize_Deserialize_FixedDateRule ()
+                       {
+                               TimeZoneInfo.TransitionTime fixedDateRule = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 1, 0, 0), 3, 12);
+                               MemoryStream stream = new MemoryStream ();
+                               BinaryFormatter formatter = new BinaryFormatter ();
+                               formatter.Serialize (stream, fixedDateRule);
+                               stream.Position = 0;
+                               TimeZoneInfo.TransitionTime deserialized = (TimeZoneInfo.TransitionTime) formatter.Deserialize (stream);
+                               stream.Close ();
+                               stream.Dispose ();
+                               Assert.AreEqual (fixedDateRule, deserialized);
+                       }
                }
        }
 }
index 06de33e9ab7262167833ebc8d6cf178b04c4713f..da26842ead4a0ababb1a9f219cad8fc1e52bb928 100644 (file)
@@ -27,6 +27,8 @@
  */
 
 using System;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
 using System.Collections;
 
 using NUnit.Framework;
@@ -657,6 +659,27 @@ namespace MonoTests.System
                                Assert.IsTrue (utc.HasSameRules (custom));
                        }
                }
+
+               [TestFixture]
+               public class SerializationTests
+               {
+                       [Test]
+                       public void Serialization_Deserialization ()
+                       {
+                               TimeZoneInfo.TransitionTime start = TimeZoneInfo.TransitionTime.CreateFloatingDateRule (new DateTime (1,1,1,1,0,0), 3, 5, DayOfWeek.Sunday);
+                               TimeZoneInfo.TransitionTime end = TimeZoneInfo.TransitionTime.CreateFloatingDateRule (new DateTime (1,1,1,2,0,0), 10, 5, DayOfWeek.Sunday);
+                               TimeZoneInfo.AdjustmentRule rule = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule (DateTime.MinValue.Date, DateTime.MaxValue.Date, new TimeSpan (1,0,0), start, end);
+                               TimeZoneInfo london = TimeZoneInfo.CreateCustomTimeZone ("Europe/London", new TimeSpan (0), "Europe/London", "British Standard Time", "British Summer Time", new TimeZoneInfo.AdjustmentRule [] {rule});
+                               MemoryStream stream = new MemoryStream ();
+                               BinaryFormatter formatter = new BinaryFormatter ();
+                               formatter.Serialize (stream, london);
+                               stream.Position = 0;
+                               TimeZoneInfo deserialized = (TimeZoneInfo) formatter.Deserialize (stream);
+                               stream.Close ();
+                               stream.Dispose ();
+                               Assert.AreEqual (london, deserialized);
+                       }
+               }
        }
 }
 #endif