Merge pull request #1936 from esdrubal/DotNetRelativeOrAbsolute
[mono.git] / mcs / class / corlib / 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         interface IAndroidTimeZoneDB {
33                 IEnumerable<string>   GetAvailableIds ();
34                 byte[]                GetTimeZoneData (string id);
35         }
36
37         [StructLayout (LayoutKind.Sequential, Pack=1)]
38         unsafe struct AndroidTzDataHeader {
39                 public fixed byte signature [12];
40                 public int        indexOffset;
41                 public int        dataOffset;
42                 public int        zoneTabOffset;
43         }
44
45         [StructLayout (LayoutKind.Sequential, Pack=1)]
46         unsafe struct AndroidTzDataEntry {
47                 public fixed byte id [40];
48                 public int        byteOffset;
49                 public int        length;
50                 public int        rawUtcOffset;
51         }
52
53         /*
54          * Android v4.3 Timezone support infrastructure.
55          *
56          * This is a C# port of libcore.util.ZoneInfoDB:
57          *
58          *    https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/util/ZoneInfoDB.java
59          *
60          * This is needed in order to read Android v4.3 tzdata files.
61          */
62         sealed class AndroidTzData : IAndroidTimeZoneDB {
63
64                 internal static readonly string[] Paths = new string[]{
65                         Environment.GetEnvironmentVariable ("ANDROID_DATA") + "/misc/zoneinfo/tzdata",
66                         Environment.GetEnvironmentVariable ("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata",
67                 };
68
69                 string    tzdataPath;
70                 Stream    data;
71                 string    version;
72                 string    zoneTab;
73
74                 string[]  ids;
75                 int[]     byteOffsets;
76                 int[]     lengths;
77
78                 public AndroidTzData (params string[] paths)
79                 {
80                         foreach (var path in paths)
81                                 if (LoadData (path)) {
82                                         tzdataPath = path;
83                                         return;
84                                 }
85
86                         Console.Error.WriteLine ("Couldn't find any tzdata!");
87                         tzdataPath  = "/";
88                         version     = "missing";
89                         zoneTab     = "# Emergency fallback data.\n";
90                         ids         = new[]{ "GMT" };
91                 }
92
93                 public string Version {
94                         get {return version;}
95                 }
96
97                 public string ZoneTab {
98                         get {return zoneTab;}
99                 }
100
101                 bool LoadData (string path)
102                 {
103                         if (!File.Exists (path))
104                                 return false;
105                         try {
106                                 data = File.OpenRead (path);
107                         } catch (IOException) {
108                                 return false;
109                         } catch (UnauthorizedAccessException) {
110                                 return false;
111                         }
112
113                         try {
114                                 ReadHeader ();
115                                 return true;
116                         } catch (Exception e) {
117                                 Console.Error.WriteLine ("tzdata file \"{0}\" was present but invalid: {1}", path, e);
118                         }
119                         return false;
120                 }
121
122                 unsafe void ReadHeader ()
123                 {
124                         int size   = System.Math.Max (Marshal.SizeOf (typeof (AndroidTzDataHeader)), Marshal.SizeOf (typeof (AndroidTzDataEntry)));
125                         var buffer = new byte [size];
126                         var header = ReadAt<AndroidTzDataHeader>(0, buffer);
127
128                         header.indexOffset    = NetworkToHostOrder (header.indexOffset);
129                         header.dataOffset     = NetworkToHostOrder (header.dataOffset);
130                         header.zoneTabOffset  = NetworkToHostOrder (header.zoneTabOffset);
131
132                         sbyte* s = (sbyte*) header.signature;
133                         string magic = new string (s, 0, 6, Encoding.ASCII);
134                         if (magic != "tzdata" || header.signature [11] != 0) {
135                                 var b = new StringBuilder ();
136                                 b.Append ("bad tzdata magic:");
137                                 for (int i = 0; i < 12; ++i) {
138                                         b.Append (" ").Append (((byte) s [i]).ToString ("x2"));
139                                 }
140                                 throw new InvalidOperationException ("bad tzdata magic: " + b.ToString ());
141                         }
142
143                         version = new string (s, 6, 5, Encoding.ASCII);
144
145                         ReadIndex (header.indexOffset, header.dataOffset, buffer);
146                         ReadZoneTab (header.zoneTabOffset, checked ((int) data.Length) - header.zoneTabOffset);
147                 }
148
149                 unsafe T ReadAt<T> (long position, byte[] buffer)
150                         where T : struct
151                 {
152                         int size = Marshal.SizeOf (typeof (T));
153                         if (buffer.Length < size)
154                                 throw new InvalidOperationException ("Internal error: buffer too small");
155
156                         data.Position = position;
157                         int r;
158                         if ((r = data.Read (buffer, 0, size)) < size)
159                                 throw new InvalidOperationException (
160                                                 string.Format ("Error reading '{0}': read {1} bytes, expected {2}", tzdataPath, r, size));
161
162                         fixed (byte* b = buffer)
163                                 return (T) Marshal.PtrToStructure ((IntPtr) b, typeof (T));
164                 }
165
166                 static int NetworkToHostOrder (int value)
167                 {
168                         if (!BitConverter.IsLittleEndian)
169                                 return value;
170
171                         return
172                                 (((value >> 24) & 0xFF) |
173                                  ((value >> 08) & 0xFF00) |
174                                  ((value << 08) & 0xFF0000) |
175                                  ((value << 24)));
176                 }
177
178                 unsafe void ReadIndex (int indexOffset, int dataOffset, byte[] buffer)
179                 {
180                         int indexSize   = dataOffset - indexOffset;
181                         int entryCount  = indexSize / Marshal.SizeOf (typeof (AndroidTzDataEntry));
182                         int entrySize   = Marshal.SizeOf (typeof (AndroidTzDataEntry));
183
184                         byteOffsets   = new int [entryCount];
185                         ids           = new string [entryCount];
186                         lengths       = new int [entryCount];
187
188                         for (int i = 0; i < entryCount; ++i) {
189                                 var entry = ReadAt<AndroidTzDataEntry>(indexOffset + (entrySize*i), buffer);
190                                 var p     = (sbyte*) entry.id;
191
192                                 byteOffsets [i]   = NetworkToHostOrder (entry.byteOffset) + dataOffset;
193                                 ids [i]           = new string (p, 0, GetStringLength (p, 40), Encoding.ASCII);
194                                 lengths [i]       = NetworkToHostOrder (entry.length);
195
196                                 if (lengths [i] < Marshal.SizeOf (typeof (AndroidTzDataHeader)))
197                                         throw new InvalidOperationException ("Length in index file < sizeof(tzhead)");
198                         }
199                 }
200
201                 static unsafe int GetStringLength (sbyte* s, int maxLength)
202                 {
203                         int len;
204                         for (len = 0; len < maxLength; len++, s++) {
205                                 if (*s == 0)
206                                         break;
207                         }
208                         return len;
209                 }
210
211                 unsafe void ReadZoneTab (int zoneTabOffset, int zoneTabSize)
212                 {
213                         byte[] zoneTab = new byte [zoneTabSize];
214
215                         data.Position = zoneTabOffset;
216
217                         int r;
218                         if ((r = data.Read (zoneTab, 0, zoneTab.Length)) < zoneTab.Length)
219                                 throw new InvalidOperationException (
220                                                 string.Format ("Error reading zonetab: read {0} bytes, expected {1}", r, zoneTabSize));
221
222                         this.zoneTab = Encoding.ASCII.GetString (zoneTab, 0, zoneTab.Length);
223                 }
224
225                 public IEnumerable<string> GetAvailableIds ()
226                 {
227                         return ids;
228                 }
229
230                 public byte[] GetTimeZoneData (string id)
231                 {
232                         int i = Array.BinarySearch (ids, id, StringComparer.Ordinal);
233                         if (i < 0)
234                                 return null;
235
236                         int offset = byteOffsets [i];
237                         int length = lengths [i];
238                         var buffer = new byte [length];
239
240                         lock (data) {
241                                 data.Position = offset;
242                                 int r;
243                                 if ((r = data.Read (buffer, 0, buffer.Length)) < buffer.Length)
244                                         throw new InvalidOperationException (
245                                                         string.Format ("Unable to fully read from file '{0}' at offset {1} length {2}; read {3} bytes expected {4}.",
246                                                                 tzdataPath, offset, length, r, buffer.Length));
247                         }
248
249                         TimeZoneInfo.DumpTimeZoneDataToFile (id, buffer);
250                         return buffer;
251                 }
252         }
253
254         partial class TimeZoneInfo {
255
256                 static TimeZoneInfo CreateLocal ()
257                 {
258                         return AndroidTimeZones.Local;
259                 }
260
261                 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
262                 {
263                         var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
264                         if (timeZoneInfo == null)
265                                 throw new TimeZoneNotFoundException ();
266                         return timeZoneInfo;
267                 }
268
269                 static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
270                 {
271                         foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
272                                 var tz = AndroidTimeZones.GetTimeZone (id, id);
273                                 if (tz != null)
274                                         systemTimeZones.Add (tz);
275                         }
276                 }
277
278                 /*
279                  * Android < v4.3 Timezone support infrastructure.
280                  *
281                  * This is a C# port of org.apache.harmony.luni.internal.util.ZoneInfoDB:
282                  *
283                  *    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
284                  *
285                  * From the ZoneInfoDB source:
286                  *
287                  *    However, to conserve disk space the data for all time zones are 
288                  *    concatenated into a single file, and a second file is used to indicate 
289                  *    the starting position of each time zone record.  A third file indicates
290                  *    the version of the zoneinfo databse used to generate the data.
291                  *
292                  * which succinctly describes why we can't just use the LIBC implementation in
293                  * TimeZoneInfo.cs -- the "standard Unixy" directory structure is NOT used.
294                  */
295                 sealed class ZoneInfoDB : IAndroidTimeZoneDB {
296                         const int TimeZoneNameLength  = 40;
297                         const int TimeZoneIntSize     = 4;
298
299                         internal static readonly string ZoneDirectoryName  = Environment.GetEnvironmentVariable ("ANDROID_ROOT") + "/usr/share/zoneinfo/";
300
301                         const    string ZoneFileName       = "zoneinfo.dat";
302                         const    string IndexFileName      = "zoneinfo.idx";
303                         const    string DefaultVersion     = "2007h";
304                         const    string VersionFileName    = "zoneinfo.version";
305
306                         readonly string    zoneRoot;
307                         readonly string    version;
308                         readonly string[]  names;
309                         readonly int[]     starts;
310                         readonly int[]     lengths;
311                         readonly int[]     offsets;
312
313                         public ZoneInfoDB (string zoneInfoDB = null)
314                         {
315                                 zoneRoot = zoneInfoDB ?? ZoneDirectoryName;
316                                 try {
317                                         version = ReadVersion (Path.Combine (zoneRoot, VersionFileName));
318                                 } catch {
319                                         version = DefaultVersion;
320                                 }
321
322                                 try {
323                                         ReadDatabase (Path.Combine (zoneRoot, IndexFileName), out names, out starts, out lengths, out offsets);
324                                 } catch {
325                                         names   = new string [0];
326                                         starts  = new int [0];
327                                         lengths = new int [0];
328                                         offsets = new int [0];
329                                 }
330                         }
331
332                         static string ReadVersion (string path)
333                         {
334                                 using (var file = new StreamReader (path, Encoding.GetEncoding ("iso-8859-1"))) {
335                                         return file.ReadToEnd ().Trim ();
336                                 }
337                         }
338
339                         void ReadDatabase (string path, out string[] names, out int[] starts, out int[] lengths, out int[] offsets)
340                         {
341                                 using (var file = File.OpenRead (path)) {
342                                         var nbuf = new byte [TimeZoneNameLength];
343
344                                         int numEntries = (int) (file.Length / (TimeZoneNameLength + 3*TimeZoneIntSize));
345
346                                         char[]  namebuf = new char [TimeZoneNameLength];
347
348                                         names   = new string [numEntries];
349                                         starts  = new int [numEntries];
350                                         lengths = new int [numEntries];
351                                         offsets = new int [numEntries];
352
353                                         for (int i = 0; i < numEntries; ++i) {
354                                                 Fill (file, nbuf, nbuf.Length);
355                                                 int namelen;
356                                                 for (namelen = 0; namelen < nbuf.Length; ++namelen) {
357                                                         if (nbuf [namelen] == '\0')
358                                                                 break;
359                                                         namebuf [namelen] = (char) (nbuf [namelen] & 0xFF);
360                                                 }
361
362                                                 names   [i] = new string (namebuf, 0, namelen);
363                                                 starts  [i] = ReadInt32 (file, nbuf);
364                                                 lengths [i] = ReadInt32 (file, nbuf);
365                                                 offsets [i] = ReadInt32 (file, nbuf);
366                                         }
367                                 }
368                         }
369
370                         static void Fill (Stream stream, byte[] nbuf, int required)
371                         {
372                                 int read = 0, offset = 0;
373                                 while (offset < required && (read = stream.Read (nbuf, offset, required - offset)) > 0)
374                                         offset += read;
375                                 if (read != required)
376                                         throw new EndOfStreamException ("Needed to read " + required + " bytes; read " + read + " bytes");
377                         }
378
379                         // From java.io.RandomAccessFioe.readInt(), as we need to use the same
380                         // byte ordering as Java uses.
381                         static int ReadInt32 (Stream stream, byte[] nbuf)
382                         {
383                                 Fill (stream, nbuf, 4);
384                                 return ((nbuf [0] & 0xff) << 24) + ((nbuf [1] & 0xff) << 16) +
385                                         ((nbuf [2] & 0xff) << 8) + (nbuf [3] & 0xff);
386                         }
387
388                         internal string Version {
389                                 get {return version;}
390                         }
391
392                         public IEnumerable<string> GetAvailableIds ()
393                         {
394                                 return GetAvailableIds (0, false);
395                         }
396
397                         IEnumerable<string> GetAvailableIds (int rawOffset)
398                         {
399                                 return GetAvailableIds (rawOffset, true);
400                         }
401
402                         IEnumerable<string> GetAvailableIds (int rawOffset, bool checkOffset)
403                         {
404                                 for (int i = 0; i < offsets.Length; ++i) {
405                                         if (!checkOffset || offsets [i] == rawOffset)
406                                                 yield return names [i];
407                                 }
408                         }
409
410                         public byte[] GetTimeZoneData (string id)
411                         {
412                                 int start, length;
413                                 using (var stream = GetTimeZoneData (id, out start, out length)) {
414                                         if (stream == null)
415                                                 return null;
416                                         byte[] buf = new byte [length];
417                                         Fill (stream, buf, buf.Length);
418                                         return buf;
419                                 }
420                         }
421
422                         FileStream GetTimeZoneData (string name, out int start, out int length)
423                         {
424                                 if (name == null) { // Just in case, to avoid NREX as in xambug #4902
425                                         start = 0;
426                                         length = 0;
427                                         return null;
428                                 }
429                                 
430                                 var f = new FileInfo (Path.Combine (zoneRoot, name));
431                                 if (f.Exists) {
432                                         start   = 0;
433                                         length  = (int) f.Length;
434                                         return f.OpenRead ();
435                                 }
436
437                                 start = length = 0;
438
439                                 int i = Array.BinarySearch (names, name, StringComparer.Ordinal);
440                                 if (i < 0)
441                                         return null;
442
443                                 start   = starts [i];
444                                 length  = lengths [i];
445
446                                 var stream = File.OpenRead (Path.Combine (zoneRoot, ZoneFileName));
447                                 stream.Seek (start, SeekOrigin.Begin);
448
449                                 return stream;
450                         }
451                 }
452
453                 static class AndroidTimeZones {
454
455                         static IAndroidTimeZoneDB db;
456
457                         static AndroidTimeZones ()
458                         {
459                                 db = GetDefaultTimeZoneDB ();
460                         }
461
462                         static IAndroidTimeZoneDB GetDefaultTimeZoneDB ()
463                         {
464                                 foreach (var p in AndroidTzData.Paths)
465                                         if (File.Exists (p))
466                                                 return new AndroidTzData (AndroidTzData.Paths);
467                                 if (Directory.Exists (ZoneInfoDB.ZoneDirectoryName))
468                                         return new ZoneInfoDB ();
469                                 return null;
470                         }
471
472                         internal static IEnumerable<string> GetAvailableIds ()
473                         {
474                                 return db == null
475                                         ? new string [0]
476                                         : db.GetAvailableIds ();
477                         }
478
479                         static TimeZoneInfo _GetTimeZone (string id, string name)
480                         {
481                                 if (db == null)
482                                         return null;
483                                 byte[] buffer = db.GetTimeZoneData (name);
484                                 if (buffer == null)
485                                         return null;
486                                 return TimeZoneInfo.ParseTZBuffer (id, buffer, buffer.Length);
487                         }
488
489                         internal static TimeZoneInfo GetTimeZone (string id, string name)
490                         {
491                                 if (name != null) {
492                                         if (name == "GMT" || name == "UTC")
493                                                 return new TimeZoneInfo (id, TimeSpan.FromSeconds (0), id, name, name, null, disableDaylightSavingTime:true);
494                                         if (name.StartsWith ("GMT"))
495                                                 return new TimeZoneInfo (id,
496                                                                 TimeSpan.FromSeconds (ParseNumericZone (name)),
497                                                                 id, name, name, null, disableDaylightSavingTime:true);
498                                 }
499
500                                 try {
501                                         return _GetTimeZone (id, name);
502                                 } catch (Exception) {
503                                         return null;
504                                 }
505                         }
506
507                         static int ParseNumericZone (string name)
508                         {
509                                 if (name == null || !name.StartsWith ("GMT") || name.Length <= 3)
510                                         return 0;
511
512                                 int sign;
513                                 if (name [3] == '+')
514                                         sign = 1;
515                                 else if (name [3] == '-')
516                                         sign = -1;
517                                 else
518                                         return 0;
519
520                                 int where;
521                                 int hour = 0;
522                                 bool colon = false;
523                                 for (where = 4; where < name.Length; where++) {
524                                         char c = name [where];
525
526                                         if (c == ':') {
527                                                 where++;
528                                                 colon = true;
529                                                 break;
530                                         }
531
532                                         if (c >= '0' && c <= '9')
533                                                 hour = hour * 10 + c - '0';
534                                         else
535                                                 return 0;
536                                 }
537
538                                 int min = 0;
539                                 for (; where < name.Length; where++) {
540                                         char c = name [where];
541
542                                         if (c >= '0' && c <= '9')
543                                                 min = min * 10 + c - '0';
544                                         else
545                                                 return 0;
546                                 }
547
548                                 if (colon)
549                                         return sign * (hour * 60 + min) * 60;
550                                 else if (hour >= 100)
551                                         return sign * ((hour / 100) * 60 + (hour % 100)) * 60;
552                                 else
553                                         return sign * (hour * 60) * 60;
554                         }
555
556                         static TimeZoneInfo defaultZone;
557                         internal static TimeZoneInfo Local {
558                                 get {
559                                         var id  = GetDefaultTimeZoneName ();
560                                         return defaultZone = GetTimeZone (id, id);
561                                 }
562                         }
563                         
564                         [DllImport ("__Internal")]
565                         static extern int monodroid_get_system_property (string name, ref IntPtr value);
566
567                         [DllImport ("__Internal")]
568                         static extern void monodroid_free (IntPtr ptr);
569                         
570                         static string GetDefaultTimeZoneName ()
571                         {
572                                 IntPtr value = IntPtr.Zero;
573                                 int n = 0;
574                                 string defaultTimeZone  = Environment.GetEnvironmentVariable ("__XA_OVERRIDE_TIMEZONE_ID__");
575
576                                 if (!string.IsNullOrEmpty (defaultTimeZone))
577                                         return defaultTimeZone;
578
579                                 // Used by the tests
580                                 if (Environment.GetEnvironmentVariable ("__XA_USE_JAVA_DEFAULT_TIMEZONE_ID__") == null)
581                                         n = monodroid_get_system_property ("persist.sys.timezone", ref value);
582                                 
583                                 if (n > 0 && value != IntPtr.Zero) {
584                                         defaultTimeZone = (Marshal.PtrToStringAnsi (value) ?? String.Empty).Trim ();
585                                         monodroid_free (value);
586                                         if (!String.IsNullOrEmpty (defaultTimeZone))
587                                                 return defaultTimeZone;
588                                 }
589                                 
590                                 defaultTimeZone = (AndroidPlatform.GetDefaultTimeZone () ?? String.Empty).Trim ();
591                                 if (!String.IsNullOrEmpty (defaultTimeZone))
592                                         return defaultTimeZone;
593
594                                 return null;
595                         }
596
597 #if SELF_TEST
598                         /*
599                          * Compile:
600                          *    mcs /debug+ /out:tzi.exe /unsafe "/d:INSIDE_CORLIB;MONODROID;NET_4_0;LIBC;SELF_TEST" ../corlib/System/AndroidPlatform.cs System/TimeZone*.cs ../../build/common/Consts.cs ../Mono.Options/Mono.Options/Options.cs
601                          * Prep:
602                          *    mkdir -p android/tzdb/usr/share/zoneinfo
603                          *    mkdir -p android/tzdb/misc/zoneinfo/zoneinfo
604                          *    android_root=`adb shell echo '$ANDROID_ROOT' | tr -d "\r"`
605                          *    android_data=`adb shell echo '$ANDROID_DATA' | tr -d "\r"`
606                          *    adb pull $android_root/usr/share/zoneinfo   android/tzdb/usr/share/zoneinfo
607                          *    adb pull $android_data/misc/zoneinfo/tzdata android/tzdb/misc/zoneinfo
608                          * Run:
609                          *    # Dump all timezone names
610                          *    __XA_OVERRIDE_TIMEZONE_ID__=America/New_York ANDROID_ROOT=`pwd` ANDROID_DATA=`pwd` mono --debug tzi.exe --offset=1969-01-01
611                          *
612                          *    # Dump TimeZone data to files under path `tzdata`
613                          *    __XA_OVERRIDE_TIMEZONE_ID__=America/New_York ANDROID_ROOT=`pwd` ANDROID_DATA=`pwd` mono --debug tzi.exe -o android/tzdata
614                          *
615                          *    # Dump TimeZone rules for specific timezone data (as dumped above)
616                          *    mono tzi.exe --offset=2012-10-24 -i=tzdata/Asia/Amman
617                          */
618                         static void Main (string[] args)
619                         {
620                                 DateTime? offset           = null;
621                                 Func<IAndroidTimeZoneDB> c = () => GetDefaultTimeZoneDB ();
622                                 bool dump_rules            = false;
623                                 Mono.Options.OptionSet p = null;
624                                 p = new Mono.Options.OptionSet () {
625                                         { "i=",
626                                           "TimeZone data {FILE} to parse and dump",
627                                           v => DumpTimeZoneFile (v, offset)
628                                         },
629                                         { "o=",
630                                           "Write TimeZone data files to {PATH}",
631                                           v => TimeZoneInfo.TimeZoneDataExportPath = v
632                                         },
633                                         { "T=", "Create AndroidTzData from {PATH}.", v => {
634                                                         c = () => new AndroidTzData (v);
635                                         } },
636                                         { "Z=", "Create ZoneInfoDB from {DIR}.", v => {
637                                                         c = () => new ZoneInfoDB (v);
638                                         } },
639                                         { "offset=", "Show timezone info offset for DateTime {OFFSET}.", v => {
640                                                 offset = DateTime.Parse (v);
641                                                 Console.WriteLine ("Using DateTime Offset: {0}", offset);
642                                         } },
643                                         { "R|dump-rules",
644                                           "Show timezone info offset for DateTime {OFFSET}.",
645                                           v => dump_rules = v != null },
646                                         { "help", "Show this message and exit", v => {
647                                                         p.WriteOptionDescriptions (Console.Out);
648                                                         Environment.Exit (0);
649                                         } },
650                                 };
651                                 p.Parse (args);
652                                 AndroidTimeZones.db = c ();
653                                 Console.WriteLine ("DB type: {0}", AndroidTimeZones.db.GetType ().FullName);
654                                 foreach (var id in GetAvailableIds ()) {
655                                         Console.Write ("name={0,-40}", id);
656                                         try {
657                                                 TimeZoneInfo zone = _GetTimeZone (id, id);
658                                                 if (zone != null) {
659                                                         Console.Write (" {0,-40}", zone);
660                                                         if (offset.HasValue) {
661                                                                 Console.Write ("From Offset: {0}", zone.GetUtcOffset (offset.Value));
662                                                         }
663                                                         if (dump_rules) {
664                                                                 WriteZoneRules (zone);
665                                                         }
666                                                 }
667                                                 else {
668                                                         Console.Write (" ERROR:null");
669                                                 }
670                                         } catch (Exception e) {
671                                                 Console.WriteLine ();
672                                                 Console.Write ("ERROR: {0}", e);
673                                         }
674                                         Console.WriteLine ();
675                                 }
676                         }
677
678                         static void WriteZoneRules (TimeZoneInfo zone)
679                         {
680                                 var rules = zone.GetAdjustmentRules ();
681                                 for (int i = 0; i < rules.Length; ++i) {
682                                         var rule = rules [i];
683                                         Console.WriteLine ();
684                                         Console.Write ("\tAdjustmentRules[{0,3}]: DaylightDelta={1}; DateStart={2:yyyy-MM}; DateEnd={3:yyyy-MM}; DaylightTransitionStart={4:D2}-{5:D2}T{6}; DaylightTransitionEnd={7:D2}-{8:D2}T{9}",
685                                                 i,
686                                                 rule.DaylightDelta,
687                                                 rule.DateStart, rule.DateEnd,
688                                                 rule.DaylightTransitionStart.Month, rule.DaylightTransitionStart.Day, rule.DaylightTransitionStart.TimeOfDay.TimeOfDay,
689                                                 rule.DaylightTransitionEnd.Month, rule.DaylightTransitionEnd.Day, rule.DaylightTransitionEnd.TimeOfDay.TimeOfDay);
690                                 }
691                         }
692
693                         static void DumpTimeZoneFile (string path, DateTime? time)
694                         {
695                                 var buffer = File.ReadAllBytes (path);
696                                 var zone = ParseTZBuffer (path, buffer, buffer.Length);
697                                 Console.Write ("Rules for: {0}", path);
698                                 WriteZoneRules (zone);
699                                 Console.WriteLine ();
700                                 if (time.HasValue) {
701                                         var offset = zone.GetUtcOffset (time.Value);
702                                         var isDst  = zone.IsDaylightSavingTime (time.Value);
703                                         Console.WriteLine ("\tDate({0}): Offset({1}) IsDST({2})", time.Value, offset, isDst);
704                                 }
705
706                                 if (zone.transitions != null) {
707                                         Console.WriteLine ("Transitions for: {0}", path);
708                                         foreach (var transition in zone.transitions) {
709                                                 Console.WriteLine ("\t Date({0}): {1}", transition.Key, transition.Value);
710                                         }
711                                 }
712                         }
713 #endif
714                 }
715
716 #if SELF_TEST
717                 static string TimeZoneDataExportPath;
718 #endif
719
720                 internal static void DumpTimeZoneDataToFile (string id, byte[] buffer)
721                 {
722 #if SELF_TEST
723                         int p = id.LastIndexOf ('/');
724                         var o = Path.Combine (TimeZoneDataExportPath,
725                                         p >= 0 ? id.Substring (0, p) : id);
726                         if (p >= 0)
727                                 o = Path.Combine (o, id.Substring (p+1));
728                         Directory.CreateDirectory (Path.GetDirectoryName (o));
729                         using (var f = File.OpenWrite (o))
730                                 f.Write (buffer, 0, buffer.Length);
731 #endif
732                 }
733         }
734 }
735
736 #endif // MONODROID
737