[corlib] TimeZoneInfo.ConvertTimeToUtc maximum datetime handling
[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                         //Transitions are always 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 always in standard time
1051                         DateTime date = dateTime;
1052
1053                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
1054                                 date = date.ToUniversalTime () + BaseUtcOffset;
1055
1056                         if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
1057                                 date = date + BaseUtcOffset;
1058
1059                         for (var i =  transitions.Count - 1; i >= 0; i--) {
1060                                 var pair = transitions [i];
1061                                 DateTime ttime = pair.Key;
1062                                 TimeType ttype = pair.Value;
1063
1064                                 if (ttime > date)
1065                                         continue;
1066
1067                                 offset =  new TimeSpan (0, 0, ttype.Offset);
1068                                 isDst = ttype.IsDst;
1069
1070                                 return true;
1071                         }
1072
1073                         return false;
1074                 }
1075
1076                 private static DateTime TransitionPoint (TransitionTime transition, int year)
1077                 {
1078                         if (transition.IsFixedDateRule)
1079                                 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1080
1081                         DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1082                         int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1083                         if (day >  DateTime.DaysInMonth (year, transition.Month))
1084                                 day -= 7;
1085                         if (day < 1)
1086                                 day += 7;
1087                         return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1088                 }
1089
1090                 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1091                 {
1092                         AdjustmentRule prev = null;
1093                         foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1094                                 if (prev != null && prev.DateEnd > current.DateStart) {
1095                                         adjustmentRules.Remove (current);
1096                                 }
1097                                 prev = current;
1098                         }
1099                         return adjustmentRules;
1100                 }
1101
1102 #if LIBC || MONODROID
1103                 private static bool ValidTZFile (byte [] buffer, int length)
1104                 {
1105                         StringBuilder magic = new StringBuilder ();
1106
1107                         for (int i = 0; i < 4; i++)
1108                                 magic.Append ((char)buffer [i]);
1109                         
1110                         if (magic.ToString () != "TZif")
1111                                 return false;
1112
1113                         if (length >= BUFFER_SIZE)
1114                                 return false;
1115
1116                         return true;
1117                 }
1118
1119                 static int SwapInt32 (int i)
1120                 {
1121                         return (((i >> 24) & 0xff)
1122                                 | ((i >> 8) & 0xff00)
1123                                 | ((i << 8) & 0xff0000)
1124                                 | ((i << 24)));
1125                 }
1126
1127                 static int ReadBigEndianInt32 (byte [] buffer, int start)
1128                 {
1129                         int i = BitConverter.ToInt32 (buffer, start);
1130                         if (!BitConverter.IsLittleEndian)
1131                                 return i;
1132
1133                         return SwapInt32 (i);
1134                 }
1135
1136                 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1137                 {
1138                         //Reading the header. 4 bytes for magic, 16 are reserved
1139                         int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1140                         int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1141                         int leapcnt = ReadBigEndianInt32 (buffer, 28);
1142                         int timecnt = ReadBigEndianInt32 (buffer, 32);
1143                         int typecnt = ReadBigEndianInt32 (buffer, 36);
1144                         int charcnt = ReadBigEndianInt32 (buffer, 40);
1145
1146                         if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1147                                 throw new InvalidTimeZoneException ();
1148
1149                         Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1150                         Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1151                         List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1152
1153                         if (time_types.Count == 0)
1154                                 throw new InvalidTimeZoneException ();
1155
1156                         if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1157                                 throw new InvalidTimeZoneException ();
1158
1159                         TimeSpan baseUtcOffset = new TimeSpan (0);
1160                         TimeSpan dstDelta = new TimeSpan (0);
1161                         string standardDisplayName = null;
1162                         string daylightDisplayName = null;
1163                         bool dst_observed = false;
1164                         DateTime dst_start = DateTime.MinValue;
1165                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1166                         bool storeTransition = false;
1167
1168                         for (int i = 0; i < transitions.Count; i++) {
1169                                 var pair = transitions [i];
1170                                 DateTime ttime = pair.Key;
1171                                 TimeType ttype = pair.Value;
1172                                 if (!ttype.IsDst) {
1173                                         if (standardDisplayName != ttype.Name)
1174                                                 standardDisplayName = ttype.Name;
1175                                         if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1176                                                 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1177                                                 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1178                                                         storeTransition = true;
1179                                                 adjustmentRules = new List<AdjustmentRule> ();
1180                                                 dst_observed = false;
1181                                         }
1182                                         if (dst_observed) {
1183                                                 //FIXME: check additional fields for this:
1184                                                 //most of the transitions are expressed in GMT 
1185                                                 dst_start += baseUtcOffset;
1186                                                 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1187
1188                                                 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1189                                                 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1190                                                         dst_end -= new TimeSpan (24, 0, 0);
1191
1192                                                 DateTime dateStart, dateEnd;
1193                                                 if (dst_start.Month < 7)
1194                                                         dateStart = new DateTime (dst_start.Year, 1, 1);
1195                                                 else
1196                                                         dateStart = new DateTime (dst_start.Year, 7, 1);
1197
1198                                                 if (dst_end.Month >= 7)
1199                                                         dateEnd = new DateTime (dst_end.Year, 12, 31);
1200                                                 else
1201                                                         dateEnd = new DateTime (dst_end.Year, 6, 30);
1202
1203                                                 
1204                                                 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1205                                                 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1206                                                 if  (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1207                                                         adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1208                                         }
1209                                         dst_observed = false;
1210                                 } else {
1211                                         if (daylightDisplayName != ttype.Name)
1212                                                 daylightDisplayName = ttype.Name;
1213                                         if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
1214                                                 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
1215
1216                                         dst_start = ttime;
1217                                         dst_observed = true;
1218                                 }
1219                         }
1220
1221                         TimeZoneInfo tz;
1222                         if (adjustmentRules.Count == 0 && !storeTransition) {
1223                                 TimeType t = (TimeType)time_types [0];
1224                                 if (standardDisplayName == null) {
1225                                         standardDisplayName = t.Name;
1226                                         baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1227                                 }
1228                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1229                         } else {
1230                                 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1231                         }
1232
1233                         if (storeTransition)
1234                                 tz.transitions = transitions;
1235
1236                         return tz;
1237                 }
1238
1239                 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1240                 {
1241                         var abbrevs = new Dictionary<int, string> ();
1242                         int abbrev_index = 0;
1243                         var sb = new StringBuilder ();
1244                         for (int i = 0; i < count; i++) {
1245                                 char c = (char) buffer [index + i];
1246                                 if (c != '\0')
1247                                         sb.Append (c);
1248                                 else {
1249                                         abbrevs.Add (abbrev_index, sb.ToString ());
1250                                         //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1251                                         for (int j = 1; j < sb.Length; j++)
1252                                                 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1253                                         abbrev_index = i + 1;
1254                                         sb = new StringBuilder ();
1255                                 }
1256                         }
1257                         return abbrevs;
1258                 }
1259
1260                 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1261                 {
1262                         var types = new Dictionary<int, TimeType> (count);
1263                         for (int i = 0; i < count; i++) {
1264                                 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1265                                 byte is_dst = buffer [index + 6 * i + 4];
1266                                 byte abbrev = buffer [index + 6 * i + 5];
1267                                 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1268                         }
1269                         return types;
1270                 }
1271
1272                 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1273                 {
1274                         var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1275                         for (int i = 0; i < count; i++) {
1276                                 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1277                                 DateTime ttime = DateTimeFromUnixTime (unixtime);
1278                                 byte ttype = buffer [index + 4 * count + i];
1279                                 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1280                         }
1281                         return list;
1282                 }
1283
1284                 static DateTime DateTimeFromUnixTime (long unix_time)
1285                 {
1286                         DateTime date_time = new DateTime (1970, 1, 1);
1287                         return date_time.AddSeconds (unix_time);
1288                 }
1289
1290 #region reference sources
1291         // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1292         internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1293         {
1294             bool dst;
1295             return Local.GetUtcOffset (dateTime, out dst);
1296         }
1297
1298         internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1299         {
1300             bool dst;
1301             return GetUtcOffset (dateTime, out dst);
1302         }
1303
1304         // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks
1305         private static DateTime s_maxDateOnly = new DateTime(9999, 12, 31);
1306         private static DateTime s_minDateOnly = new DateTime(1, 1, 2);
1307
1308         static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1309         {
1310             isDaylightSavings = false;
1311             isAmbiguousLocalDst = false;
1312             TimeSpan baseOffset = zone.BaseUtcOffset;
1313             Int32 year;
1314             AdjustmentRule rule;
1315
1316             if (time > s_maxDateOnly) {
1317                 rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue);
1318                 year = 9999;
1319             }
1320             else if (time < s_minDateOnly) {
1321                 rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue);
1322                 year = 1;
1323             }
1324             else {
1325                 DateTime targetTime = time + baseOffset;
1326                 year = time.Year;
1327                 rule = zone.GetAdjustmentRuleForTime(targetTime);
1328             }
1329
1330             if (rule != null) {
1331                 isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone.baseUtcOffset, rule, out isAmbiguousLocalDst);
1332                 baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* */);
1333             }
1334
1335             return baseOffset;
1336         }
1337
1338         // assumes dateTime is in the current time zone's time
1339         private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime) {
1340             if (adjustmentRules == null || adjustmentRules.Length == 0) {
1341                 return null;
1342             }
1343
1344 #if WINXP_AND_WIN2K3_SUPPORT
1345             // On pre-Vista versions of Windows if you run "cmd /c date" or "cmd /c time" to update the system time
1346             // the operating system doesn't pick up the correct time zone adjustment rule (it stays on the currently loaded rule).
1347             // We need to use the OS API data in this scenario instead of the loaded adjustment rules from the registry for
1348             // consistency.  Otherwise DateTime.Now might not match the time displayed in the system tray.              
1349             if (!Environment.IsWindowsVistaOrAbove && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local) {
1350                 return s_cachedData.GetOneYearLocalFromLocal(dateTime.Year).rule;
1351             }
1352 #endif
1353             // Only check the whole-date portion of the dateTime -
1354             // This is because the AdjustmentRule DateStart & DateEnd are stored as
1355             // Date-only values {4/2/2006 - 10/28/2006} but actually represent the
1356             // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999}
1357             DateTime date = dateTime.Date;
1358
1359             for (int i = 0; i < adjustmentRules.Length; i++) {
1360                 if (adjustmentRules[i].DateStart <= date && adjustmentRules[i].DateEnd >= date) {
1361                     return adjustmentRules[i];
1362                 }
1363             }
1364
1365             return null;
1366         }
1367
1368         //
1369         // GetIsDaylightSavingsFromUtc -
1370         //
1371         // Helper function that checks if a given dateTime is in Daylight Saving Time (DST)
1372         // This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone
1373         //
1374         static private Boolean GetIsDaylightSavingsFromUtc(DateTime time, Int32 Year, TimeSpan utc, AdjustmentRule rule, out Boolean isAmbiguousLocalDst) {
1375             isAmbiguousLocalDst = false;
1376
1377             if (rule == null) {
1378                 return false;
1379             }
1380
1381             // Get the daylight changes for the year of the specified time.
1382             TimeSpan offset = utc; /* */
1383             DaylightTime daylightTime = GetDaylightTime(Year, rule);
1384
1385             // The start and end times represent the range of universal times that are in DST for that year.                
1386             // Within that there is an ambiguous hour, usually right at the end, but at the beginning in
1387             // the unusual case of a negative daylight savings delta.
1388             DateTime startTime = daylightTime.Start - offset;
1389             DateTime endTime = daylightTime.End - offset - rule.DaylightDelta; /* */
1390             DateTime ambiguousStart;
1391             DateTime ambiguousEnd;
1392             if (daylightTime.Delta.Ticks > 0) {
1393                 ambiguousStart = endTime - daylightTime.Delta;
1394                 ambiguousEnd = endTime;
1395             } else {
1396                 ambiguousStart = startTime;
1397                 ambiguousEnd = startTime - daylightTime.Delta;
1398             }
1399
1400             Boolean isDst = CheckIsDst(startTime, time, endTime);
1401
1402             // See if the resulting local time becomes ambiguous. This must be captured here or the
1403             // DateTime will not be able to round-trip back to UTC accurately.
1404             if (isDst) {
1405                 isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
1406
1407                 if (!isAmbiguousLocalDst && ambiguousStart.Year != ambiguousEnd.Year) {
1408                     // there exists an extreme corner case where the start or end period is on a year boundary and
1409                     // because of this the comparison above might have been performed for a year-early or a year-later
1410                     // than it should have been.
1411                     DateTime ambiguousStartModified;
1412                     DateTime ambiguousEndModified;
1413                     try {
1414                         ambiguousStartModified = ambiguousStart.AddYears(1);
1415                         ambiguousEndModified   = ambiguousEnd.AddYears(1);
1416                         isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); 
1417                     }
1418                     catch (ArgumentOutOfRangeException) {}
1419
1420                     if (!isAmbiguousLocalDst) {
1421                         try {
1422                             ambiguousStartModified = ambiguousStart.AddYears(-1);
1423                             ambiguousEndModified   = ambiguousEnd.AddYears(-1);
1424                             isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd);
1425                         }
1426                         catch (ArgumentOutOfRangeException) {}
1427                     }
1428
1429                 }
1430             }
1431
1432             return isDst;
1433         }
1434
1435
1436         static private Boolean CheckIsDst(DateTime startTime, DateTime time, DateTime endTime) {
1437             Boolean isDst;
1438
1439             int startTimeYear = startTime.Year;
1440             int endTimeYear = endTime.Year;
1441
1442             if (startTimeYear != endTimeYear) {
1443                 endTime = endTime.AddYears(startTimeYear - endTimeYear);
1444             }
1445
1446             int timeYear = time.Year;
1447
1448             if (startTimeYear != timeYear) {
1449                 time = time.AddYears(startTimeYear - timeYear);
1450             }
1451
1452             if (startTime > endTime) {
1453                 // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year.
1454                 // Note, the summer in the southern hemisphere begins late in the year.
1455                 isDst = (time < endTime || time >= startTime);
1456             }
1457             else {
1458                 // In northern hemisphere, the daylight saving time starts in the middle of the year.
1459                 isDst = (time >= startTime && time < endTime);
1460             }
1461             return isDst;
1462         }
1463
1464         //
1465         // GetDaylightTime -
1466         //
1467         // Helper function that returns a DaylightTime from a year and AdjustmentRule
1468         //
1469         static private DaylightTime GetDaylightTime(Int32 year, AdjustmentRule rule) {
1470             TimeSpan delta = rule.DaylightDelta;
1471             DateTime startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart);
1472             DateTime endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd);
1473             return new DaylightTime(startTime, endTime, delta);
1474         }
1475
1476         //
1477         // TransitionTimeToDateTime -
1478         //
1479         // Helper function that converts a year and TransitionTime into a DateTime
1480         //
1481         static private DateTime TransitionTimeToDateTime(Int32 year, TransitionTime transitionTime) {
1482             DateTime value;
1483             DateTime timeOfDay = transitionTime.TimeOfDay;
1484
1485             if (transitionTime.IsFixedDateRule) {
1486                 // create a DateTime from the passed in year and the properties on the transitionTime
1487
1488                 // if the day is out of range for the month then use the last day of the month
1489                 Int32 day = DateTime.DaysInMonth(year, transitionTime.Month);
1490
1491                 value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day, 
1492                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1493             }
1494             else {
1495                 if (transitionTime.Week <= 4) {
1496                     //
1497                     // Get the (transitionTime.Week)th Sunday.
1498                     //
1499                     value = new DateTime(year, transitionTime.Month, 1,
1500                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1501
1502                     int dayOfWeek = (int)value.DayOfWeek;
1503                     int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
1504                     if (delta < 0) {
1505                         delta += 7;
1506                     }
1507                     delta += 7 * (transitionTime.Week - 1);
1508
1509                     if (delta > 0) {
1510                         value = value.AddDays(delta);
1511                     }
1512                 }
1513                 else {
1514                     //
1515                     // If TransitionWeek is greater than 4, we will get the last week.
1516                     //
1517                     Int32 daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
1518                     value = new DateTime(year, transitionTime.Month, daysInMonth,
1519                             timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
1520
1521                     // This is the day of week for the last day of the month.
1522                     int dayOfWeek = (int)value.DayOfWeek;
1523                     int delta = dayOfWeek - (int)transitionTime.DayOfWeek;
1524                     if (delta < 0) {
1525                         delta += 7;
1526                     }
1527
1528                     if (delta > 0) {
1529                         value = value.AddDays(-delta);
1530                     }
1531                 }
1532             }
1533             return value;
1534         }
1535
1536         //
1537         // IsInvalidTime -
1538         //
1539         // returns true when dateTime falls into a "hole in time".
1540         //
1541         public Boolean IsInvalidTime(DateTime dateTime) {
1542             Boolean isInvalid = false;
1543           
1544             if ( (dateTime.Kind == DateTimeKind.Unspecified)
1545             ||   (dateTime.Kind == DateTimeKind.Local && this == Local) ) {
1546
1547                 // only check Unspecified and (Local when this TimeZoneInfo instance is Local)
1548                 AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime);
1549
1550
1551                 if (rule != null) {
1552                     DaylightTime daylightTime = GetDaylightTime(dateTime.Year, rule);
1553                     isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime);
1554                 }
1555                 else {
1556                     isInvalid = false;
1557                 }
1558             }
1559
1560             return isInvalid;
1561         }
1562
1563         //
1564         // GetIsInvalidTime -
1565         //
1566         // Helper function that checks if a given DateTime is in an invalid time ("time hole")
1567         // A "time hole" occurs at a DST transition point when time jumps forward;
1568         // For example, in Pacific Standard Time on Sunday, April 2, 2006 time jumps from
1569         // 1:59:59.9999999 to 3AM.  The time range 2AM to 2:59:59.9999999AM is the "time hole".
1570         // A "time hole" is not limited to only occurring at the start of DST, and may occur at
1571         // the end of DST as well.
1572         //
1573         static private Boolean GetIsInvalidTime(DateTime time, AdjustmentRule rule, DaylightTime daylightTime) {
1574             Boolean isInvalid = false;
1575             if (rule == null || rule.DaylightDelta == TimeSpan.Zero) {
1576                 return isInvalid;
1577             }
1578
1579             DateTime startInvalidTime;
1580             DateTime endInvalidTime;
1581
1582             // if at DST start we transition forward in time then there is an ambiguous time range at the DST end
1583             if (rule.DaylightDelta < TimeSpan.Zero) {
1584                 startInvalidTime = daylightTime.End;
1585                 endInvalidTime = daylightTime.End - rule.DaylightDelta; /* */
1586             }
1587             else {
1588                 startInvalidTime = daylightTime.Start;
1589                 endInvalidTime = daylightTime.Start + rule.DaylightDelta; /* */
1590             }
1591
1592             isInvalid = (time >= startInvalidTime && time < endInvalidTime);
1593
1594             if (!isInvalid && startInvalidTime.Year != endInvalidTime.Year) {
1595                 // there exists an extreme corner case where the start or end period is on a year boundary and
1596                 // because of this the comparison above might have been performed for a year-early or a year-later
1597                 // than it should have been.
1598                 DateTime startModifiedInvalidTime;
1599                 DateTime endModifiedInvalidTime;
1600                 try {
1601                     startModifiedInvalidTime = startInvalidTime.AddYears(1);
1602                     endModifiedInvalidTime   = endInvalidTime.AddYears(1);
1603                     isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
1604                 }
1605                 catch (ArgumentOutOfRangeException) {}
1606
1607                 if (!isInvalid) {
1608                     try {
1609                         startModifiedInvalidTime = startInvalidTime.AddYears(-1);
1610                         endModifiedInvalidTime  = endInvalidTime.AddYears(-1);
1611                         isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime);
1612                     }
1613                     catch (ArgumentOutOfRangeException) {}
1614                 }
1615             }
1616             return isInvalid;
1617         } 
1618 #endregion
1619         }
1620
1621         struct TimeType {
1622                 public readonly int Offset;
1623                 public readonly bool IsDst;
1624                 public string Name;
1625
1626                 public TimeType (int offset, bool is_dst, string abbrev)
1627                 {
1628                         this.Offset = offset;
1629                         this.IsDst = is_dst;
1630                         this.Name = abbrev;
1631                 }
1632
1633                 public override string ToString ()
1634                 {
1635                         return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
1636                 }
1637 #else
1638         }
1639 #endif
1640         }
1641 }