Merge pull request #1304 from slluis/mac-proxy-autoconfig
[mono.git] / mcs / class / System.Core / System / TimeZoneInfo.cs
index ee440a4bd51daa99e02153f2e065e79b31adc04c..aead18c764f230a9889dc542459b921994681f3a 100644 (file)
@@ -1,3 +1,4 @@
+
 /*
  * System.TimeZoneInfo
  *
@@ -28,6 +29,7 @@
 
 using System;
 using System.Runtime.CompilerServices;
+using System.Threading;
 
 #if !INSIDE_CORLIB && NET_4_0
 
@@ -84,37 +86,48 @@ namespace System
                static TimeZoneInfo local;
                public static TimeZoneInfo Local {
                        get { 
-                               if (local == null) {
+                               var l = local;
+                               if (l == null) {
+                                       l = CreateLocal ();
+                                       if (l == null)
+                                               throw new TimeZoneNotFoundException ();
+
+                                       if (Interlocked.CompareExchange (ref local, l, null) != null)
+                                               l = local;
+                               }
+
+                               return l;
+                       }
+               }
+
+               static TimeZoneInfo CreateLocal ()
+               {
 #if MONODROID
-                                       local = ZoneInfoDB.Default;
+                       return AndroidTimeZones.Default;
 #elif MONOTOUCH
-                                       using (Stream stream = GetMonoTouchData (null)) {
-                                               local = BuildFromStream ("Local", stream);
-                                       }
+                       using (Stream stream = GetMonoTouchData (null)) {
+                               return BuildFromStream ("Local", stream);
+                       }
 #elif LIBC
-                                       try {
-                                               local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");       
-                                       } catch {
-                                               try {
-                                                       local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));  
-                                               } catch {
-                                                       throw new TimeZoneNotFoundException ();
-                                               }
-                                       }
-#else
-                                       if (IsWindows && LocalZoneKey != null) {
-                                               string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
-                                               name = TrimSpecial (name);
-                                               if (name != null)
-                                                       local = TimeZoneInfo.FindSystemTimeZoneById (name);
-                                       }
-                                       
-                                       if (local == null)
-                                               throw new TimeZoneNotFoundException ();
-#endif
+                       try {
+                               return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");        
+                       } catch {
+                               try {
+                                       return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));   
+                               } catch {
+                                       return null;
                                }
-                               return local;
                        }
+#else
+                       if (IsWindows && LocalZoneKey != null) {
+                               string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
+                               name = TrimSpecial (name);
+                               if (name != null)
+                                       return TimeZoneInfo.FindSystemTimeZoneById (name);
+                       }
+
+                       return null;
+#endif
                }
 
                string standardDisplayName;
@@ -214,36 +227,38 @@ namespace System
 
                public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
                {
-                       return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
+                       return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
                }
 
                public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
                {
+                       if (sourceTimeZone == null)
+                               throw new ArgumentNullException ("sourceTimeZone");
+
+                       if (destinationTimeZone == null)
+                               throw new ArgumentNullException ("destinationTimeZone");
+                       
                        if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
                                throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
 
                        if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
                                throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
-
+                       
                        if (sourceTimeZone.IsInvalidTime (dateTime))
                                throw new ArgumentException ("dateTime parameter is an invalid time");
 
-                       if (sourceTimeZone == null)
-                               throw new ArgumentNullException ("sourceTimeZone");
-
-                       if (destinationTimeZone == null)
-                               throw new ArgumentNullException ("destinationTimeZone");
-
                        if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
                                return dateTime;
 
                        DateTime utc = ConvertTimeToUtc (dateTime);
 
-                       if (destinationTimeZone == TimeZoneInfo.Utc)
-                               return utc;
-
-                       return ConvertTimeFromUtc (utc, destinationTimeZone);   
-
+                       if (destinationTimeZone != TimeZoneInfo.Utc) {
+                               utc = ConvertTimeFromUtc (utc, destinationTimeZone);
+                               if (dateTime.Kind == DateTimeKind.Unspecified)
+                                       return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
+                       }
+                       
+                       return utc;
                }
 
                public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) 
@@ -285,13 +300,19 @@ namespace System
 
                        if (this == TimeZoneInfo.Utc)
                                return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
-
+                       
                        //FIXME: do not rely on DateTime implementation !
-                       if (this == TimeZoneInfo.Local)
+                       if (this == TimeZoneInfo.Local) 
+                       {
+#if NET_4_0
+                               return dateTime.ToLocalTime ();
+#else
                                return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
+#endif
+                       }
+
 
                        AdjustmentRule rule = GetApplicableRule (dateTime);
-               
                        if (rule != null && IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
                                return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
                        else
@@ -394,7 +415,7 @@ namespace System
                        }
 #endif
 #if MONODROID
-                       var timeZoneInfo = ZoneInfoDB.GetTimeZone (id);
+                       var timeZoneInfo = AndroidTimeZones.GetTimeZone (id);
                        if (timeZoneInfo == null)
                                throw new TimeZoneNotFoundException ();
                        return timeZoneInfo;
@@ -551,11 +572,6 @@ namespace System
                }
 #endif
 
-               public static TimeZoneInfo FromSerializedString (string source)
-               {
-                       throw new NotImplementedException ();
-               }
-
                public AdjustmentRule [] GetAdjustmentRules ()
                {
                        if (!supportsDaylightSavingTime)
@@ -598,7 +614,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
@@ -619,15 +643,17 @@ namespace System
                                }
 #endif
 #if MONODROID
-                       foreach (string id in ZoneInfoDB.GetAvailableIds ()) {
-                               var tz = ZoneInfoDB.GetTimeZone (id);
+                       foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
+                               var tz = AndroidTimeZones.GetTimeZone (id);
                                if (tz != null)
                                        systemTimeZones.Add (tz);
                        }
 #elif MONOTOUCH
                                if (systemTimeZones.Count == 0) {
                                        foreach (string name in GetMonoTouchNames ()) {
-                                               using (Stream stream = GetMonoTouchData (name)) {
+                                               using (Stream stream = GetMonoTouchData (name, false)) {
+                                                       if (stream == null)
+                                                               continue;
                                                        systemTimeZones.Add (BuildFromStream (name, stream));
                                                }
                                        }
@@ -726,6 +752,18 @@ namespace System
                        throw new NotImplementedException ();
                }
 
+               bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
+               {
+                       DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
+                       DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
+                       if (dateTime.Kind == DateTimeKind.Utc) {
+                               DST_start -= BaseUtcOffset;
+                               DST_end -= (BaseUtcOffset + rule.DaylightDelta);
+                       }
+
+                       return (dateTime >= DST_start && dateTime < DST_end);
+               }
+               
                public bool IsDaylightSavingTime (DateTime dateTime)
                {
                        if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
@@ -733,29 +771,28 @@ namespace System
 
                        if (this == TimeZoneInfo.Utc)
                                return false;
-
+                       
                        if (!SupportsDaylightSavingTime)
                                return false;
+                       
                        //FIXME: do not rely on DateTime implementation !
                        if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
                                return dateTime.IsDaylightSavingTime ();
-
+                       
                        //FIXME: do not rely on DateTime implementation !
                        if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
                                return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
-                               
+                       
                        AdjustmentRule rule = GetApplicableRule (dateTime.Date);
                        if (rule == null)
                                return false;
 
-                       DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
-                       DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
-                       if (dateTime.Kind == DateTimeKind.Utc) {
-                               DST_start -= BaseUtcOffset;
-                               DST_end -= (BaseUtcOffset + rule.DaylightDelta);
-                       }
+                       // Check whether we're in the dateTime year's DST period
+                       if (IsInDSTForYear (rule, dateTime, dateTime.Year))
+                               return true;
 
-                       return (dateTime >= DST_start && dateTime < DST_end);
+                       // We might be in the dateTime previous year's DST period
+                       return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
                }
 
                public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
@@ -786,17 +823,72 @@ 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 ()
+               public override string ToString ()
                {
-                       throw new NotImplementedException ();
+                       return DisplayName;
                }
 
-               public override string ToString ()
+               private TimeZoneInfo (SerializationInfo info, StreamingContext context)
                {
-                       return DisplayName;
+                       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)
@@ -886,6 +978,8 @@ namespace System
                        int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
                        if (day >  DateTime.DaysInMonth (year, transition.Month))
                                day -= 7;
+                       if (day < 1)
+                               day += 7;
                        return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
                }