[sgen] Don't assert in GC.GetTotalMemory.
[mono.git] / mcs / class / corlib / System / TimeZoneInfo.cs
1
2 /*
3  * System.TimeZoneInfo
4  *
5  * Author(s)
6  *      Stephane Delcroix <stephane@delcroix.org>
7  *
8  * Copyright 2011 Xamarin Inc.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining
11  * a copy of this software and associated documentation files (the
12  * "Software"), to deal in the Software without restriction, including
13  * without limitation the rights to use, copy, modify, merge, publish,
14  * distribute, sublicense, and/or sell copies of the Software, and to
15  * permit persons to whom the Software is furnished to do so, subject to
16  * the following conditions:
17  * 
18  * The above copyright notice and this permission notice shall be
19  * included in all copies or substantial portions of the Software.
20  * 
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28  */
29
30 using System;
31 using System.Runtime.CompilerServices;
32 using System.Threading;
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Runtime.Serialization;
36 using System.Text;
37 using System.Globalization;
38
39 #if LIBC || MONODROID
40 using System.IO;
41 using Mono;
42 #endif
43
44 using Microsoft.Win32;
45
46 namespace System
47 {
48 #if MOBILE
49         [TypeForwardedFrom (Consts.AssemblySystem_Core)]
50 #else
51         [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
52 #endif
53         [SerializableAttribute]
54         public
55         sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
56         {
57                 TimeSpan baseUtcOffset;
58                 public TimeSpan BaseUtcOffset {
59                         get { return baseUtcOffset; }
60                 }
61
62                 string daylightDisplayName;
63                 public string DaylightName {
64                         get { 
65                                 return supportsDaylightSavingTime
66                                         ? daylightDisplayName
67                                         : string.Empty;
68                         }
69                 }
70
71                 string displayName;
72                 public string DisplayName {
73                         get { return displayName; }
74                 }
75
76                 string id;
77                 public string Id {
78                         get { return id; }
79                 }
80
81                 static TimeZoneInfo local;
82                 public static TimeZoneInfo Local {
83                         get { 
84                                 var l = local;
85                                 if (l == null) {
86                                         l = CreateLocal ();
87                                         if (l == null)
88                                                 throw new TimeZoneNotFoundException ();
89
90                                         if (Interlocked.CompareExchange (ref local, l, null) != null)
91                                                 l = local;
92                                 }
93
94                                 return l;
95                         }
96                 }
97
98                 /*
99                         TimeZone transitions are stored when there is a change on the base offset.
100                 */
101                 private List<KeyValuePair<DateTime, TimeType>> transitions;
102
103                 static TimeZoneInfo CreateLocal ()
104                 {
105 #if MONODROID
106                         return AndroidTimeZones.Local;
107 #elif MONOTOUCH
108                         using (Stream stream = GetMonoTouchData (null)) {
109                                 return BuildFromStream ("Local", stream);
110                         }
111 #else
112 #if !NET_2_1
113                         if (IsWindows && LocalZoneKey != null) {
114                                 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
115                                 if (name == null)
116                                         name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
117                                 name = TrimSpecial (name);
118                                 if (name != null)
119                                         return TimeZoneInfo.FindSystemTimeZoneById (name);
120                         }
121 #endif
122
123                         var tz = Environment.GetEnvironmentVariable ("TZ");
124                         if (tz != null) {
125                                 if (tz == String.Empty)
126                                         return Utc;
127                                 try {
128                                         return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
129                                 } catch {
130                                         return Utc;
131                                 }
132                         }
133
134                         try {
135                                 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");        
136                         } catch {
137                                 try {
138                                         return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));   
139                                 } catch {
140                                         return null;
141                                 }
142                         }
143 #endif
144                 }
145
146                 string standardDisplayName;
147                 public string StandardName {
148                         get { return standardDisplayName; }
149                 }
150
151                 bool supportsDaylightSavingTime;
152                 public bool SupportsDaylightSavingTime {
153                         get  { return supportsDaylightSavingTime; }
154                 }
155
156                 static TimeZoneInfo utc;
157                 public static TimeZoneInfo Utc {
158                         get {
159                                 if (utc == null)
160                                         utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
161                                 return utc;
162                         }
163                 }
164 #if LIBC
165                 static string timeZoneDirectory;
166                 static string TimeZoneDirectory {
167                         get {
168                                 if (timeZoneDirectory == null)
169                                         timeZoneDirectory = "/usr/share/zoneinfo";
170                                 return timeZoneDirectory;
171                         }
172                         set {
173                                 ClearCachedData ();
174                                 timeZoneDirectory = value;
175                         }
176                 }
177 #endif
178                 private AdjustmentRule [] adjustmentRules;
179
180 #if !NET_2_1
181                 /// <summary>
182                 /// Determine whether windows of not (taken Stephane Delcroix's code)
183                 /// </summary>
184                 private static bool IsWindows
185                 {
186                         get {
187                                 int platform = (int) Environment.OSVersion.Platform;
188                                 return ((platform != 4) && (platform != 6) && (platform != 128));
189                         }
190                 }
191                 
192                 /// <summary>
193                 /// Needed to trim misc garbage in MS registry keys
194                 /// </summary>
195                 private static string TrimSpecial (string str)
196                 {
197                         if (str == null)
198                                 return str;
199                         var Istart = 0;
200                         while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
201                         var Iend = str.Length - 1;
202                         while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
203                         
204                         return str.Substring (Istart, Iend-Istart+1);
205                 }
206                 
207                 static RegistryKey timeZoneKey;
208                 static RegistryKey TimeZoneKey {
209                         get {
210                                 if (timeZoneKey != null)
211                                         return timeZoneKey;
212                                 if (!IsWindows)
213                                         return null;
214                                 
215                                 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
216                                         "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
217                                         false);
218                         }
219                 }
220                 
221                 static RegistryKey localZoneKey;
222                 static RegistryKey LocalZoneKey {
223                         get {
224                                 if (localZoneKey != null)
225                                         return localZoneKey;
226                                 
227                                 if (!IsWindows)
228                                         return null;
229                                 
230                                 return localZoneKey = Registry.LocalMachine.OpenSubKey (
231                                         "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
232                         }
233                 }
234 #endif
235
236                 public static void ClearCachedData ()
237                 {
238                         local = null;
239                         utc = null;
240                         systemTimeZones = null;
241                 }
242
243                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
244                 {
245                         return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
246                 }
247
248                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
249                 {
250                         if (sourceTimeZone == null)
251                                 throw new ArgumentNullException ("sourceTimeZone");
252
253                         if (destinationTimeZone == null)
254                                 throw new ArgumentNullException ("destinationTimeZone");
255                         
256                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
257                                 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
258
259                         if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
260                                 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
261                         
262                         if (sourceTimeZone.IsInvalidTime (dateTime))
263                                 throw new ArgumentException ("dateTime parameter is an invalid time");
264
265                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
266                                 return dateTime;
267
268                         DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
269
270                         if (destinationTimeZone != TimeZoneInfo.Utc) {
271                                 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
272                                 if (dateTime.Kind == DateTimeKind.Unspecified)
273                                         return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
274                         }
275                         
276                         return utc;
277                 }
278
279                 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) 
280                 {
281                         if (destinationTimeZone == null) 
282                                 throw new ArgumentNullException("destinationTimeZone");
283                 
284                         var utcDateTime = dateTimeOffset.UtcDateTime;
285                         AdjustmentRule rule = destinationTimeZone.GetApplicableRule (utcDateTime);
286                 
287                         if (rule != null && destinationTimeZone.IsDaylightSavingTime(utcDateTime)) {
288                                 var offset = destinationTimeZone.BaseUtcOffset + rule.DaylightDelta;
289                                 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + offset, offset);
290                         }
291                         else {
292                                 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + destinationTimeZone.BaseUtcOffset, destinationTimeZone.BaseUtcOffset);
293                         }
294                 }
295
296                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
297                 {
298                         return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
299                 }
300
301                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
302                 {
303                         return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
304                 }
305
306                 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
307                 {
308                         return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
309                 }
310
311                 private DateTime ConvertTimeFromUtc (DateTime dateTime)
312                 {
313                         if (dateTime.Kind == DateTimeKind.Local)
314                                 throw new ArgumentException ("Kind property of dateTime is Local");
315
316                         if (this == TimeZoneInfo.Utc)
317                                 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
318                         
319                         //FIXME: do not rely on DateTime implementation !
320                         if (this == TimeZoneInfo.Local) 
321                         {
322                                 return dateTime.ToLocalTime ();
323                         }
324
325
326                         AdjustmentRule rule = GetApplicableRule (dateTime);
327                         if (rule != null && IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
328                                 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
329                         else
330                                 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
331                 }
332
333                 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
334                 {
335                         if (destinationTimeZone == null)
336                                 throw new ArgumentNullException ("destinationTimeZone");
337
338                         return destinationTimeZone.ConvertTimeFromUtc (dateTime);
339                 }
340
341                 public static DateTime ConvertTimeToUtc (DateTime dateTime)
342                 {
343                         if (dateTime.Kind == DateTimeKind.Utc)
344                                 return dateTime;
345
346                         return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
347                 }
348
349                 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
350                 {
351                         return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
352                 }
353
354                 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
355                 {
356                         return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
357                 }
358
359                 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
360                 {
361                         if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
362                                 if (sourceTimeZone == null)
363                                         throw new ArgumentNullException ("sourceTimeZone");
364
365                                 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
366                                         throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
367
368                                 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
369                                         throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
370
371                                 if (sourceTimeZone.IsInvalidTime (dateTime))
372                                         throw new ArgumentException ("dateTime parameter is an invalid time");
373                         }
374
375                         if (dateTime.Kind == DateTimeKind.Utc)
376                                 return dateTime;
377
378                         if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime)) {
379                                 var ticks = dateTime.Ticks - sourceTimeZone.BaseUtcOffset.Ticks;
380                                 if (ticks < DateTime.MinValue.Ticks)
381                                         ticks = DateTime.MinValue.Ticks;
382                                 else if (ticks > DateTime.MaxValue.Ticks)
383                                         ticks = DateTime.MaxValue.Ticks;
384
385                                 return new DateTime (ticks, DateTimeKind.Utc);
386                         }
387                         
388                                 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
389                                 if (rule != null)
390                                         return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
391                                 else
392                                         return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
393                         
394                 }
395
396                 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
397                 {
398                         bool isDaylightSavings;
399                         return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
400                 }
401
402                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName) 
403                 {
404                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
405                 }
406
407                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
408                 {
409                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
410                 }
411
412                 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
413                 {
414                         return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
415                 }
416
417                 public override bool Equals (object obj)
418                 {
419                         return Equals (obj as TimeZoneInfo);
420                 }
421
422                 public bool Equals (TimeZoneInfo other)
423                 {
424                         if (other == null)
425                                 return false;
426
427                         return other.Id == this.Id && HasSameRules (other);
428                 }
429
430                 public static TimeZoneInfo FindSystemTimeZoneById (string id)
431                 {
432                         //FIXME: this method should check for cached values in systemTimeZones
433                         if (id == null)
434                                 throw new ArgumentNullException ("id");
435 #if !NET_2_1
436                         if (TimeZoneKey != null)
437                         {
438                                 if (id == "Coordinated Universal Time")
439                                         id = "UTC"; //windows xp exception for "StandardName" property
440                                 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
441                                 if (key == null)
442                                         throw new TimeZoneNotFoundException ();
443                                 return FromRegistryKey(id, key);
444                         }
445 #endif
446                         // Local requires special logic that already exists in the Local property (bug #326)
447                         if (id == "Local")
448                                 return Local;
449 #if MONOTOUCH
450                         using (Stream stream = GetMonoTouchData (id)) {
451                                 return BuildFromStream (id, stream);
452                         }
453 #elif MONODROID
454                         var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
455                         if (timeZoneInfo == null)
456                                 throw new TimeZoneNotFoundException ();
457                         return timeZoneInfo;
458 #elif LIBC
459                         string filepath = Path.Combine (TimeZoneDirectory, id);
460                         return FindSystemTimeZoneByFileName (id, filepath);
461 #else
462                         throw new NotImplementedException ();
463 #endif
464                 }
465
466 #if LIBC
467                 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
468                 {
469                         if (!File.Exists (filepath))
470                                 throw new TimeZoneNotFoundException ();
471
472                         using (FileStream stream = File.OpenRead (filepath)) {
473                                 return BuildFromStream (id, stream);
474                         }
475                 }
476 #endif
477 #if LIBC || MONOTOUCH
478                 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
479                 
480                 private static TimeZoneInfo BuildFromStream (string id, Stream stream) 
481                 {
482                         byte [] buffer = new byte [BUFFER_SIZE];
483                         int length = stream.Read (buffer, 0, BUFFER_SIZE);
484                         
485                         if (!ValidTZFile (buffer, length))
486                                 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
487
488                         try {
489                                 return ParseTZBuffer (id, buffer, length);
490                         } catch (Exception e) {
491                                 throw new InvalidTimeZoneException (e.Message);
492                         }
493                 }
494 #endif
495
496 #if !NET_2_1
497                 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
498                 {
499                         byte [] reg_tzi = (byte []) key.GetValue ("TZI");
500
501                         if (reg_tzi == null)
502                                 throw new InvalidTimeZoneException ();
503
504                         int bias = BitConverter.ToInt32 (reg_tzi, 0);
505                         TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
506
507                         string display_name = (string) key.GetValue ("Display");
508                         string standard_name = (string) key.GetValue ("Std");
509                         string daylight_name = (string) key.GetValue ("Dlt");
510
511                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
512
513                         RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
514                         if (dst_key != null) {
515                                 int first_year = (int) dst_key.GetValue ("FirstEntry");
516                                 int last_year = (int) dst_key.GetValue ("LastEntry");
517                                 int year;
518
519                                 for (year=first_year; year<=last_year; year++) {
520                                         byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
521                                         if (dst_tzi != null) {
522                                                 int start_year = year == first_year ? 1 : year;
523                                                 int end_year = year == last_year ? 9999 : year;
524                                                 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
525                                         }
526                                 }
527                         }
528                         else
529                                 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
530
531                         return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
532                 }
533
534                 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
535                 {
536                         //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
537                         int daylight_bias = BitConverter.ToInt32 (buffer, 8);
538
539                         int standard_year = BitConverter.ToInt16 (buffer, 12);
540                         int standard_month = BitConverter.ToInt16 (buffer, 14);
541                         int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
542                         int standard_day = BitConverter.ToInt16 (buffer, 18);
543                         int standard_hour = BitConverter.ToInt16 (buffer, 20);
544                         int standard_minute = BitConverter.ToInt16 (buffer, 22);
545                         int standard_second = BitConverter.ToInt16 (buffer, 24);
546                         int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
547
548                         int daylight_year = BitConverter.ToInt16 (buffer, 28);
549                         int daylight_month = BitConverter.ToInt16 (buffer, 30);
550                         int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
551                         int daylight_day = BitConverter.ToInt16 (buffer, 34);
552                         int daylight_hour = BitConverter.ToInt16 (buffer, 36);
553                         int daylight_minute = BitConverter.ToInt16 (buffer, 38);
554                         int daylight_second = BitConverter.ToInt16 (buffer, 40);
555                         int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
556
557                         if (standard_month == 0 || daylight_month == 0)
558                                 return;
559
560                         DateTime start_date;
561                         DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
562                         TransitionTime start_transition_time;
563
564                         if (daylight_year == 0) {
565                                 start_date = new DateTime (start_year, 1, 1);
566                                 start_transition_time = TransitionTime.CreateFloatingDateRule (
567                                         start_timeofday, daylight_month, daylight_day,
568                                         (DayOfWeek) daylight_dayofweek);
569                         }
570                         else {
571                                 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
572                                         daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
573                                 start_transition_time = TransitionTime.CreateFixedDateRule (
574                                         start_timeofday, daylight_month, daylight_day);
575                         }
576
577                         DateTime end_date;
578                         DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
579                         TransitionTime end_transition_time;
580
581                         if (standard_year == 0) {
582                                 end_date = new DateTime (end_year, 12, 31);
583                                 end_transition_time = TransitionTime.CreateFloatingDateRule (
584                                         end_timeofday, standard_month, standard_day,
585                                         (DayOfWeek) standard_dayofweek);
586                         }
587                         else {
588                                 end_date = new DateTime (standard_year, standard_month, standard_day,
589                                         standard_hour, standard_minute, standard_second, standard_millisecond);
590                                 end_transition_time = TransitionTime.CreateFixedDateRule (
591                                         end_timeofday, standard_month, standard_day);
592                         }
593
594                         TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
595
596                         adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
597                                 start_date, end_date, daylight_delta,
598                                 start_transition_time, end_transition_time));
599                 }
600 #endif
601
602                 public AdjustmentRule [] GetAdjustmentRules ()
603                 {
604                         if (!supportsDaylightSavingTime)
605                                 return new AdjustmentRule [0];
606                         else
607                                 return (AdjustmentRule []) adjustmentRules.Clone ();
608                 }
609
610                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
611                 {
612                         if (!IsAmbiguousTime (dateTime))
613                                 throw new ArgumentException ("dateTime is not an ambiguous time");
614
615                         AdjustmentRule rule = GetApplicableRule (dateTime);
616                         if (rule != null)
617                                 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
618                         else
619                                 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
620                 }
621
622                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
623                 {
624                         if (!IsAmbiguousTime (dateTimeOffset))
625                                 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
626
627                         throw new NotImplementedException ();
628                 }
629
630                 public override int GetHashCode ()
631                 {
632                         int hash_code = Id.GetHashCode ();
633                         foreach (AdjustmentRule rule in GetAdjustmentRules ())
634                                 hash_code ^= rule.GetHashCode ();
635                         return hash_code;
636                 }
637
638                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
639                 {
640                         if (info == null)
641                                 throw new ArgumentNullException ("info");
642                         info.AddValue ("Id", id);
643                         info.AddValue ("DisplayName", displayName);
644                         info.AddValue ("StandardName", standardDisplayName);
645                         info.AddValue ("DaylightName", daylightDisplayName);
646                         info.AddValue ("BaseUtcOffset", baseUtcOffset);
647                         info.AddValue ("AdjustmentRules", adjustmentRules);
648                         info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
649                 }
650
651                 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
652                 private static List<TimeZoneInfo> systemTimeZones;
653                 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
654                 {
655                         if (systemTimeZones == null) {
656                                 systemTimeZones = new List<TimeZoneInfo> ();
657 #if !NET_2_1
658                                 if (TimeZoneKey != null) {
659                                         foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
660                                                 try {
661                                                         systemTimeZones.Add (FindSystemTimeZoneById (id));
662                                                 } catch {}
663                                         }
664
665                                         return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
666                                 }
667 #endif
668 #if MONODROID
669                         foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
670                                 var tz = AndroidTimeZones.GetTimeZone (id, id);
671                                 if (tz != null)
672                                         systemTimeZones.Add (tz);
673                         }
674 #elif MONOTOUCH
675                                 if (systemTimeZones.Count == 0) {
676                                         foreach (string name in GetMonoTouchNames ()) {
677                                                 using (Stream stream = GetMonoTouchData (name, false)) {
678                                                         if (stream == null)
679                                                                 continue;
680                                                         systemTimeZones.Add (BuildFromStream (name, stream));
681                                                 }
682                                         }
683                                 }
684 #elif LIBC
685                                 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
686                                 foreach (string continent in continents) {
687                                         try {
688                                                 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
689                                                         try {
690                                                                 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
691                                                                 systemTimeZones.Add (FindSystemTimeZoneById (id));
692                                                         } catch (ArgumentNullException) {
693                                                         } catch (TimeZoneNotFoundException) {
694                                                         } catch (InvalidTimeZoneException) {
695                                                         } catch (Exception) {
696                                                                 throw;
697                                                         }
698                                                 }
699                                         } catch {}
700                                 }
701 #else
702                                 throw new NotImplementedException ("This method is not implemented for this platform");
703 #endif
704                         }
705                         return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
706                 }
707
708                 public TimeSpan GetUtcOffset (DateTime dateTime)
709                 {
710                         bool isDST;
711                         return GetUtcOffset (dateTime, out isDST);
712                 }
713
714                 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
715                 {
716                         throw new NotImplementedException ();
717                 }
718
719                 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
720                 {
721                         isDST = false;
722
723                         TimeZoneInfo tz = this;
724                         if (dateTime.Kind == DateTimeKind.Utc)
725                                 tz = TimeZoneInfo.Utc;
726
727                         if (dateTime.Kind == DateTimeKind.Local)
728                                 tz = TimeZoneInfo.Local;
729
730                         bool isTzDst;
731                         var tzOffset = GetUtcOffset (dateTime, tz, out isTzDst);
732
733                         if (tz == this) {
734                                 isDST = isTzDst;
735                                 return tzOffset;
736                         }
737
738                         var utcTicks = dateTime.Ticks - tzOffset.Ticks;
739                         if (utcTicks < 0 || utcTicks > DateTime.MaxValue.Ticks)
740                                 return BaseUtcOffset;
741
742                         var utcDateTime = new DateTime (utcTicks, DateTimeKind.Utc);
743
744                         return GetUtcOffset (utcDateTime, this, out isDST);
745                 }
746
747                 private static TimeSpan GetUtcOffset (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
748                 {
749                         if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
750                                 throw new Exception ();
751
752                         isDST = false;
753
754                         if (tz == TimeZoneInfo.Utc)
755                                 return TimeSpan.Zero;
756
757                         TimeSpan offset;
758                         if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
759                                 return offset;
760
761                         if (dateTime.Kind == DateTimeKind.Utc) {
762                                 var utcRule = tz.GetApplicableRule (dateTime);
763                                 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
764                                         isDST = true;
765                                         return tz.BaseUtcOffset + utcRule.DaylightDelta;
766                                 }
767
768                                 return tz.BaseUtcOffset;
769                         }
770
771                         var stdTicks = dateTime.Ticks - tz.BaseUtcOffset.Ticks;
772                         if (stdTicks < 0 || stdTicks > DateTime.MaxValue.Ticks)
773                                 return tz.BaseUtcOffset;
774
775                         var stdUtcDateTime = new DateTime (stdTicks, DateTimeKind.Utc);
776                         var tzRule = tz.GetApplicableRule (stdUtcDateTime);
777
778                         DateTime dstUtcDateTime = DateTime.MinValue;
779                         if (tzRule != null) {
780                                 var dstTicks = stdUtcDateTime.Ticks - tzRule.DaylightDelta.Ticks;
781                                 if (dstTicks < 0 || dstTicks > DateTime.MaxValue.Ticks)
782                                         return tz.BaseUtcOffset;
783
784                                 dstUtcDateTime = new DateTime (dstTicks, DateTimeKind.Utc);
785                         }
786
787                         if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
788                                 isDST = true;
789                                 return tz.BaseUtcOffset + tzRule.DaylightDelta;
790                         }
791
792                         return tz.BaseUtcOffset;
793                 }
794
795                 public bool HasSameRules (TimeZoneInfo other)
796                 {
797                         if (other == null)
798                                 throw new ArgumentNullException ("other");
799
800                         if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
801                                 return false;
802
803                         if (this.adjustmentRules == null)
804                                 return true;
805
806                         if (this.BaseUtcOffset != other.BaseUtcOffset)
807                                 return false;
808
809                         if (this.adjustmentRules.Length != other.adjustmentRules.Length)
810                                 return false;
811
812                         for (int i = 0; i < adjustmentRules.Length; i++) {
813                                 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
814                                         return false;
815                         }
816                         
817                         return true;
818                 }
819
820                 public bool IsAmbiguousTime (DateTime dateTime)
821                 {
822                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
823                                 throw new ArgumentException ("Kind is Local and time is Invalid");
824
825                         if (this == TimeZoneInfo.Utc)
826                                 return false;
827                         
828                         if (dateTime.Kind == DateTimeKind.Utc)
829                                 dateTime = ConvertTimeFromUtc (dateTime);
830
831                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
832                                 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
833
834                         AdjustmentRule rule = GetApplicableRule (dateTime);
835                         if (rule != null) {
836                                 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
837                                 if (dateTime > tpoint - rule.DaylightDelta  && dateTime <= tpoint)
838                                         return true;
839                         }
840                                 
841                         return false;
842                 }
843
844                 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
845                 {
846                         throw new NotImplementedException ();
847                 }
848
849                 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
850                 {
851                         // Check whether we're in the dateTime year's DST period
852                         if (IsInDSTForYear (rule, dateTime, dateTime.Year))
853                                 return true;
854
855                         // We might be in the dateTime previous year's DST period
856                         return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
857                 }
858
859                 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
860                 {
861                         DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
862                         DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
863                         if (dateTime.Kind == DateTimeKind.Utc) {
864                                 DST_start -= BaseUtcOffset;
865                                 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
866                         }
867
868                         return (dateTime >= DST_start && dateTime < DST_end);
869                 }
870                 
871                 public bool IsDaylightSavingTime (DateTime dateTime)
872                 {
873                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
874                                 throw new ArgumentException ("dateTime is invalid and Kind is Local");
875
876                         if (this == TimeZoneInfo.Utc)
877                                 return false;
878                         
879                         if (!SupportsDaylightSavingTime)
880                                 return false;
881
882                         bool isDst;
883                         GetUtcOffset (dateTime, out isDst);
884
885                         return isDst;
886                 }
887
888                 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
889                 {
890                         return IsDaylightSavingTime (dateTime);
891                 }
892
893                 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
894                 {
895                         throw new NotImplementedException ();
896                 }
897
898                 void IDeserializationCallback.OnDeserialization (object sender)
899                 {
900                         try {
901                                         TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
902                                 } catch (ArgumentException ex) {
903                                         throw new SerializationException ("invalid serialization data", ex);
904                                 }
905                 }
906
907                 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
908                 {
909                         if (id == null)
910                                 throw new ArgumentNullException ("id");
911
912                         if (id == String.Empty)
913                                 throw new ArgumentException ("id parameter is an empty string");
914
915                         if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
916                                 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
917
918                         if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
919                                 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
920
921 #if STRICT
922                         if (id.Length > 32)
923                                 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
924 #endif
925
926                         if (adjustmentRules != null && adjustmentRules.Length != 0) {
927                                 AdjustmentRule prev = null;
928                                 foreach (AdjustmentRule current in adjustmentRules) {
929                                         if (current == null)
930                                                 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
931
932                                         if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
933                                                         (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
934                                                 throw new InvalidTimeZoneException ("Sum of baseUtcOffset and DaylightDelta of one or more object in adjustmentRules array is greater than 14 or less than -14 hours;");
935
936                                         if (prev != null && prev.DateStart > current.DateStart)
937                                                 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
938                                         
939                                         if (prev != null && prev.DateEnd > current.DateStart)
940                                                 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
941
942                                         if (prev != null && prev.DateEnd == current.DateStart)
943                                                 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
944
945                                         prev = current;
946                                 }
947                         }
948                 }
949                 
950                 public override string ToString ()
951                 {
952                         return DisplayName;
953                 }
954
955                 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
956                 {
957                         if (info == null)
958                                 throw new ArgumentNullException ("info");
959                         id = (string) info.GetValue ("Id", typeof (string));
960                         displayName = (string) info.GetValue ("DisplayName", typeof (string));
961                         standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
962                         daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
963                         baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
964                         adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
965                         supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
966                 }
967
968                 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
969                 {
970                         if (id == null)
971                                 throw new ArgumentNullException ("id");
972
973                         if (id == String.Empty)
974                                 throw new ArgumentException ("id parameter is an empty string");
975
976                         if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
977                                 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
978
979                         if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
980                                 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
981
982 #if STRICT
983                         if (id.Length > 32)
984                                 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
985 #endif
986
987                         bool supportsDaylightSavingTime = !disableDaylightSavingTime;
988
989                         if (adjustmentRules != null && adjustmentRules.Length != 0) {
990                                 AdjustmentRule prev = null;
991                                 foreach (AdjustmentRule current in adjustmentRules) {
992                                         if (current == null)
993                                                 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
994
995                                         if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
996                                                         (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
997                                                 throw new InvalidTimeZoneException ("Sum of baseUtcOffset and DaylightDelta of one or more object in adjustmentRules array is greater than 14 or less than -14 hours;");
998
999                                         if (prev != null && prev.DateStart > current.DateStart)
1000                                                 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1001                                         
1002                                         if (prev != null && prev.DateEnd > current.DateStart)
1003                                                 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1004
1005                                         if (prev != null && prev.DateEnd == current.DateStart)
1006                                                 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1007
1008                                         prev = current;
1009                                 }
1010                         } else {
1011                                 supportsDaylightSavingTime = false;
1012                         }
1013                         
1014                         this.id = id;
1015                         this.baseUtcOffset = baseUtcOffset;
1016                         this.displayName = displayName ?? id;
1017                         this.standardDisplayName = standardDisplayName ?? id;
1018                         this.daylightDisplayName = daylightDisplayName;
1019                         this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1020                         this.adjustmentRules = adjustmentRules;
1021                 }
1022
1023                 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1024                 {
1025                         //Applicable rules are in standard time
1026                         DateTime date = dateTime;
1027
1028                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
1029                                 date = date.ToUniversalTime () + BaseUtcOffset;
1030                         else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
1031                                 date = date + BaseUtcOffset;
1032
1033                         // get the date component of the datetime
1034                         date = date.Date;
1035
1036                         if (adjustmentRules != null) {
1037                                 foreach (AdjustmentRule rule in adjustmentRules) {
1038                                         if (rule.DateStart > date)
1039                                                 return null;
1040                                         if (rule.DateEnd < date)
1041                                                 continue;
1042                                         return rule;
1043                                 }
1044                         }
1045                         return null;
1046                 }
1047
1048                 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1049                 {
1050                         offset = BaseUtcOffset;
1051                         isDst = false;
1052
1053                         if (transitions == null)
1054                                 return false;
1055
1056                         //Transitions are in UTC
1057                         DateTime date = dateTime;
1058
1059                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1060                                 var ticks = date.ToUniversalTime ().Ticks + BaseUtcOffset.Ticks;
1061                                 if (ticks < DateTime.MinValue.Ticks || ticks > DateTime.MaxValue.Ticks)
1062                                         return false;
1063
1064                                 date = new DateTime (ticks, DateTimeKind.Utc);
1065                         }
1066
1067                         if (dateTime.Kind != DateTimeKind.Utc) {
1068                                 var ticks = date.Ticks - BaseUtcOffset.Ticks;
1069                                 if (ticks < DateTime.MinValue.Ticks || ticks > DateTime.MaxValue.Ticks)
1070                                         return false;
1071
1072                                 date = new DateTime (ticks, DateTimeKind.Utc);
1073                         }
1074
1075                         for (var i =  transitions.Count - 1; i >= 0; i--) {
1076                                 var pair = transitions [i];
1077                                 DateTime ttime = pair.Key;
1078                                 TimeType ttype = pair.Value;
1079
1080                                 if (ttime > date)
1081                                         continue;
1082
1083                                 offset =  new TimeSpan (0, 0, ttype.Offset);
1084                                 isDst = ttype.IsDst;
1085
1086                                 return true;
1087                         }
1088
1089                         return false;
1090                 }
1091
1092                 private static DateTime TransitionPoint (TransitionTime transition, int year)
1093                 {
1094                         if (transition.IsFixedDateRule)
1095                                 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1096
1097                         DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1098                         int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1099                         if (day >  DateTime.DaysInMonth (year, transition.Month))
1100                                 day -= 7;
1101                         if (day < 1)
1102                                 day += 7;
1103                         return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1104                 }
1105
1106                 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1107                 {
1108                         AdjustmentRule prev = null;
1109                         foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1110                                 if (prev != null && prev.DateEnd > current.DateStart) {
1111                                         adjustmentRules.Remove (current);
1112                                 }
1113                                 prev = current;
1114                         }
1115                         return adjustmentRules;
1116                 }
1117
1118 #if LIBC || MONODROID
1119                 private static bool ValidTZFile (byte [] buffer, int length)
1120                 {
1121                         StringBuilder magic = new StringBuilder ();
1122
1123                         for (int i = 0; i < 4; i++)
1124                                 magic.Append ((char)buffer [i]);
1125                         
1126                         if (magic.ToString () != "TZif")
1127                                 return false;
1128
1129                         if (length >= BUFFER_SIZE)
1130                                 return false;
1131
1132                         return true;
1133                 }
1134
1135                 static int SwapInt32 (int i)
1136                 {
1137                         return (((i >> 24) & 0xff)
1138                                 | ((i >> 8) & 0xff00)
1139                                 | ((i << 8) & 0xff0000)
1140                                 | ((i << 24)));
1141                 }
1142
1143                 static int ReadBigEndianInt32 (byte [] buffer, int start)
1144                 {
1145                         int i = BitConverter.ToInt32 (buffer, start);
1146                         if (!BitConverter.IsLittleEndian)
1147                                 return i;
1148
1149                         return SwapInt32 (i);
1150                 }
1151
1152                 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1153                 {
1154                         //Reading the header. 4 bytes for magic, 16 are reserved
1155                         int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1156                         int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1157                         int leapcnt = ReadBigEndianInt32 (buffer, 28);
1158                         int timecnt = ReadBigEndianInt32 (buffer, 32);
1159                         int typecnt = ReadBigEndianInt32 (buffer, 36);
1160                         int charcnt = ReadBigEndianInt32 (buffer, 40);
1161
1162                         if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1163                                 throw new InvalidTimeZoneException ();
1164
1165                         Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1166                         Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1167                         List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1168
1169                         if (time_types.Count == 0)
1170                                 throw new InvalidTimeZoneException ();
1171
1172                         if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1173                                 throw new InvalidTimeZoneException ();
1174
1175                         TimeSpan baseUtcOffset = new TimeSpan (0);
1176                         TimeSpan dstDelta = new TimeSpan (0);
1177                         string standardDisplayName = null;
1178                         string daylightDisplayName = null;
1179                         bool dst_observed = false;
1180                         DateTime dst_start = DateTime.MinValue;
1181                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1182                         bool storeTransition = false;
1183
1184                         for (int i = 0; i < transitions.Count; i++) {
1185                                 var pair = transitions [i];
1186                                 DateTime ttime = pair.Key;
1187                                 TimeType ttype = pair.Value;
1188                                 if (!ttype.IsDst) {
1189                                         if (standardDisplayName != ttype.Name)
1190                                                 standardDisplayName = ttype.Name;
1191                                         if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1192                                                 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1193                                                 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1194                                                         storeTransition = true;
1195                                                 adjustmentRules = new List<AdjustmentRule> ();
1196                                                 dst_observed = false;
1197                                         }
1198                                         if (dst_observed) {
1199                                                 //FIXME: check additional fields for this:
1200                                                 //most of the transitions are expressed in GMT 
1201                                                 dst_start += baseUtcOffset;
1202                                                 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1203
1204                                                 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1205                                                 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1206                                                         dst_end -= new TimeSpan (24, 0, 0);
1207
1208                                                 DateTime dateStart, dateEnd;
1209                                                 if (dst_start.Month < 7)
1210                                                         dateStart = new DateTime (dst_start.Year, 1, 1);
1211                                                 else
1212                                                         dateStart = new DateTime (dst_start.Year, 7, 1);
1213
1214                                                 if (dst_end.Month >= 7)
1215                                                         dateEnd = new DateTime (dst_end.Year, 12, 31);
1216                                                 else
1217                                                         dateEnd = new DateTime (dst_end.Year, 6, 30);
1218
1219                                                 
1220                                                 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1221                                                 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1222                                                 if  (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1223                                                         adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1224                                         }
1225                                         dst_observed = false;
1226                                 } else {
1227                                         if (daylightDisplayName != ttype.Name)
1228                                                 daylightDisplayName = ttype.Name;
1229                                         if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
1230                                                 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
1231
1232                                         dst_start = ttime;
1233                                         dst_observed = true;
1234                                 }
1235                         }
1236
1237                         TimeZoneInfo tz;
1238                         if (adjustmentRules.Count == 0 && !storeTransition) {
1239                                 TimeType t = (TimeType)time_types [0];
1240                                 if (standardDisplayName == null) {
1241                                         standardDisplayName = t.Name;
1242                                         baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1243                                 }
1244                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1245                         } else {
1246                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1247                         }
1248
1249                         if (storeTransition)
1250                                 tz.transitions = transitions;
1251
1252                         return tz;
1253                 }
1254
1255                 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1256                 {
1257                         var abbrevs = new Dictionary<int, string> ();
1258                         int abbrev_index = 0;
1259                         var sb = new StringBuilder ();
1260                         for (int i = 0; i < count; i++) {
1261                                 char c = (char) buffer [index + i];
1262                                 if (c != '\0')
1263                                         sb.Append (c);
1264                                 else {
1265                                         abbrevs.Add (abbrev_index, sb.ToString ());
1266                                         //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1267                                         for (int j = 1; j < sb.Length; j++)
1268                                                 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1269                                         abbrev_index = i + 1;
1270                                         sb = new StringBuilder ();
1271                                 }
1272                         }
1273                         return abbrevs;
1274                 }
1275
1276                 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1277                 {
1278                         var types = new Dictionary<int, TimeType> (count);
1279                         for (int i = 0; i < count; i++) {
1280                                 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1281                                 byte is_dst = buffer [index + 6 * i + 4];
1282                                 byte abbrev = buffer [index + 6 * i + 5];
1283                                 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1284                         }
1285                         return types;
1286                 }
1287
1288                 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1289                 {
1290                         var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1291                         for (int i = 0; i < count; i++) {
1292                                 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1293                                 DateTime ttime = DateTimeFromUnixTime (unixtime);
1294                                 byte ttype = buffer [index + 4 * count + i];
1295                                 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1296                         }
1297                         return list;
1298                 }
1299
1300                 static DateTime DateTimeFromUnixTime (long unix_time)
1301                 {
1302                         DateTime date_time = new DateTime (1970, 1, 1);
1303                         return date_time.AddSeconds (unix_time);
1304                 }
1305
1306 #region reference sources
1307         // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1308         internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1309         {
1310             bool dst;
1311             return Local.GetUtcOffset (dateTime, out dst);
1312         }
1313
1314         internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1315         {
1316             bool dst;
1317             return GetUtcOffset (dateTime, out dst);
1318         }
1319
1320         // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks
1321         private static DateTime s_maxDateOnly = new DateTime(9999, 12, 31);
1322         private static DateTime s_minDateOnly = new DateTime(1, 1, 2);
1323
1324         static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1325         {
1326             isDaylightSavings = false;
1327             isAmbiguousLocalDst = false;
1328             TimeSpan baseOffset = zone.BaseUtcOffset;
1329             Int32 year;
1330             AdjustmentRule rule;
1331
1332             if (time > s_maxDateOnly) {
1333                 rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue);
1334                 year = 9999;
1335             }
1336             else if (time < s_minDateOnly) {
1337                 rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue);
1338                 year = 1;
1339             }
1340             else {
1341                 DateTime targetTime = time + baseOffset;
1342                 year = time.Year;
1343                 rule = zone.GetAdjustmentRuleForTime(targetTime);
1344             }
1345
1346             if (rule != null) {
1347                 isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone.baseUtcOffset, rule, out isAmbiguousLocalDst);
1348                 baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* */);
1349             }
1350
1351             return baseOffset;
1352         }
1353
1354         // assumes dateTime is in the current time zone's time
1355         private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime) {
1356             if (adjustmentRules == null || adjustmentRules.Length == 0) {
1357                 return null;
1358             }
1359
1360 #if WINXP_AND_WIN2K3_SUPPORT
1361             // On pre-Vista versions of Windows if you run "cmd /c date" or "cmd /c time" to update the system time
1362             // the operating system doesn't pick up the correct time zone adjustment rule (it stays on the currently loaded rule).
1363             // We need to use the OS API data in this scenario instead of the loaded adjustment rules from the registry for
1364             // consistency.  Otherwise DateTime.Now might not match the time displayed in the system tray.              
1365             if (!Environment.IsWindowsVistaOrAbove && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local) {
1366                 return s_cachedData.GetOneYearLocalFromLocal(dateTime.Year).rule;
1367             }
1368 #endif
1369             // Only check the whole-date portion of the dateTime -
1370             // This is because the AdjustmentRule DateStart & DateEnd are stored as
1371             // Date-only values {4/2/2006 - 10/28/2006} but actually represent the
1372             // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999}
1373             DateTime date = dateTime.Date;
1374
1375             for (int i = 0; i < adjustmentRules.Length; i++) {
1376                 if (adjustmentRules[i].DateStart <= date && adjustmentRules[i].DateEnd >= date) {
1377                     return adjustmentRules[i];
1378                 }
1379             }
1380
1381             return null;
1382         }
1383
1384         //
1385         // GetIsDaylightSavingsFromUtc -
1386         //
1387         // Helper function that checks if a given dateTime is in Daylight Saving Time (DST)
1388         // This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone
1389         //
1390         static private Boolean GetIsDaylightSavingsFromUtc(DateTime time, Int32 Year, TimeSpan utc, AdjustmentRule rule, out Boolean isAmbiguousLocalDst) {
1391             isAmbiguousLocalDst = false;
1392
1393             if (rule == null) {
1394                 return false;
1395             }
1396
1397             // Get the daylight changes for the year of the specified time.
1398             TimeSpan offset = utc; /* */
1399             DaylightTime daylightTime = GetDaylightTime(Year, rule);
1400
1401             // The start and end times represent the range of universal times that are in DST for that year.                
1402             // Within that there is an ambiguous hour, usually right at the end, but at the beginning in
1403             // the unusual case of a negative daylight savings delta.
1404             DateTime startTime = daylightTime.Start - offset;
1405             DateTime endTime = daylightTime.End - offset - rule.DaylightDelta; /* */
1406             DateTime ambiguousStart;
1407             DateTime ambiguousEnd;
1408             if (daylightTime.Delta.Ticks > 0) {
1409                 ambiguousStart = endTime - daylightTime.Delta;
1410                 ambiguousEnd = endTime;
1411             } else {
1412                 ambiguousStart = startTime;
1413                 ambiguousEnd = startTime - daylightTime.Delta;
1414             }
1415
1416             Boolean isDst = CheckIsDst(startTime, time, endTime);
1417
1418             // See if the resulting local time becomes ambiguous. This must be captured here or the
1419             // DateTime will not be able to round-trip back to UTC accurately.
1420             if (isDst) {
1421                 isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
1422
1423                 if (!isAmbiguousLocalDst && ambiguousStart.Year != ambiguousEnd.Year) {
1424                     // there exists an extreme corner case where the start or end period is on a year boundary and
1425                     // because of this the comparison above might have been performed for a year-early or a year-later
1426                     // than it should have been.
1427                     DateTime ambiguousStartModified;
1428                     DateTime ambiguousEndModified;
1429                     try {
1430                         ambiguousStartModified = ambiguousStart.AddYears(1);
1431                         ambiguousEndModified   = ambiguousEnd.AddYears(1);
1432                         isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); 
1433                     }
1434                     catch (ArgumentOutOfRangeException) {}
1435
1436                     if (!isAmbiguousLocalDst) {
1437                         try {
1438                             ambiguousStartModified = ambiguousStart.AddYears(-1);
1439                             ambiguousEndModified   = ambiguousEnd.AddYears(-1);
1440                             isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
1441                         }
1442                         catch (ArgumentOutOfRangeException) {}
1443                     }
1444
1445                 }
1446             }
1447
1448             return isDst;
1449         }
1450
1451
1452         static private Boolean CheckIsDst(DateTime startTime, DateTime time, DateTime endTime) {
1453             Boolean isDst;
1454
1455             int startTimeYear = startTime.Year;
1456             int endTimeYear = endTime.Year;
1457
1458             if (startTimeYear != endTimeYear) {
1459                 endTime = endTime.AddYears(startTimeYear - endTimeYear);
1460             }
1461
1462             int timeYear = time.Year;
1463
1464             if (startTimeYear != timeYear) {
1465                 time = time.AddYears(startTimeYear - timeYear);
1466             }
1467
1468             if (startTime > endTime) {
1469                 // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
1470                 // Note, the summer in the southern hemisphere begins late in the year.
1471                 isDst = (time < endTime || time >= startTime);
1472             }
1473             else {
1474                 // In northern hemisphere, the daylight saving time starts in the middle of the year.
1475                 isDst = (time >= startTime && time < endTime);
1476             }
1477             return isDst;
1478         }
1479
1480         //
1481         // GetDaylightTime -
1482         //
1483         // Helper function that returns a DaylightTime from a year and AdjustmentRule
1484         //
1485         static private DaylightTime GetDaylightTime(Int32 year, AdjustmentRule rule) {
1486             TimeSpan delta = rule.DaylightDelta;
1487             DateTime startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
1488             DateTime endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
1489             return new DaylightTime(startTime, endTime, delta);
1490         }
1491
1492         //
1493         // TransitionTimeToDateTime -
1494         //
1495         // Helper function that converts a year and TransitionTime into a DateTime
1496         //
1497         static private DateTime TransitionTimeToDateTime(Int32 year, TransitionTime transitionTime) {
1498             DateTime value;
1499             DateTime timeOfDay = transitionTime.TimeOfDay;
1500
1501             if (transitionTime.IsFixedDateRule) {
1502                 // create a DateTime from the passed in year and the properties on the transitionTime
1503
1504                 // if the day is out of range for the month then use the last day of the month
1505                 Int32 day = DateTime.DaysInMonth(year, transitionTime.Month);
1506
1507                 value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day, 
1508                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1509             }
1510             else {
1511                 if (transitionTime.Week <= 4) {
1512                     //
1513                     // Get the (transitionTime.Week)th Sunday.
1514                     //
1515                     value = new DateTime(year, transitionTime.Month, 1,
1516                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1517
1518                     int dayOfWeek = (int)value.DayOfWeek;
1519                     int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
1520                     if (delta < 0) {
1521                         delta += 7;
1522                     }
1523                     delta += 7 * (transitionTime.Week - 1);
1524
1525                     if (delta > 0) {
1526                         value = value.AddDays(delta);
1527                     }
1528                 }
1529                 else {
1530                     //
1531                     // If TransitionWeek is greater than 4, we will get the last week.
1532                     //
1533                     Int32 daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
1534                     value = new DateTime(year, transitionTime.Month, daysInMonth,
1535                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1536
1537                     // This is the day of week for the last day of the month.
1538                     int dayOfWeek = (int)value.DayOfWeek;
1539                     int delta = dayOfWeek - (int)transitionTime.DayOfWeek;
1540                     if (delta < 0) {
1541                         delta += 7;
1542                     }
1543
1544                     if (delta > 0) {
1545                         value = value.AddDays(-delta);
1546                     }
1547                 }
1548             }
1549             return value;
1550         }
1551
1552         //
1553         // IsInvalidTime -
1554         //
1555         // returns true when dateTime falls into a "hole in time".
1556         //
1557         public Boolean IsInvalidTime(DateTime dateTime) {
1558             Boolean isInvalid = false;
1559           
1560             if ( (dateTime.Kind == DateTimeKind.Unspecified)
1561             ||   (dateTime.Kind == DateTimeKind.Local && this == Local) ) {
1562
1563                 // only check Unspecified and (Local when this TimeZoneInfo instance is Local)
1564                 AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime);
1565
1566
1567                 if (rule != null) {
1568                     DaylightTime daylightTime = GetDaylightTime(dateTime.Year, rule);
1569                     isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime);
1570                 }
1571                 else {
1572                     isInvalid = false;
1573                 }
1574             }
1575
1576             return isInvalid;
1577         }
1578
1579         //
1580         // GetIsInvalidTime -
1581         //
1582         // Helper function that checks if a given DateTime is in an invalid time ("time hole")
1583         // A "time hole" occurs at a DST transition point when time jumps forward;
1584         // For example, in Pacific Standard Time on Sunday, April 2, 2006 time jumps from
1585         // 1:59:59.9999999 to 3AM.  The time range 2AM to 2:59:59.9999999AM is the "time hole".
1586         // A "time hole" is not limited to only occurring at the start of DST, and may occur at
1587         // the end of DST as well.
1588         //
1589         static private Boolean GetIsInvalidTime(DateTime time, AdjustmentRule rule, DaylightTime daylightTime) {
1590             Boolean isInvalid = false;
1591             if (rule == null || rule.DaylightDelta == TimeSpan.Zero) {
1592                 return isInvalid;
1593             }
1594
1595             DateTime startInvalidTime;
1596             DateTime endInvalidTime;
1597
1598             // if at DST start we transition forward in time then there is an ambiguous time range at the DST end
1599             if (rule.DaylightDelta < TimeSpan.Zero) {
1600                 startInvalidTime = daylightTime.End;
1601                 endInvalidTime = daylightTime.End - rule.DaylightDelta; /* */
1602             }
1603             else {
1604                 startInvalidTime = daylightTime.Start;
1605                 endInvalidTime = daylightTime.Start + rule.DaylightDelta; /* */
1606             }
1607
1608             isInvalid = (time >= startInvalidTime && time < endInvalidTime);
1609
1610             if (!isInvalid && startInvalidTime.Year != endInvalidTime.Year) {
1611                 // there exists an extreme corner case where the start or end period is on a year boundary and
1612                 // because of this the comparison above might have been performed for a year-early or a year-later
1613                 // than it should have been.
1614                 DateTime startModifiedInvalidTime;
1615                 DateTime endModifiedInvalidTime;
1616                 try {
1617                     startModifiedInvalidTime = startInvalidTime.AddYears(1);
1618                     endModifiedInvalidTime   = endInvalidTime.AddYears(1);
1619                     isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
1620                 }
1621                 catch (ArgumentOutOfRangeException) {}
1622
1623                 if (!isInvalid) {
1624                     try {
1625                         startModifiedInvalidTime = startInvalidTime.AddYears(-1);
1626                         endModifiedInvalidTime  = endInvalidTime.AddYears(-1);
1627                         isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
1628                     }
1629                     catch (ArgumentOutOfRangeException) {}
1630                 }
1631             }
1632             return isInvalid;
1633         } 
1634 #endregion
1635         }
1636
1637         struct TimeType {
1638                 public readonly int Offset;
1639                 public readonly bool IsDst;
1640                 public string Name;
1641
1642                 public TimeType (int offset, bool is_dst, string abbrev)
1643                 {
1644                         this.Offset = offset;
1645                         this.IsDst = is_dst;
1646                         this.Name = abbrev;
1647                 }
1648
1649                 public override string ToString ()
1650                 {
1651                         return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
1652                 }
1653 #else
1654         }
1655 #endif
1656         }
1657 }