Merge pull request #2003 from esdrubal/seq_test_fix2
[mono.git] / mcs / class / corlib / System / TimeZoneInfo.cs
index dadd2b7003e8b8b045b054e60d0e345ab4f197ce..d3fc6ff2c6615a6614d78497aab908a3d13d5bd6 100644 (file)
@@ -33,13 +33,10 @@ using System.Threading;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Runtime.Serialization;
+using System.Runtime.InteropServices;
 using System.Text;
 using System.Globalization;
-
-#if LIBC || MONODROID
 using System.IO;
-using Mono;
-#endif
 
 using Microsoft.Win32;
 
@@ -93,16 +90,62 @@ namespace System
                */
                private List<KeyValuePair<DateTime, TimeType>> transitions;
 
-               static TimeZoneInfo CreateLocal ()
+               private static bool libcNotFound;
+
+               [DllImport ("libc")]
+               private static extern int readlink (string path, byte[] buffer, int buflen);
+
+               private static string readlink (string path)
                {
-#if MONODROID
-                       return AndroidTimeZones.Local;
-#elif MONOTOUCH
-                       using (Stream stream = GetMonoTouchData (null)) {
-                               return BuildFromStream ("Local", stream);
+                       if (libcNotFound)
+                               return null;
+
+                       byte[] buf = new byte [512];
+                       int ret;
+
+                       try {
+                               ret = readlink (path, buf, buf.Length);
+                       } catch (DllNotFoundException e) {
+                               libcNotFound = true;
+                               return null;
                        }
-#else
-#if !NET_2_1
+
+                       if (ret == -1) return null;
+                       char[] cbuf = new char [512];
+                       int chars = System.Text.Encoding.Default.GetChars (buf, 0, ret, cbuf, 0);
+                       return new String (cbuf, 0, chars);
+               }
+
+               private static bool TryGetNameFromPath (string path, out string name)
+               {
+                       name = null;
+                       var linkPath = readlink (path);
+                       if (linkPath != null)
+                               path = linkPath;
+
+                       path = Path.GetFullPath (path);
+
+                       if (string.IsNullOrEmpty (TimeZoneDirectory))
+                               return false;
+
+                       var baseDir = TimeZoneDirectory;
+                       if (baseDir [baseDir.Length-1] != Path.DirectorySeparatorChar)
+                               baseDir += Path.DirectorySeparatorChar;
+
+                       if (!path.StartsWith (baseDir, StringComparison.InvariantCulture))
+                               return false;
+
+                       name = path.Substring (baseDir.Length);
+                       if (name == "localtime")
+                               name = "Local";
+
+                       return true;
+               }
+
+#if !MOBILE || MOBILE_STATIC
+               static TimeZoneInfo CreateLocal ()
+               {
+#if !MOBILE_STATIC
                        if (IsWindows && LocalZoneKey != null) {
                                string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
                                if (name == null)
@@ -124,17 +167,70 @@ namespace System
                                }
                        }
 
-                       try {
-                               return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");        
-                       } catch {
+                       var tzFilePaths = new string [] {
+                               "/etc/localtime",
+                               Path.Combine (TimeZoneDirectory, "localtime")};
+
+                       foreach (var tzFilePath in tzFilePaths) {
                                try {
-                                       return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));   
-                               } catch {
-                                       return null;
+                                       string tzName = null;
+                                       if (!TryGetNameFromPath (tzFilePath, out tzName))
+                                               tzName = "Local";
+                                       return FindSystemTimeZoneByFileName (tzName, tzFilePath);
+                               } catch (TimeZoneNotFoundException) {
+                                       continue;
                                }
                        }
+
+                       return Utc;
+               }
+
+               static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
+               {
+#if LIBC
+                       string filepath = Path.Combine (TimeZoneDirectory, id);
+                       return FindSystemTimeZoneByFileName (id, filepath);
+#else
+                       throw new NotImplementedException ();
+#endif
+               }
+
+               static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
+               {
+#if !MOBILE_STATIC
+                       if (TimeZoneKey != null) {
+                               foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
+                                       try {
+                                               systemTimeZones.Add (FindSystemTimeZoneById (id));
+                                       } catch {}
+                               }
+
+                               return;
+                       }
+#endif
+
+#if LIBC
+                       string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
+                       foreach (string continent in continents) {
+                               try {
+                                       foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
+                                               try {
+                                                       string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
+                                                       systemTimeZones.Add (FindSystemTimeZoneById (id));
+                                               } catch (ArgumentNullException) {
+                                               } catch (TimeZoneNotFoundException) {
+                                               } catch (InvalidTimeZoneException) {
+                                               } catch (Exception) {
+                                                       throw;
+                                               }
+                                       }
+                               } catch {}
+                       }
+#else
+                       throw new NotImplementedException ("This method is not implemented for this platform");
 #endif
                }
