Merge pull request #1816 from esdrubal/getdaylight
[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 using System.IO;
39
40 using Microsoft.Win32;
41
42 namespace System
43 {
44         partial class TimeZoneInfo
45         {
46                 TimeSpan baseUtcOffset;
47                 public TimeSpan BaseUtcOffset {
48                         get { return baseUtcOffset; }
49                 }
50
51                 string daylightDisplayName;
52                 public string DaylightName {
53                         get { 
54                                 return supportsDaylightSavingTime
55                                         ? daylightDisplayName
56                                         : string.Empty;
57                         }
58                 }
59
60                 string displayName;
61                 public string DisplayName {
62                         get { return displayName; }
63                 }
64
65                 string id;
66                 public string Id {
67                         get { return id; }
68                 }
69
70                 static TimeZoneInfo local;
71                 public static TimeZoneInfo Local {
72                         get { 
73                                 var l = local;
74                                 if (l == null) {
75                                         l = CreateLocal ();
76                                         if (l == null)
77                                                 throw new TimeZoneNotFoundException ();
78
79                                         if (Interlocked.CompareExchange (ref local, l, null) != null)
80                                                 l = local;
81                                 }
82
83                                 return l;
84                         }
85                 }
86
87                 /*
88                         TimeZone transitions are stored when there is a change on the base offset.
89                 */
90                 private List<KeyValuePair<DateTime, TimeType>> transitions;
91
92 #if !MOBILE
93                 static TimeZoneInfo CreateLocal ()
94                 {
95                         if (IsWindows && LocalZoneKey != null) {
96                                 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
97                                 if (name == null)
98                                         name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
99                                 name = TrimSpecial (name);
100                                 if (name != null)
101                                         return TimeZoneInfo.FindSystemTimeZoneById (name);
102                         }
103
104                         var tz = Environment.GetEnvironmentVariable ("TZ");
105                         if (tz != null) {
106                                 if (tz == String.Empty)
107                                         return Utc;
108                                 try {
109                                         return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
110                                 } catch {
111                                         return Utc;
112                                 }
113                         }
114
115                         try {
116                                 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");        
117                         } catch {
118                                 try {
119                                         return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));   
120                                 } catch {
121                                         return null;
122                                 }
123                         }
124                 }
125
126                 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
127                 {
128 #if LIBC
129                         string filepath = Path.Combine (TimeZoneDirectory, id);
130                         return FindSystemTimeZoneByFileName (id, filepath);
131 #else
132                         throw new NotImplementedException ();
133 #endif
134                 }
135
136                 static void GetSystemTimeZones (List<TimeZoneInfo> systemTimeZones)
137                 {
138                         if (TimeZoneKey != null) {
139                                 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
140                                         try {
141                                                 systemTimeZones.Add (FindSystemTimeZoneById (id));
142                                         } catch {}
143                                 }
144
145                                 return;
146                         }
147
148 #if LIBC
149                         string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
150                         foreach (string continent in continents) {
151                                 try {
152                                         foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
153                                                 try {
154                                                         string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
155                                                         systemTimeZones.Add (FindSystemTimeZoneById (id));
156                                                 } catch (ArgumentNullException) {
157                                                 } catch (TimeZoneNotFoundException) {
158                                                 } catch (InvalidTimeZoneException) {
159                                                 } catch (Exception) {
160                                                         throw;
161                                                 }
162                                         }
163                                 } catch {}
164                         }
165 #else
166                         throw new NotImplementedException ("This method is not implemented for this platform");
167 #endif
168                 }
169 #endif
170
171                 string standardDisplayName;
172                 public string StandardName {
173                         get { return standardDisplayName; }
174                 }
175
176                 bool supportsDaylightSavingTime;
177                 public bool SupportsDaylightSavingTime {
178                         get  { return supportsDaylightSavingTime; }
179                 }
180
181                 static TimeZoneInfo utc;
182                 public static TimeZoneInfo Utc {
183                         get {
184                                 if (utc == null)
185                                         utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
186                                 return utc;
187                         }
188                 }
189 #if LIBC
190                 static string timeZoneDirectory;
191                 static string TimeZoneDirectory {
192                         get {
193                                 if (timeZoneDirectory == null)
194                                         timeZoneDirectory = "/usr/share/zoneinfo";
195                                 return timeZoneDirectory;
196                         }
197                         set {
198                                 ClearCachedData ();
199                                 timeZoneDirectory = value;
200                         }
201                 }
202 #endif
203                 private AdjustmentRule [] adjustmentRules;
204
205 #if !NET_2_1
206                 /// <summary>
207                 /// Determine whether windows of not (taken Stephane Delcroix's code)
208                 /// </summary>
209                 private static bool IsWindows
210                 {
211                         get {
212                                 int platform = (int) Environment.OSVersion.Platform;
213                                 return ((platform != 4) && (platform != 6) && (platform != 128));
214                         }
215                 }
216                 
217                 /// <summary>
218                 /// Needed to trim misc garbage in MS registry keys
219                 /// </summary>
220                 private static string TrimSpecial (string str)
221                 {
222                         if (str == null)
223                                 return str;
224                         var Istart = 0;
225                         while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
226                         var Iend = str.Length - 1;
227                         while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
228                         
229                         return str.Substring (Istart, Iend-Istart+1);
230                 }
231                 
232                 static RegistryKey timeZoneKey;
233                 static RegistryKey TimeZoneKey {
234                         get {
235                                 if (timeZoneKey != null)
236                                         return timeZoneKey;
237                                 if (!IsWindows)
238                                         return null;
239                                 
240                                 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
241                                         "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
242                                         false);
243                         }
244                 }
245                 
246                 static RegistryKey localZoneKey;
247                 static RegistryKey LocalZoneKey {
248                         get {
249                                 if (localZoneKey != null)
250                                         return localZoneKey;
251                                 
252                                 if (!IsWindows)
253                                         return null;
254                                 
255                                 return localZoneKey = Registry.LocalMachine.OpenSubKey (
256                                         "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
257                         }
258                 }
259 #endif
260
261                 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
262                 {
263                         var resultTicks = date.Ticks + ticks;
264                         if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
265                                 result =  default (DateTime);
266                                 return false;
267                         }
268
269                         result = new DateTime (resultTicks, kind);
270                         return true;
271                 }
272
273                 public static void ClearCachedData ()
274                 {
275                         local = null;
276                         utc = null;
277                         systemTimeZones = null;
278                 }
279
280                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
281                 {
282                         return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
283                 }
284
285                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
286                 {
287                         if (sourceTimeZone == null)
288                                 throw new ArgumentNullException ("sourceTimeZone");
289
290                         if (destinationTimeZone == null)
291                                 throw new ArgumentNullException ("destinationTimeZone");
292                         
293                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
294                                 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
295
296                         if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
297                                 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
298                         
299                         if (sourceTimeZone.IsInvalidTime (dateTime))
300                                 throw new ArgumentException ("dateTime parameter is an invalid time");
301
302                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
303                                 return dateTime;
304
305                         DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
306
307                         if (destinationTimeZone != TimeZoneInfo.Utc) {
308                                 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
309                                 if (dateTime.Kind == DateTimeKind.Unspecified)
310                                         return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
311                         }
312                         
313                         return utc;
314                 }
315
316                 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) 
317                 {
318                         if (destinationTimeZone == null) 
319                                 throw new ArgumentNullException("destinationTimeZone");
320
321                         var utcDateTime = dateTimeOffset.UtcDateTime;
322
323                         bool isDst;
324                         var utcOffset =  destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
325
326                         return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
327                 }
328
329                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
330                 {
331                         return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
332                 }
333
334                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
335                 {
336                         return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
337                 }
338
339                 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
340                 {
341                         return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
342                 }
343
344                 private DateTime ConvertTimeFromUtc (DateTime dateTime)
345                 {
346                         if (dateTime.Kind == DateTimeKind.Local)
347                                 throw new ArgumentException ("Kind property of dateTime is Local");
348
349                         if (this == TimeZoneInfo.Utc)
350                                 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
351
352                         var utcOffset = GetUtcOffset (dateTime);
353
354                         var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
355
356                         DateTime result;
357                         if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
358                                 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
359
360                         return result;
361                 }
362
363                 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
364                 {
365                         if (destinationTimeZone == null)
366                                 throw new ArgumentNullException ("destinationTimeZone");
367
368                         return destinationTimeZone.ConvertTimeFromUtc (dateTime);
369                 }
370
371                 public static DateTime ConvertTimeToUtc (DateTime dateTime)
372                 {
373                         if (dateTime.Kind == DateTimeKind.Utc)
374                                 return dateTime;
375
376                         return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
377                 }
378
379                 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
380                 {
381                         return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
382                 }
383
384                 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
385                 {
386                         return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
387                 }
388
389                 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
390                 {
391                         if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
392                                 if (sourceTimeZone == null)
393                                         throw new ArgumentNullException ("sourceTimeZone");
394
395                                 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
396                                         throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
397
398                                 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
399                                         throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
400
401                                 if (sourceTimeZone.IsInvalidTime (dateTime))
402                                         throw new ArgumentException ("dateTime parameter is an invalid time");
403                         }
404
405                         if (dateTime.Kind == DateTimeKind.Utc)
406                                 return dateTime;
407
408                         bool isDst;
409                         var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
410
411                         DateTime utcDateTime;
412                         if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
413                                 return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
414
415                         return utcDateTime;
416                 }
417
418                 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
419                 {
420                         bool isDaylightSavings;
421                         return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
422                 }
423
424                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName) 
425                 {
426                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
427                 }
428
429                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
430                 {
431                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
432                 }
433
434                 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
435                 {
436                         return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
437                 }
438
439                 public override bool Equals (object obj)
440                 {
441                         return Equals (obj as TimeZoneInfo);
442                 }
443
444                 public bool Equals (TimeZoneInfo other)
445                 {
446                         if (other == null)
447                                 return false;
448
449                         return other.Id == this.Id && HasSameRules (other);
450                 }
451
452                 public static TimeZoneInfo FindSystemTimeZoneById (string id)
453                 {
454                         //FIXME: this method should check for cached values in systemTimeZones
455                         if (id == null)
456                                 throw new ArgumentNullException ("id");
457 #if !NET_2_1
458                         if (TimeZoneKey != null)
459                         {
460                                 if (id == "Coordinated Universal Time")
461                                         id = "UTC"; //windows xp exception for "StandardName" property
462                                 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
463                                 if (key == null)
464                                         throw new TimeZoneNotFoundException ();
465                                 return FromRegistryKey(id, key);
466                         }
467 #endif
468                         // Local requires special logic that already exists in the Local property (bug #326)
469                         if (id == "Local")
470                                 return Local;
471
472                         return FindSystemTimeZoneByIdCore (id);
473                 }
474
475 #if LIBC
476                 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
477                 {
478                         if (!File.Exists (filepath))
479                                 throw new TimeZoneNotFoundException ();
480
481                         using (FileStream stream = File.OpenRead (filepath)) {
482                                 return BuildFromStream (id, stream);
483                         }
484                 }
485 #endif
486
487 #if !NET_2_1
488                 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
489                 {
490                         byte [] reg_tzi = (byte []) key.GetValue ("TZI");
491
492                         if (reg_tzi == null)
493                                 throw new InvalidTimeZoneException ();
494
495                         int bias = BitConverter.ToInt32 (reg_tzi, 0);
496                         TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
497
498                         string display_name = (string) key.GetValue ("Display");
499                         string standard_name = (string) key.GetValue ("Std");
500                         string daylight_name = (string) key.GetValue ("Dlt");
501
502                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
503
504                         RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
505                         if (dst_key != null) {
506                                 int first_year = (int) dst_key.GetValue ("FirstEntry");
507                                 int last_year = (int) dst_key.GetValue ("LastEntry");
508                                 int year;
509
510                                 for (year=first_year; year<=last_year; year++) {
511                                         byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
512                                         if (dst_tzi != null) {
513                                                 int start_year = year == first_year ? 1 : year;
514                                                 int end_year = year == last_year ? 9999 : year;
515                                                 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
516                                         }
517                                 }
518                         }
519                         else
520                                 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
521
522                         return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
523                 }
524
525                 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
526                 {
527                         //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
528                         int daylight_bias = BitConverter.ToInt32 (buffer, 8);
529
530                         int standard_year = BitConverter.ToInt16 (buffer, 12);
531                         int standard_month = BitConverter.ToInt16 (buffer, 14);
532                         int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
533                         int standard_day = BitConverter.ToInt16 (buffer, 18);
534                         int standard_hour = BitConverter.ToInt16 (buffer, 20);
535                         int standard_minute = BitConverter.ToInt16 (buffer, 22);
536                         int standard_second = BitConverter.ToInt16 (buffer, 24);
537                         int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
538
539                         int daylight_year = BitConverter.ToInt16 (buffer, 28);
540                         int daylight_month = BitConverter.ToInt16 (buffer, 30);
541                         int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
542                         int daylight_day = BitConverter.ToInt16 (buffer, 34);
543                         int daylight_hour = BitConverter.ToInt16 (buffer, 36);
544                         int daylight_minute = BitConverter.ToInt16 (buffer, 38);
545                         int daylight_second = BitConverter.ToInt16 (buffer, 40);
546                         int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
547
548                         if (standard_month == 0 || daylight_month == 0)
549                                 return;
550
551                         DateTime start_date;
552                         DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
553                         TransitionTime start_transition_time;
554
555                         if (daylight_year == 0) {
556                                 start_date = new DateTime (start_year, 1, 1);
557                                 start_transition_time = TransitionTime.CreateFloatingDateRule (
558                                         start_timeofday, daylight_month, daylight_day,
559                                         (DayOfWeek) daylight_dayofweek);
560                         }
561                         else {
562                                 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
563                                         daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
564                                 start_transition_time = TransitionTime.CreateFixedDateRule (
565                                         start_timeofday, daylight_month, daylight_day);
566                         }
567
568                         DateTime end_date;
569                         DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
570                         TransitionTime end_transition_time;
571
572                         if (standard_year == 0) {
573                                 end_date = new DateTime (end_year, 12, 31);
574                                 end_transition_time = TransitionTime.CreateFloatingDateRule (
575                                         end_timeofday, standard_month, standard_day,
576                                         (DayOfWeek) standard_dayofweek);
577                         }
578                         else {
579                                 end_date = new DateTime (standard_year, standard_month, standard_day,
580                                         standard_hour, standard_minute, standard_second, standard_millisecond);
581                                 end_transition_time = TransitionTime.CreateFixedDateRule (
582                                         end_timeofday, standard_month, standard_day);
583                         }
584
585                         TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
586
587                         adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
588                                 start_date, end_date, daylight_delta,
589                                 start_transition_time, end_transition_time));
590                 }
591 #endif
592
593                 public AdjustmentRule [] GetAdjustmentRules ()
594                 {
595                         if (!supportsDaylightSavingTime || adjustmentRules == null)
596                                 return new AdjustmentRule [0];
597                         else
598                                 return (AdjustmentRule []) adjustmentRules.Clone ();
599                 }
600
601                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
602                 {
603                         if (!IsAmbiguousTime (dateTime))
604                                 throw new ArgumentException ("dateTime is not an ambiguous time");
605
606                         AdjustmentRule rule = GetApplicableRule (dateTime);
607                         if (rule != null)
608                                 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
609                         else
610                                 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
611                 }
612
613                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
614                 {
615                         if (!IsAmbiguousTime (dateTimeOffset))
616                                 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
617
618                         throw new NotImplementedException ();
619                 }
620
621                 public override int GetHashCode ()
622                 {
623                         int hash_code = Id.GetHashCode ();
624                         foreach (AdjustmentRule rule in GetAdjustmentRules ())
625                                 hash_code ^= rule.GetHashCode ();
626                         return hash_code;
627                 }
628
629                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
630                 {
631                         if (info == null)
632                                 throw new ArgumentNullException ("info");
633                         info.AddValue ("Id", id);
634                         info.AddValue ("DisplayName", displayName);
635                         info.AddValue ("StandardName", standardDisplayName);
636                         info.AddValue ("DaylightName", daylightDisplayName);
637                         info.AddValue ("BaseUtcOffset", baseUtcOffset);
638                         info.AddValue ("AdjustmentRules", adjustmentRules);
639                         info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
640                 }
641
642                 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
643
644                 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
645                 {
646                         if (systemTimeZones == null) {
647                                 var tz = new List<TimeZoneInfo> ();
648                                 GetSystemTimeZones (tz);
649                                 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
650                         }
651
652                         return systemTimeZones;
653                 }
654
655                 public TimeSpan GetUtcOffset (DateTime dateTime)
656                 {
657                         bool isDST;
658                         return GetUtcOffset (dateTime, out isDST);
659                 }
660
661                 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
662                 {
663                         throw new NotImplementedException ();
664                 }
665
666                 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
667                 {
668                         isDST = false;
669
670                         TimeZoneInfo tz = this;
671                         if (dateTime.Kind == DateTimeKind.Utc)
672                                 tz = TimeZoneInfo.Utc;
673
674                         if (dateTime.Kind == DateTimeKind.Local)
675                                 tz = TimeZoneInfo.Local;
676
677                         bool isTzDst;
678                         var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
679
680                         if (tz == this) {
681                                 isDST = isTzDst;
682                                 return tzOffset;
683                         }
684
685                         DateTime utcDateTime;
686                         if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
687                                 return BaseUtcOffset;
688
689                         return GetUtcOffsetHelper (utcDateTime, this, out isDST);
690                 }
691
692                 // This is an helper method used by the method above, do not use this on its own.
693                 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
694                 {
695                         if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
696                                 throw new Exception ();
697
698                         isDST = false;
699
700                         if (tz == TimeZoneInfo.Utc)
701                                 return TimeSpan.Zero;
702
703                         TimeSpan offset;
704                         if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
705                                 return offset;
706
707                         if (dateTime.Kind == DateTimeKind.Utc) {
708                                 var utcRule = tz.GetApplicableRule (dateTime);
709                                 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
710                                         isDST = true;
711                                         return tz.BaseUtcOffset + utcRule.DaylightDelta;
712                                 }
713
714                                 return tz.BaseUtcOffset;
715                         }
716
717                         DateTime stdUtcDateTime;
718                         if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
719                                 return tz.BaseUtcOffset;
720
721                         var tzRule = tz.GetApplicableRule (stdUtcDateTime);
722
723                         DateTime dstUtcDateTime = DateTime.MinValue;
724                         if (tzRule != null) {
725                                 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
726                                         return tz.BaseUtcOffset;
727                         }
728
729                         if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
730                                 isDST = true;
731                                 return tz.BaseUtcOffset + tzRule.DaylightDelta;
732                         }
733
734                         return tz.BaseUtcOffset;
735                 }
736
737                 public bool HasSameRules (TimeZoneInfo other)
738                 {
739                         if (other == null)
740                                 throw new ArgumentNullException ("other");
741
742                         if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
743                                 return false;
744
745                         if (this.adjustmentRules == null)
746                                 return true;
747
748                         if (this.BaseUtcOffset != other.BaseUtcOffset)
749                                 return false;
750
751                         if (this.adjustmentRules.Length != other.adjustmentRules.Length)
752                                 return false;
753
754                         for (int i = 0; i < adjustmentRules.Length; i++) {
755                                 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
756                                         return false;
757                         }
758                         
759                         return true;
760                 }
761
762                 public bool IsAmbiguousTime (DateTime dateTime)
763                 {
764                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
765                                 throw new ArgumentException ("Kind is Local and time is Invalid");
766
767                         if (this == TimeZoneInfo.Utc)
768                                 return false;
769                         
770                         if (dateTime.Kind == DateTimeKind.Utc)
771                                 dateTime = ConvertTimeFromUtc (dateTime);
772
773                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
774                                 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
775
776                         AdjustmentRule rule = GetApplicableRule (dateTime);
777                         if (rule != null) {
778                                 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
779                                 if (dateTime > tpoint - rule.DaylightDelta  && dateTime <= tpoint)
780                                         return true;
781                         }
782                                 
783                         return false;
784                 }
785
786                 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
787                 {
788                         throw new NotImplementedException ();
789                 }
790
791                 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
792                 {
793                         // Check whether we're in the dateTime year's DST period
794                         if (IsInDSTForYear (rule, dateTime, dateTime.Year))
795                                 return true;
796
797                         // We might be in the dateTime previous year's DST period
798                         return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
799                 }
800
801                 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
802                 {
803                         DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
804                         DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
805                         if (dateTime.Kind == DateTimeKind.Utc) {
806                                 DST_start -= BaseUtcOffset;
807                                 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
808                         }
809
810                         return (dateTime >= DST_start && dateTime < DST_end);
811                 }
812                 
813                 public bool IsDaylightSavingTime (DateTime dateTime)
814                 {
815                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
816                                 throw new ArgumentException ("dateTime is invalid and Kind is Local");
817
818                         if (this == TimeZoneInfo.Utc)
819                                 return false;
820                         
821                         if (!SupportsDaylightSavingTime)
822                                 return false;
823
824                         bool isDst;
825                         GetUtcOffset (dateTime, out isDst);
826
827                         return isDst;
828                 }
829
830                 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
831                 {
832                         return IsDaylightSavingTime (dateTime);
833                 }
834
835                 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
836                 {
837                         throw new NotImplementedException ();
838                 }
839
840                 internal DaylightTime GetDaylightChanges (int year)
841                 {
842                         DateTime start = DateTime.MinValue, end = DateTime.MinValue;
843                         TimeSpan delta = new TimeSpan ();
844
845                         if (transitions != null) {
846                                 end = DateTime.MaxValue;
847                                 for (var i =  transitions.Count - 1; i >= 0; i--) {
848                                         var pair = transitions [i];
849                                         DateTime ttime = pair.Key;
850                                         TimeType ttype = pair.Value;
851
852                                         if (ttime.Year > year)
853                                                 continue;
854                                         if (ttime.Year < year)
855                                                 break;
856
857                                         if (ttype.IsDst) {
858                                                 // DaylightTime.Delta is relative to the current BaseUtcOffset.
859                                                 delta =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
860                                                 start = ttime;
861                                         } else {
862                                                 end = ttime;
863                                         }
864                                 }
865
866                                 // DaylightTime.Start is relative to the Standard time.
867                                 if (start != DateTime.MinValue)
868                                         start += BaseUtcOffset;
869
870                                 // DaylightTime.End is relative to the DST time.
871                                 if (end != DateTime.MinValue)
872                                         end += BaseUtcOffset + delta;
873                         } else {
874                                 AdjustmentRule first = null, last = null;
875
876                                 foreach (var rule in GetAdjustmentRules ()) {
877                                         if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
878                                                 continue;
879                                         if (rule.DateStart.Year == year)
880                                                 first = rule;
881                                         if (rule.DateEnd.Year == year)
882                                                 last = rule;
883                                 }
884
885                                 if (first == null || last == null)
886                                         return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
887
888                                 start = TransitionPoint (first.DaylightTransitionStart, year);
889                                 end = TransitionPoint (last.DaylightTransitionEnd, year);
890                                 delta = first.DaylightDelta;
891                         }
892
893                         if (start == DateTime.MinValue || end == DateTime.MinValue)
894                                 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
895
896                         return new DaylightTime (start, end, delta);
897                 }
898
899                 public bool IsInvalidTime (DateTime dateTime)
900                 {
901                         if (dateTime.Kind == DateTimeKind.Utc)
902                                 return false;
903                         if (dateTime.Kind == DateTimeKind.Local && this != Local)
904                                 return false;
905
906                         AdjustmentRule rule = GetApplicableRule (dateTime);
907                         if (rule != null) {
908                                 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
909                                 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
910                                         return true;
911                         }
912
913                         return false;
914                 }
915
916                 void IDeserializationCallback.OnDeserialization (object sender)
917                 {
918                         try {
919                                         TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
920                                 } catch (ArgumentException ex) {
921                                         throw new SerializationException ("invalid serialization data", ex);
922                                 }
923                 }
924
925                 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
926                 {
927                         if (id == null)
928                                 throw new ArgumentNullException ("id");
929
930                         if (id == String.Empty)
931                                 throw new ArgumentException ("id parameter is an empty string");
932
933                         if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
934                                 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
935
936                         if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
937                                 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
938
939 #if STRICT
940                         if (id.Length > 32)
941                                 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
942 #endif
943
944                         if (adjustmentRules != null && adjustmentRules.Length != 0) {
945                                 AdjustmentRule prev = null;
946                                 foreach (AdjustmentRule current in adjustmentRules) {
947                                         if (current == null)
948                                                 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
949
950                                         if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
951                                                         (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
952                                                 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;");
953
954                                         if (prev != null && prev.DateStart > current.DateStart)
955                                                 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
956                                         
957                                         if (prev != null && prev.DateEnd > current.DateStart)
958                                                 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
959
960                                         if (prev != null && prev.DateEnd == current.DateStart)
961                                                 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
962
963                                         prev = current;
964                                 }
965                         }
966                 }
967                 
968                 public override string ToString ()
969                 {
970                         return DisplayName;
971                 }
972
973                 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
974                 {
975                         if (info == null)
976                                 throw new ArgumentNullException ("info");
977                         id = (string) info.GetValue ("Id", typeof (string));
978                         displayName = (string) info.GetValue ("DisplayName", typeof (string));
979                         standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
980                         daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
981                         baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
982                         adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
983                         supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
984                 }
985
986                 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
987                 {
988                         if (id == null)
989                                 throw new ArgumentNullException ("id");
990
991                         if (id == String.Empty)
992                                 throw new ArgumentException ("id parameter is an empty string");
993
994                         if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
995                                 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
996
997                         if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
998                                 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
999
1000 #if STRICT
1001                         if (id.Length > 32)
1002                                 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1003 #endif
1004
1005                         bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1006
1007                         if (adjustmentRules != null && adjustmentRules.Length != 0) {
1008                                 AdjustmentRule prev = null;
1009                                 foreach (AdjustmentRule current in adjustmentRules) {
1010                                         if (current == null)
1011                                                 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1012
1013                                         if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1014                                                         (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1015                                                 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;");
1016
1017                                         if (prev != null && prev.DateStart > current.DateStart)
1018                                                 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1019                                         
1020                                         if (prev != null && prev.DateEnd > current.DateStart)
1021                                                 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1022
1023                                         if (prev != null && prev.DateEnd == current.DateStart)
1024                                                 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1025
1026                                         prev = current;
1027                                 }
1028                         } else {
1029                                 supportsDaylightSavingTime = false;
1030                         }
1031                         
1032                         this.id = id;
1033                         this.baseUtcOffset = baseUtcOffset;
1034                         this.displayName = displayName ?? id;
1035                         this.standardDisplayName = standardDisplayName ?? id;
1036                         this.daylightDisplayName = daylightDisplayName;
1037                         this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1038                         this.adjustmentRules = adjustmentRules;
1039                 }
1040
1041                 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1042                 {
1043                         //Applicable rules are in standard time
1044                         DateTime date = dateTime;
1045
1046                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1047                                 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1048                                         return null;
1049                         } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1050                                 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1051                                         return null;
1052                         }
1053
1054                         // get the date component of the datetime
1055                         date = date.Date;
1056
1057                         if (adjustmentRules != null) {
1058                                 foreach (AdjustmentRule rule in adjustmentRules) {
1059                                         if (rule.DateStart > date)
1060                                                 return null;
1061                                         if (rule.DateEnd < date)
1062                                                 continue;
1063                                         return rule;
1064                                 }
1065                         }
1066                         return null;
1067                 }
1068
1069                 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1070                 {
1071                         offset = BaseUtcOffset;
1072                         isDst = false;
1073
1074                         if (transitions == null)
1075                                 return false;
1076
1077                         //Transitions are in UTC
1078                         DateTime date = dateTime;
1079
1080                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1081                                 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1082                                         return false;
1083                         }
1084
1085                         if (dateTime.Kind != DateTimeKind.Utc) {
1086                                 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1087                                         return false;
1088                         }
1089
1090                         for (var i =  transitions.Count - 1; i >= 0; i--) {
1091                                 var pair = transitions [i];
1092                                 DateTime ttime = pair.Key;
1093                                 TimeType ttype = pair.Value;
1094
1095                                 if (ttime > date)
1096                                         continue;
1097
1098                                 offset =  new TimeSpan (0, 0, ttype.Offset);
1099                                 isDst = ttype.IsDst;
1100
1101                                 return true;
1102                         }
1103
1104                         return false;
1105                 }
1106
1107                 private static DateTime TransitionPoint (TransitionTime transition, int year)
1108                 {
1109                         if (transition.IsFixedDateRule)
1110                                 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1111
1112                         DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1113                         int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1114                         if (day >  DateTime.DaysInMonth (year, transition.Month))
1115                                 day -= 7;
1116                         if (day < 1)
1117                                 day += 7;
1118                         return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1119                 }
1120
1121                 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1122                 {
1123                         AdjustmentRule prev = null;
1124                         foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1125                                 if (prev != null && prev.DateEnd > current.DateStart) {
1126                                         adjustmentRules.Remove (current);
1127                                 }
1128                                 prev = current;
1129                         }
1130                         return adjustmentRules;
1131                 }
1132
1133 #if LIBC || MONOTOUCH
1134                 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1135                 
1136                 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1137                 {
1138                         byte [] buffer = new byte [BUFFER_SIZE];
1139                         int length = stream.Read (buffer, 0, BUFFER_SIZE);
1140                         
1141                         if (!ValidTZFile (buffer, length))
1142                                 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1143
1144                         try {
1145                                 return ParseTZBuffer (id, buffer, length);
1146                         } catch (Exception e) {
1147                                 throw new InvalidTimeZoneException (e.Message);
1148                         }
1149                 }
1150
1151                 private static bool ValidTZFile (byte [] buffer, int length)
1152                 {
1153                         StringBuilder magic = new StringBuilder ();
1154
1155                         for (int i = 0; i < 4; i++)
1156                                 magic.Append ((char)buffer [i]);
1157                         
1158                         if (magic.ToString () != "TZif")
1159                                 return false;
1160
1161                         if (length >= BUFFER_SIZE)
1162                                 return false;
1163
1164                         return true;
1165                 }
1166
1167                 static int SwapInt32 (int i)
1168                 {
1169                         return (((i >> 24) & 0xff)
1170                                 | ((i >> 8) & 0xff00)
1171                                 | ((i << 8) & 0xff0000)
1172                                 | ((i << 24)));
1173                 }
1174
1175                 static int ReadBigEndianInt32 (byte [] buffer, int start)
1176                 {
1177                         int i = BitConverter.ToInt32 (buffer, start);
1178                         if (!BitConverter.IsLittleEndian)
1179                                 return i;
1180
1181                         return SwapInt32 (i);
1182                 }
1183
1184                 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1185                 {
1186                         //Reading the header. 4 bytes for magic, 16 are reserved
1187                         int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1188                         int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1189                         int leapcnt = ReadBigEndianInt32 (buffer, 28);
1190                         int timecnt = ReadBigEndianInt32 (buffer, 32);
1191                         int typecnt = ReadBigEndianInt32 (buffer, 36);
1192                         int charcnt = ReadBigEndianInt32 (buffer, 40);
1193
1194                         if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1195                                 throw new InvalidTimeZoneException ();
1196
1197                         Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1198                         Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1199                         List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1200
1201                         if (time_types.Count == 0)
1202                                 throw new InvalidTimeZoneException ();
1203
1204                         if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1205                                 throw new InvalidTimeZoneException ();
1206
1207                         TimeSpan baseUtcOffset = new TimeSpan (0);
1208                         TimeSpan dstDelta = new TimeSpan (0);
1209                         string standardDisplayName = null;
1210                         string daylightDisplayName = null;
1211                         bool dst_observed = false;
1212                         DateTime dst_start = DateTime.MinValue;
1213                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1214                         bool storeTransition = false;
1215
1216                         for (int i = 0; i < transitions.Count; i++) {
1217                                 var pair = transitions [i];
1218                                 DateTime ttime = pair.Key;
1219                                 TimeType ttype = pair.Value;
1220                                 if (!ttype.IsDst) {
1221                                         if (standardDisplayName != ttype.Name)
1222                                                 standardDisplayName = ttype.Name;
1223                                         if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1224                                                 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1225                                                 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1226                                                         storeTransition = true;
1227                                                 adjustmentRules = new List<AdjustmentRule> ();
1228                                                 dst_observed = false;
1229                                         }
1230                                         if (dst_observed) {
1231                                                 //FIXME: check additional fields for this:
1232                                                 //most of the transitions are expressed in GMT 
1233                                                 dst_start += baseUtcOffset;
1234                                                 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1235
1236                                                 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1237                                                 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1238                                                         dst_end -= new TimeSpan (24, 0, 0);
1239
1240                                                 /*
1241                                                  * AdjustmentRule specifies a DST period that starts and ends within a year.
1242                                                  * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1243                                                  * Thus we fallback to the transitions.
1244                                                  */
1245                                                 if (dst_start.AddYears (1) < dst_end)
1246                                                         storeTransition = true;
1247
1248                                                 DateTime dateStart, dateEnd;
1249                                                 if (dst_start.Month < 7)
1250                                                         dateStart = new DateTime (dst_start.Year, 1, 1);
1251                                                 else
1252                                                         dateStart = new DateTime (dst_start.Year, 7, 1);
1253
1254                                                 if (dst_end.Month >= 7)
1255                                                         dateEnd = new DateTime (dst_end.Year, 12, 31);
1256                                                 else
1257                                                         dateEnd = new DateTime (dst_end.Year, 6, 30);
1258
1259                                                 
1260                                                 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1261                                                 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1262                                                 if  (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1263                                                         adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1264                                         }
1265                                         dst_observed = false;
1266                                 } else {
1267                                         if (daylightDisplayName != ttype.Name)
1268                                                 daylightDisplayName = ttype.Name;
1269                                         if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1270                                                 // Round to nearest minute, since it's not possible to create an adjustment rule
1271                                                 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1272                                                 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1273                                                 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1274                                                 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1275                                                         dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1276                                         }
1277
1278                                         dst_start = ttime;
1279                                         dst_observed = true;
1280                                 }
1281                         }
1282
1283                         TimeZoneInfo tz;
1284                         if (adjustmentRules.Count == 0 && !storeTransition) {
1285                                 TimeType t = (TimeType)time_types [0];
1286                                 if (standardDisplayName == null) {
1287                                         standardDisplayName = t.Name;
1288                                         baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1289                                 }
1290                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1291                         } else {
1292                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1293                         }
1294
1295                         if (storeTransition && transitions.Count > 0) {
1296                                 tz.transitions = transitions;
1297                                 tz.supportsDaylightSavingTime = true;
1298                         }
1299
1300                         return tz;
1301                 }
1302
1303                 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1304                 {
1305                         var abbrevs = new Dictionary<int, string> ();
1306                         int abbrev_index = 0;
1307                         var sb = new StringBuilder ();
1308                         for (int i = 0; i < count; i++) {
1309                                 char c = (char) buffer [index + i];
1310                                 if (c != '\0')
1311                                         sb.Append (c);
1312                                 else {
1313                                         abbrevs.Add (abbrev_index, sb.ToString ());
1314                                         //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1315                                         for (int j = 1; j < sb.Length; j++)
1316                                                 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1317                                         abbrev_index = i + 1;
1318                                         sb = new StringBuilder ();
1319                                 }
1320                         }
1321                         return abbrevs;
1322                 }
1323
1324                 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1325                 {
1326                         var types = new Dictionary<int, TimeType> (count);
1327                         for (int i = 0; i < count; i++) {
1328                                 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1329                                 byte is_dst = buffer [index + 6 * i + 4];
1330                                 byte abbrev = buffer [index + 6 * i + 5];
1331                                 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1332                         }
1333                         return types;
1334                 }
1335
1336                 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1337                 {
1338                         var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1339                         for (int i = 0; i < count; i++) {
1340                                 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1341                                 DateTime ttime = DateTimeFromUnixTime (unixtime);
1342                                 byte ttype = buffer [index + 4 * count + i];
1343                                 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1344                         }
1345                         return list;
1346                 }
1347
1348                 static DateTime DateTimeFromUnixTime (long unix_time)
1349                 {
1350                         DateTime date_time = new DateTime (1970, 1, 1);
1351                         return date_time.AddSeconds (unix_time);
1352                 }
1353
1354 #region reference sources
1355                 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1356                 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1357                 {
1358                         bool dst;
1359                         return Local.GetUtcOffset (dateTime, out dst);
1360                 }
1361
1362                 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1363                 {
1364                         bool dst;
1365                         return GetUtcOffset (dateTime, out dst);
1366                 }
1367
1368                 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1369                 {
1370                         isDaylightSavings = false;
1371                         isAmbiguousLocalDst = false;
1372                         TimeSpan baseOffset = zone.BaseUtcOffset;
1373
1374                         if (zone.IsAmbiguousTime (time)) {
1375                                 isAmbiguousLocalDst = true;
1376                                 return baseOffset;
1377                         }
1378
1379                         return zone.GetUtcOffset (time, out isDaylightSavings);
1380                 }
1381 #endregion
1382         }
1383
1384         struct TimeType {
1385                 public readonly int Offset;
1386                 public readonly bool IsDst;
1387                 public string Name;
1388
1389                 public TimeType (int offset, bool is_dst, string abbrev)
1390                 {
1391                         this.Offset = offset;
1392                         this.IsDst = is_dst;
1393                         this.Name = abbrev;
1394                 }
1395
1396                 public override string ToString ()
1397                 {
1398                         return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
1399                 }
1400 #else
1401         }
1402 #endif
1403         }
1404 }