TimeZoneInfo.Android.cs 21 KB

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