+#endif
 
                string standardDisplayName;
                public string StandardName {
@@ -170,7 +266,7 @@ namespace System
 #endif
                private AdjustmentRule [] adjustmentRules;
 
-#if !NET_2_1
+#if !NET_2_1 || MOBILE_STATIC
                /// <summary>
                /// Determine whether windows of not (taken Stephane Delcroix's code)
                /// </summary>
@@ -197,6 +293,7 @@ namespace System
                        return str.Substring (Istart, Iend-Istart+1);
                }
                
+#if !MOBILE_STATIC
                static RegistryKey timeZoneKey;
                static RegistryKey TimeZoneKey {
                        get {
@@ -224,6 +321,7 @@ namespace System
                                        "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
                        }
                }
+#endif
 #endif
 
                private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
@@ -436,21 +534,8 @@ namespace System
                        // Local requires special logic that already exists in the Local property (bug #326)
                        if (id == "Local")
                                return Local;
-#if MONOTOUCH
-                       using (Stream stream = GetMonoTouchData (id)) {
-                               return BuildFromStream (id, stream);
-                       }
-#elif MONODROID
-                       var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
-                       if (timeZoneInfo == null)
-                               throw new TimeZoneNotFoundException ();
-                       return timeZoneInfo;
-#elif LIBC
-                       string filepath = Path.Combine (TimeZoneDirectory, id);
-                       return FindSystemTimeZoneByFileName (id, filepath);
-#else
-                       throw new NotImplementedException ();
-#endif
+
+                       return FindSystemTimeZoneByIdCore (id);
                }
 
 #if LIBC
@@ -464,24 +549,6 @@ namespace System
                        }
                }
 #endif
-#if LIBC || MONOTOUCH
-               const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
-               
-               private static TimeZoneInfo BuildFromStream (string id, Stream stream) 
-               {
-                       byte [] buffer = new byte [BUFFER_SIZE];
-                       int length = stream.Read (buffer, 0, BUFFER_SIZE);
-                       
-                       if (!ValidTZFile (buffer, length))
-                               throw new InvalidTimeZoneException ("TZ file too big for the buffer");
-
-                       try {
-                               return ParseTZBuffer (id, buffer, length);
-                       } catch (Exception e) {
-                               throw new InvalidTimeZoneException (e.Message);
-                       }
-               }
-#endif
 
 #if !NET_2_1
                private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
@@ -591,7 +658,7 @@ namespace System
 
                public AdjustmentRule [] GetAdjustmentRules ()
                {
-                       if (!supportsDaylightSavingTime)
+                       if (!supportsDaylightSavingTime || adjustmentRules == null)
                                return new AdjustmentRule [0];
                        else
                                return (AdjustmentRule []) adjustmentRules.Clone ();
@@ -638,61 +705,17 @@ namespace System
                        info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
                }
 
-               //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
-               private static List<TimeZoneInfo> systemTimeZones;
+               static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
+
                public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
                {
                        if (systemTimeZones == null) {
-                               systemTimeZones = new List<TimeZoneInfo> ();
-#if !NET_2_1
-                               if (TimeZoneKey != null) {
-                                       foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
-                                               try {
-                                                       systemTimeZones.Add (FindSystemTimeZoneById (id));
-                                               } catch {}
-                                       }
-
-                                       return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
-                               }
-#endif
-#if MONODROID
-                       foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
-                               var tz = AndroidTimeZones.GetTimeZone (id, id);
-                               if (tz != null)
-                                       systemTimeZones.Add (tz);
-                       }
-#elif MONOTOUCH
-                               if (systemTimeZones.Count == 0) {
-                                       foreach (string name in GetMonoTouchNames ()) {
-                                               using (Stream stream = GetMonoTouchData (name, false)) {
-                                                       if (stream == null)
-                                                               continue;
-                                                       systemTimeZones.Add (BuildFromStream (name, stream));
-                                               }
-                                       }
-                               }
-#elif LIBC
-                               string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
-                               foreach (string continent in continents) {
-                                       try {
-                                               foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
-                                                       try {
-                                                               string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
-                                                               systemTimeZones.Add (FindSystemTimeZoneById (id));
-                                                       } catch (ArgumentNullException) {
-                                                       } catch (TimeZoneNotFoundException) {
-                                                       } catch (InvalidTimeZoneException) {
-                                                       } catch (Exception) {
-                                                               throw;
-                                                       }
-                                               }
-                                       } catch {}
-                               }
-#else
-                               throw new NotImplementedException ("This method is not implemented for this platform");
-#endif
+                               var tz = new List<TimeZoneInfo> ();
+                               GetSystemTimeZonesCore (tz);
+                               Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
                        }
