[monodroid] Add support for the Android TimeZone file format.
authorJonathan Pryor <jonpryor@vt.edu>
Thu, 16 Dec 2010 18:10:44 +0000 (13:10 -0500)
committerJonathan Pryor <jonpryor@vt.edu>
Thu, 16 Dec 2010 18:17:38 +0000 (13:17 -0500)
Fixes #657609.

Android uses "ye standard" timezone file format, but instead of using
a directory + file structure as libc uses, they throw everything into
two files, a "zoneinfo.dat" and a "zoneinfo.idx", where "zoneinfo.dat"
is the concatenation of all the TZIF files and "zoneinfo.idx" contains
the timezone names and offsets into "zoneinfo.dat".  From the
ZoneInfoDB documentation:

However, to conserve disk space the data for all time zones
are concatenated into a single file, and a second file is
used to indicate the starting position of each time zone
record.  A third file indicates the version of the zoneinfo
databse used to generate the data.

TimeZoneInfo.Android.cs is a C# port of the corresponding Android
ZoneInfoDB type so that Mono can use Android's timezone DB.

LICENSE
mcs/class/System.Core/System/TimeZoneInfo.Android.cs [new file with mode: 0644]
mcs/class/System.Core/System/TimeZoneInfo.cs
mcs/class/System.Core/monodroid_System.Core.dll.sources

diff --git a/LICENSE b/LICENSE
index d78a60a852530dfc386e0580aa9703e0f554decd..0cb310e112686e2e4eafcfcf4274d5c5fa2cd9c0 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -98,6 +98,13 @@ For comments, corrections and updates, please contact mono@novell.com
             ICSharpCode.SharpZipLib, GPL with exceptions.
                See: mcs/class/ICSharpCode.SharpZipLib/README
 
+** mcs/class/System.Core/System/TimeZoneInfo.Android.cs
+
+       This is a port of Apache 2.0-licensed Android code, and thus is
+       licensed under the Apache 2.0 license:
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
             
 ** mcs/tools
 
