[monodroid] Add support for the Android TimeZone file format.
[mono.git] / mcs / class / System.Core / System / TimeZoneInfo.Android.cs
1 /*
2  * System.TimeZoneInfo Android Support
3  *
4  * Author(s)
5  *      Jonathan Pryor  <jpryor@novell.com>
6  *      The Android Open Source Project
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20
21 #if MONODROID
22
23 using System;
24 using System.Collections.Generic;
25 using System.IO;
26 using System.Runtime.CompilerServices;
27 using System.Runtime.InteropServices;
28 using System.Text;
29
30 namespace System {
31
32         partial class TimeZoneInfo {
33
34                 /*
35                  * Android Timezone support infrastructure.
36                  *
37                  * This is a C# port of org.apache.harmony.luni.internal.util.ZoneInfoDB:
38                  *
39                  *    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
40                  *
41                  * From the ZoneInfoDB source:
42                  *
43                  *    However, to conserve disk space the data for all time zones are 
44                  *    concatenated into a single file, and a second file is used to indicate 
45                  *    the starting position of each time zone record.  A third file indicates
46                  *    the version of the zoneinfo databse used to generate the data.
47                  *
48                  * which succinctly describes why we can't just use the LIBC implementation in
49                  * TimeZoneInfo.cs -- the "standard Unixy" directory structure is NOT used.
50                  */
51                 static class ZoneInfoDB {
52                         const int TimeZoneNameLength  = 40;
53                         const int TimeZoneIntSize     = 4;
54
55                         static readonly string ZoneDirectoryName  = Environment.GetEnvironmentVariable ("ANDROID_ROOT") + "/usr/share/zoneinfo/";
56                         static readonly string ZoneFileName       = ZoneDirectoryName + "zoneinfo.dat";
57                         static readonly string IndexFileName      = ZoneDirectoryName + "zoneinfo.idx";
58                         const           string DefaultVersion     = "2007h";
59                         static readonly string VersionFileName    = ZoneDirectoryName + "zoneinfo.version";
60
61                         static readonly object _lock = new object ();
62
63                         static readonly string    version;
64                         static readonly string[]  names;
65                         static readonly int[]     starts;
66                         static readonly int[]     lengths;
67                         static readonly int[]     offsets;
68
69                         static ZoneInfoDB ()
70                         {
71                                 try {
72                                         version = ReadVersion ();
73                                 } catch {
74                                         version = DefaultVersion;
75                                 }
76
77                                 try {
78                                         ReadDatabase (out names, out starts, out lengths, out offsets);
79                                 } catch {
80                                         names   = new string [0];
81                                         starts  = new int [0];
82                                         lengths = new int [0];
83                                         offsets = new int [0];
84                                 }
85                         }
86
87                         static string ReadVersion ()
88                         {
89                                 using (var file = new StreamReader (VersionFileName, Encoding.GetEncoding ("iso-8859-1"))) {
90                                         return file.ReadToEnd ().Trim ();
91                                 }
92                         }
93
94                         static void ReadDatabase (out string[] names, out int[] starts, out int[] lengths, out int[] offsets)
95                         {
96                                 using (var file = File.OpenRead (IndexFileName)) {
97                                         var nbuf = new byte [TimeZoneNameLength];
98
99                                         int numEntries = (int) (file.Length / (TimeZoneNameLength + 3*TimeZoneIntSize));
100
101                                         char[]  namebuf = new char [TimeZoneNameLength];
102
103                                         names   = new string [numEntries];
104                                         starts  = new int [numEntries];
105                                         lengths = new int [numEntries];
106                                         offsets = new int [numEntries];
107
108                                         for (int i = 0; i < numEntries; ++i) {
109                                                 Fill (file, nbuf, nbuf.Length);
110                                                 int namelen;
111                                                 for (namelen = 0; namelen < nbuf.Length; ++namelen) {
112                                                         if (nbuf [namelen] == '\0')
113                                                                 break;
114                                                         namebuf [namelen] = (char) (nbuf [namelen] & 0xFF);
115                                                 }
116
117                                                 names   [i] = new string (namebuf, 0, namelen);
118                                                 starts  [i] = ReadInt32 (file, nbuf);
119                                                 lengths [i] = ReadInt32 (file, nbuf);
120                                                 offsets [i] = ReadInt32 (file, nbuf);
121                                         }
122                                 }
123                         }
124
125                         static void Fill (Stream stream, byte[] nbuf, int required)
126                         {
127                                 int read, offset = 0;
128                                 while (offset < required && (read = stream.Read (nbuf, offset, required - offset)) > 0)
129                                         offset += read;
130                                 if (read != required)
131                                         throw new EndOfStreamException ("Needed to read " + required + " bytes; read " + read + " bytes");
132                         }
133
134                         // From java.io.RandomAccessFioe.readInt(), as we need to use the same
135                         // byte ordering as Java uses.
136                         static int ReadInt32 (Stream stream, byte[] nbuf)
137                         {
138                                 Fill (stream, nbuf, 4);
139                                 return ((nbuf [0] & 0xff) << 24) + ((nbuf [1] & 0xff) << 16) +
140                                         ((nbuf [2] & 0xff) << 8) + (nbuf [3] & 0xff);
141                         }
142
143                         internal static string Version {
144                                 get {return version;}
145                         }
146
147                         internal static IEnumerable<string> GetAvailableIds ()
148                         {
149                                 return GetAvailableIds (0, false);
150                         }
151
152                         internal static IEnumerable<string> GetAvailableIds (int rawOffset)
153                         {
154                                 return GetAvailableIds (rawOffset, true);
155                         }
156
157                         static IEnumerable<string> GetAvailableIds (int rawOffset, bool checkOffset)
158                         {
159                                 for (int i = 0; i < offsets.Length; ++i) {
160                                         if (!checkOffset || offsets [i] == rawOffset)
161                                                 yield return names [i];
162                                 }
163                         }
164
165                         static TimeZoneInfo _GetTimeZone (string name)
166                         {
167                                 int start, length;
168                                 using (var stream = GetTimeZoneData (name, out start, out length)) {
169                                         if (stream == null)
170                                                 return null;
171                                         byte[] buf = new byte [length];
172                                         Fill (stream, buf, buf.Length);
173                                         return TimeZoneInfo.ParseTZBuffer (name, buf, length);
174                                 }
175                         }
176
177                         static FileStream GetTimeZoneData (string name, out int start, out int length)
178                         {
179                                 var f = new FileInfo (Path.Combine (ZoneDirectoryName, name));
180                                 if (f.Exists) {
181                                         start   = 0;
182                                         length  = (int) f.Length;
183                                         return f.OpenRead ();
184                                 }
185
186                                 start = length = 0;
187
188                                 int i = Array.BinarySearch (names, name);
189                                 if (i < 0)
190                                         return null;
191
192                                 start   = starts [i];
193                                 length  = lengths [i];
194
195                                 var stream = File.OpenRead (ZoneFileName);
196                                 stream.Seek (start, SeekOrigin.Begin);
197
198                                 return stream;
199                         }
200
201                         internal static TimeZoneInfo GetTimeZone (string id)
202                         {
203                                 if (id != null) {
204                                         if (id == "GMT" || id == "UTC")
205                                                 return new TimeZoneInfo (id, TimeSpan.FromSeconds (0), id, id, id, null, true);
206                                         if (id.StartsWith ("GMT"))
207                                                 return new TimeZoneInfo (id,
208                                                                 TimeSpan.FromSeconds (ParseNumericZone (id)),
209                                                                 id, id, id, null, true);
210                                 }
211
212                                 try {
213                                         return _GetTimeZone (id);
214                                 } catch (Exception e) {
215                                         return null;
216                                 }
217                         }
218
219                         static int ParseNumericZone (string name)
220                         {
221                                 if (name == null || !name.StartsWith ("GMT") || name.Length <= 3)
222                                         return 0;
223
224                                 int sign;
225                                 if (name [3] == '+')
226                                         sign = 1;
227                                 else if (name [3] == '-')
228                                         sign = -1;
229                                 else
230                                         return 0;
231
232                                 int where;
233                                 int hour = 0;
234                                 bool colon = false;
235                                 for (where = 4; where < name.Length; where++) {
236                                         char c = name [where];
237
238                                         if (c == ':') {
239                                                 where++;
240                                                 colon = true;
241                                                 break;
242                                         }
243
244                                         if (c >= '0' && c <= '9')
245                                                 hour = hour * 10 + c - '0';
246                                         else
247                                                 return 0;
248                                 }
249
250                                 int min = 0;
251                                 for (; where < name.Length; where++) {
252                                         char c = name [where];
253
254                                         if (c >= '0' && c <= '9')
255                                                 min = min * 10 + c - '0';
256                                         else
257                                                 return 0;
258                                 }
259
260                                 if (colon)
261                                         return sign * (hour * 60 + min) * 60;
262                                 else if (hour >= 100)
263                                         return sign * ((hour / 100) * 60 + (hour % 100)) * 60;
264                                 else
265                                         return sign * (hour * 60) * 60;
266                         }
267
268                         static TimeZoneInfo defaultZone;
269                         internal static TimeZoneInfo Default {
270                                 get {
271                                         lock (_lock) {
272                                                 if (defaultZone != null)
273                                                         return defaultZone;
274                                                 return defaultZone = GetTimeZone (GetDefaultTimeZoneName ());
275                                         }
276                                 }
277                         }
278
279                         // <sys/system_properties.h>
280                         [DllImport ("/system/lib/libc.so")]
281                         static extern int __system_property_get (string name, StringBuilder value);
282
283                         const int MaxPropertyNameLength   = 32; // <sys/system_properties.h>
284                         const int MaxPropertyValueLength  = 92; // <sys/system_properties.h>
285
286                         static string GetDefaultTimeZoneName ()
287                         {
288                                 var buf = new StringBuilder (MaxPropertyValueLength + 1);
289                                 int n = __system_property_get ("persist.sys.timezone", buf);
290                                 if (n > 0)
291                                         return buf.ToString ();
292                                 return null;
293                         }
294                 }
295         }
296 }
297
298 #endif // MONODROID
299