-                       return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
+
+                       return systemTimeZones;
                }
 
                public TimeSpan GetUtcOffset (DateTime dateTime)
@@ -880,6 +903,65 @@ namespace System
                        throw new NotImplementedException ();
                }
 
+               internal DaylightTime GetDaylightChanges (int year)
+               {
+                       DateTime start = DateTime.MinValue, end = DateTime.MinValue;
+                       TimeSpan delta = new TimeSpan ();
+
+                       if (transitions != null) {
+                               end = DateTime.MaxValue;
+                               for (var i =  transitions.Count - 1; i >= 0; i--) {
+                                       var pair = transitions [i];
+                                       DateTime ttime = pair.Key;
+                                       TimeType ttype = pair.Value;
+
+                                       if (ttime.Year > year)
+                                               continue;
+                                       if (ttime.Year < year)
+                                               break;
+
+                                       if (ttype.IsDst) {
+                                               // DaylightTime.Delta is relative to the current BaseUtcOffset.
+                                               delta =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
+                                               start = ttime;
+                                       } else {
+                                               end = ttime;
+                                       }
+                               }
+
+                               // DaylightTime.Start is relative to the Standard time.
+                               if (start != DateTime.MinValue)
+                                       start += BaseUtcOffset;
+
+                               // DaylightTime.End is relative to the DST time.
+                               if (end != DateTime.MinValue)
+                                       end += BaseUtcOffset + delta;
+                       } else {
+                               AdjustmentRule first = null, last = null;
+
+                               foreach (var rule in GetAdjustmentRules ()) {
+                                       if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
+                                               continue;
+                                       if (rule.DateStart.Year == year)
+                                               first = rule;
+                                       if (rule.DateEnd.Year == year)
+                                               last = rule;
+                               }
+
+                               if (first == null || last == null)
+                                       return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
+
+                               start = TransitionPoint (first.DaylightTransitionStart, year);
+                               end = TransitionPoint (last.DaylightTransitionEnd, year);
+                               delta = first.DaylightDelta;
+                       }
+
+                       if (start == DateTime.MinValue || end == DateTime.MinValue)
+                               return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
+
+                       return new DaylightTime (start, end, delta);
+               }
+
                public bool IsInvalidTime (DateTime dateTime)
                {
                        if (dateTime.Kind == DateTimeKind.Utc)
@@ -1114,7 +1196,24 @@ namespace System
                        return adjustmentRules;
                }
 
-#if LIBC || MONODROID
+#if LIBC || MONOTOUCH
+               const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
+               
+               private static TimeZoneInfo BuildFromStream (string id, Stream stream)
+               {
+                       byte [] buffer = new byte [BUFFER_SIZE];
+                       int length = stream.Read (buffer, 0, BUFFER_SIZE);
+                       
+                       if (!ValidTZFile (buffer, length))
+                               throw new InvalidTimeZoneException ("TZ file too big for the buffer");
+
+                       try {
+                               return ParseTZBuffer (id, buffer, length);
+                       } catch (Exception e) {
+                               throw new InvalidTimeZoneException (e.Message);
+                       }
+               }
+
                private static bool ValidTZFile (byte [] buffer, int length)
                {
                        StringBuilder magic = new StringBuilder ();
@@ -1236,8 +1335,10 @@ namespace System
                                        if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
                                                // Round to nearest minute, since it's not possible to create an adjustment rule
                                                // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
-                                               // This happens with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
-                                               dstDelta = new TimeSpan (0, 0, ttype.Offset - ttype.Offset % 60) - baseUtcOffset;
+                                               // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
+                                               dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
+                                               if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
+                                                       dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
                                        }
 
                                        dst_start = ttime;
@@ -1257,8 +1358,10 @@ namespace System
                                tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
                        }
 
-                       if (storeTransition)
+                       if (storeTransition && transitions.Count > 0) {
                                tz.transitions = transitions;
+                               tz.supportsDaylightSavingTime = true;
+                       }
 
                        return tz;
                }