From: Jonathan Pryor Date: Thu, 16 Dec 2010 18:10:44 +0000 (-0500) Subject: [monodroid] Add support for the Android TimeZone file format. X-Git-Url: http://wien.tomnetworks.com/gitweb/?p=mono.git;a=commitdiff_plain;h=8a263984e95113e8cb753e8f8b05b2342cb8e2d1 [monodroid] Add support for the Android TimeZone file format. 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. --- diff --git a/LICENSE b/LICENSE index d78a60a8525..0cb310e1126 100644 --- 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 index 00000000000..c95bb1ef15c --- /dev/null +++ b/mcs/class/System.Core/System/TimeZoneInfo.Android.cs @@ -0,0 +1,299 @@ +/* + * System.TimeZoneInfo Android Support + * + * Author(s) + * Jonathan Pryor + * 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 GetAvailableIds () + { + return GetAvailableIds (0, false); + } + + internal static IEnumerable GetAvailableIds (int rawOffset) + { + return GetAvailableIds (rawOffset, true); + } + + static IEnumerable 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 ()); + } + } + } + + // + [DllImport ("/system/lib/libc.so")] + static extern int __system_property_get (string name, StringBuilder value); + + const int MaxPropertyNameLength = 32; // + const int MaxPropertyValueLength = 92; // + + 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 + diff --git a/mcs/class/System.Core/System/TimeZoneInfo.cs b/mcs/class/System.Core/System/TimeZoneInfo.cs index 67cafd775da..cb464b54ad9 100644 --- a/mcs/class/System.Core/System/TimeZoneInfo.cs +++ b/mcs/class/System.Core/System/TimeZoneInfo.cs @@ -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 (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 (); diff --git a/mcs/class/System.Core/monodroid_System.Core.dll.sources b/mcs/class/System.Core/monodroid_System.Core.dll.sources index 0129121538c..a1b6e09476d 100644 --- a/mcs/class/System.Core/monodroid_System.Core.dll.sources +++ b/mcs/class/System.Core/monodroid_System.Core.dll.sources @@ -1 +1,2 @@ #include monodroid_bootstrap_System.Core.dll.sources +System/TimeZoneInfo.Android.cs