diff --git a/mcs/class/System.Core/System/TimeZoneInfo.Android.cs b/mcs/class/System.Core/System/TimeZoneInfo.Android.cs
new file mode 100644 (file)
index 0000000..c95bb1e
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ * System.TimeZoneInfo Android Support
+ *
+ * Author(s)
+ *     Jonathan Pryor  <jpryor@novell.com>
+ *     The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if MONODROID
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System {
+
+       partial class TimeZoneInfo {
+
+               /*
+                * Android Timezone support infrastructure.
+                *
+                * This is a C# port of org.apache.harmony.luni.internal.util.ZoneInfoDB:
+                *
+                *    http://android.git.kernel.org/?p=platform/libcore.git;a=blob;f=luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfoDB.java;h=3e7bdc3a952b24da535806d434a3a27690feae26;hb=HEAD
+                *
+                * From the ZoneInfoDB source:
+                *
+                *    However, to conserve disk space the data for all time zones are 
+                *    concatenated into a single file, and a second file is used to indicate 
+                *    the starting position of each time zone record.  A third file indicates
+                *    the version of the zoneinfo databse used to generate the data.
+                *
+                * which succinctly describes why we can't just use the LIBC implementation in
+                * TimeZoneInfo.cs -- the "standard Unixy" directory structure is NOT used.
+                */
+               static class ZoneInfoDB {
+                       const int TimeZoneNameLength  = 40;
+                       const int TimeZoneIntSize     = 4;
+
+                       static readonly string ZoneDirectoryName  = Environment.GetEnvironmentVariable ("ANDROID_ROOT") + "/usr/share/zoneinfo/";
+                       static readonly string ZoneFileName       = ZoneDirectoryName + "zoneinfo.dat";
+                       static readonly string IndexFileName      = ZoneDirectoryName + "zoneinfo.idx";
+                       const           string DefaultVersion     = "2007h";
+                       static readonly string VersionFileName    = ZoneDirectoryName + "zoneinfo.version";
+
+                       static readonly object _lock = new object ();
+
+                       static readonly string    version;
+                       static readonly string[]  names;
+                       static readonly int[]     starts;
+                       static readonly int[]     lengths;
+                       static readonly int[]     offsets;
+
+                       static ZoneInfoDB ()
+                       {
+                               try {
+                                       version = ReadVersion ();
+                               } catch {
+                                       version = DefaultVersion;
+                               }
+
+                               try {
+                                       ReadDatabase (out names, out starts, out lengths, out offsets);
+                               } catch {
+                                       names   = new string [0];
+                                       starts  = new int [0];
+                                       lengths = new int [0];
+                                       offsets = new int [0];
+                               }
+                       }
+
+                       static string ReadVersion ()
+                       {
+                               using (var file = new StreamReader (VersionFileName, Encoding.GetEncoding ("iso-8859-1"))) {
+                                       return file.ReadToEnd ().Trim ();
+                               }
+                       }
+
+                       static void ReadDatabase (out string[] names, out int[] starts, out int[] lengths, out int[] offsets)
+                       {
+                               using (var file = File.OpenRead (IndexFileName)) {
+                                       var nbuf = new byte [TimeZoneNameLength];
+
+                                       int numEntries = (int) (file.Length / (TimeZoneNameLength + 3*TimeZoneIntSize));
+
+                                       char[]  namebuf = new char [TimeZoneNameLength];
+
+                                       names   = new string [numEntries];
+                                       starts  = new int [numEntries];
+                                       lengths = new int [numEntries];
+                                       offsets = new int [numEntries];
+
+                                       for (int i = 0; i < numEntries; ++i) {
+                                               Fill (file, nbuf, nbuf.Length);
+                                               int namelen;
+                                               for (namelen = 0; namelen < nbuf.Length; ++namelen) {
+                                                       if (nbuf [namelen] == '\0')
+                                                               break;
+                                                       namebuf [namelen] = (char) (nbuf [namelen] & 0xFF);
+                                               }
+
+                                               names   [i] = new string (namebuf, 0, namelen);
+                                               starts  [i] = ReadInt32 (file, nbuf);
+                                               lengths [i] = ReadInt32 (file, nbuf);
+                                               offsets [i] = ReadInt32 (file, nbuf);
+                                       }
+                               }
+                       }
+
+                       static void Fill (Stream stream, byte[] nbuf, int required)
+                       {
+                               int read, offset = 0;
+                               while (offset < required && (read = stream.Read (nbuf, offset, required - offset)) > 0)
+                                       offset += read;
+                               if (read != required)
+                                       throw new EndOfStreamException ("Needed to read " + required + " bytes; read " + read + " bytes");
+                       }
+
+                       // From java.io.RandomAccessFioe.readInt(), as we need to use the same
+                       // byte ordering as Java uses.
+                       static int ReadInt32 (Stream stream, byte[] nbuf)
+                       {
+                               Fill (stream, nbuf, 4);
+                               return ((nbuf [0] & 0xff) << 24) + ((nbuf [1] & 0xff) << 16) +
+                                       ((nbuf [2] & 0xff) << 8) + (nbuf [3] & 0xff);
+                       }
+
+                       internal static string Version {
+                               get {return version;}
+                       }
+
+                       internal static IEnumerable<string> GetAvailableIds ()
+                       {
+                               return GetAvailableIds (0, false);
+                       }
+
+                       internal static IEnumerable<string> GetAvailableIds (int rawOffset)
+                       {
+                               return GetAvailableIds (rawOffset, true);
+                       }
+
+                       static IEnumerable<string> GetAvailableIds (int rawOffset, bool checkOffset)
+                       {
+                               for (int i = 0; i < offsets.Length; ++i) {
+                                       if (!checkOffset || offsets [i] == rawOffset)
+                                               yield return names [i];
+                               }
+                       }
+
+                       static TimeZoneInfo _GetTimeZone (string name)
+                       {
+                               int start, length;
+                               using (var stream = GetTimeZoneData (name, out start, out length)) {
+                                       if (stream == null)
+                                               return null;
+                                       byte[] buf = new byte [length];
+                                       Fill (stream, buf, buf.Length);
+                                       return TimeZoneInfo.ParseTZBuffer (name, buf, length);
+                               }
+                       }
+
+                       static FileStream GetTimeZoneData (string name, out int start, out int length)
+                       {
+                               var f = new FileInfo (Path.Combine (ZoneDirectoryName, name));
+                               if (f.Exists) {
+                                       start   = 0;
+                                       length  = (int) f.Length;
+                                       return f.OpenRead ();
+                               }
+
+                               start = length = 0;
+
+                               int i = Array.BinarySearch (names, name);
+                               if (i < 0)
+                                       return null;
+
+                               start   = starts [i];
+                               length  = lengths [i];
+
+                               var stream = File.OpenRead (ZoneFileName);
+                               stream.Seek (start, SeekOrigin.Begin);
+
+                               return stream;
+                       }
+
+                       internal static TimeZoneInfo GetTimeZone (string id)
+                       {
+                               if (id != null) {
+                                       if (id == "GMT" || id == "UTC")
+                                               return new TimeZoneInfo (id, TimeSpan.FromSeconds (0), id, id, id, null, true);
+                                       if (id.StartsWith ("GMT"))
+                                               return new TimeZoneInfo (id,
+                                                               TimeSpan.FromSeconds (ParseNumericZone (id)),
+                                                               id, id, id, null, true);
+                               }
+
+                               try {
+                                       return _GetTimeZone (id);
+                               } catch (Exception e) {
+                                       return null;
+                               }
+                       }
+
+                       static int ParseNumericZone (string name)
+                       {
+                               if (name == null || !name.StartsWith ("GMT") || name.Length <= 3)
+                                       return 0;
+
+                               int sign;
+                               if (name [3] == '+')
+                                       sign = 1;
+                               else if (name [3] == '-')
+                                       sign = -1;
+                               else
+                                       return 0;
+
+                               int where;
+                               int hour = 0;
+                               bool colon = false;
+                               for (where = 4; where < name.Length; where++) {
+                                       char c = name [where];
+
+                                       if (c == ':') {
+                                               where++;
+                                               colon = true;
+                                               break;
+                                       }
+
+                                       if (c >= '0' && c <= '9')
+                                               hour = hour * 10 + c - '0';
+                                       else
+                                               return 0;
+                               }
+
+                               int min = 0;
+                               for (; where < name.Length; where++) {
+                                       char c = name [where];
+
+                                       if (c >= '0' && c <= '9')
+                                               min = min * 10 + c - '0';
+                                       else
+                                               return 0;
+                               }
+
+                               if (colon)
+                                       return sign * (hour * 60 + min) * 60;
+                               else if (hour >= 100)
+                                       return sign * ((hour / 100) * 60 + (hour % 100)) * 60;
+                               else
+                                       return sign * (hour * 60) * 60;
+                       }
+
+                       static TimeZoneInfo defaultZone;
+                       internal static TimeZoneInfo Default {
+                               get {
+                                       lock (_lock) {
+                                               if (defaultZone != null)
+                                                       return defaultZone;
+                                               return defaultZone = GetTimeZone (GetDefaultTimeZoneName ());
+                                       }
+                               }
+                       }
+
+                       // <sys/system_properties.h>
+                       [DllImport ("/system/lib/libc.so")]
+                       static extern int __system_property_get (string name, StringBuilder value);
+
+                       const int MaxPropertyNameLength   = 32; // <sys/system_properties.h>
+                       const int MaxPropertyValueLength  = 92; // <sys/system_properties.h>
+
+                       static string GetDefaultTimeZoneName ()
+                       {
+                               var buf = new StringBuilder (MaxPropertyValueLength + 1);
+                               int n = __system_property_get ("persist.sys.timezone", buf);
+                               if (n > 0)
+                                       return buf.ToString ();
+                               return null;
+                       }
+               }
+       }
+}
+
+#endif // MONODROID
+
index 67cafd775da8fc15ca3be3f17a659eac3865ccb9..cb464b54ad9cf17060cb97c7c1978c54a4b4bab6 100644 (file)
@@ -35,10 +35,11 @@ using System.Runtime.CompilerServices;
 
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Linq;
 using System.Runtime.Serialization;
 using System.Text;
 
