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