-#if LIBC
+#if LIBC || MONODROID
 using System.IO;
 using Mono;
 #endif
@@ -83,7 +84,9 @@ namespace System
                public static TimeZoneInfo Local {
                        get { 
                                if (local == null) {
-#if LIBC
+#if MONODROID
+                                       local = ZoneInfoDB.Default;
+#elif LIBC
                                        try {
                                                local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");       
                                        } catch {
@@ -320,7 +323,9 @@ namespace System
                                return FromRegistryKey(id, key);
                        }
 #endif
-#if LIBC       
+#if MONODROID
+                       return ZoneInfoDB.GetTimeZone (id);
+#elif LIBC
                        string filepath = Path.Combine (TimeZoneDirectory, id);
                        return FindSystemTimeZoneByFileName (id, filepath);
 #else
@@ -522,7 +527,10 @@ namespace System
                                        return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
                                }
 #endif
-#if LIBC
+#if MONODROID
+                       systemTimeZones.AddRange (ZoneInfoDB.GetAvailableIds ()
+                                       .Select (id => ZoneInfoDB.GetTimeZone (id)));
+#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 {
@@ -782,7 +790,7 @@ namespace System
                        return adjustmentRules;
                }
 
-#if LIBC
+#if LIBC || MONODROID
                private static bool ValidTZFile (byte [] buffer, int length)
                {
                        StringBuilder magic = new StringBuilder ();
index 0129121538cd8d5652a3e79f965ac9d33f0ba2bc..a1b6e09476ded1d432d115d4a1da8f91e9d4455f 100644 (file)
@@ -1 +1,2 @@
 #include monodroid_bootstrap_System.Core.dll.sources
+System/TimeZoneInfo.Android.cs