2008-05-21 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Core / System / TimeZoneInfo.cs
1 /*
2  * System.TimeZoneInfo
3  *
4  * Author(s)
5  *      Stephane Delcroix <stephane@delcroix.org>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  * 
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  * 
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  */
26
27 using System.Collections.Generic;
28 using System.Collections.ObjectModel;
29 using System.Runtime.Serialization;
30 using System.Text;
31
32 #if LIBC
33 using System.IO;
34 using Mono;
35 #endif
36
37 namespace System
38 {
39         [SerializableAttribute]
40         public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
41         {
42                 TimeSpan baseUtcOffset;
43                 public TimeSpan BaseUtcOffset {
44                         get { return baseUtcOffset; }
45                 }
46
47                 string daylightDisplayName;
48                 public string DaylightName {
49                         get { 
50                                 if (disableDaylightSavingTime)
51                                         return String.Empty;
52                                 return daylightDisplayName; 
53                         }
54                 }
55
56                 string displayName;
57                 public string DisplayName {
58                         get { return displayName; }
59                 }
60
61                 string id;
62                 public string Id {
63                         get { return id; }
64                 }
65
66                 static TimeZoneInfo local;
67                 public static TimeZoneInfo Local {
68                         get { 
69                                 if (local == null) {
70 #if LIBC
71                                         try {
72                                                 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");       
73                                         } catch {
74                                                 try {
75                                                         local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));  
76                                                 } catch {
77                                                         throw new TimeZoneNotFoundException ();
78                                                 }
79                                         }
80 #else
81                                         throw new TimeZoneNotFoundException ();
82 #endif
83                                 }
84                                 return local;
85                         }
86                 }
87
88                 string standardDisplayName;
89                 public string StandardName {
90                         get { return standardDisplayName; }
91                 }
92
93                 bool disableDaylightSavingTime;
94                 public bool SupportsDaylightSavingTime {
95                         get  { return !disableDaylightSavingTime; }
96                 }
97
98                 static TimeZoneInfo utc;
99                 public static TimeZoneInfo Utc {
100                         get {
101                                 if (utc == null)
102                                         utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
103                                 return utc;
104                         }
105                 }
106 #if LIBC
107                 static string timeZoneDirectory = null;
108                 public static string TimeZoneDirectory {
109                         get {
110                                 if (timeZoneDirectory == null)
111                                         timeZoneDirectory = "/usr/share/zoneinfo";
112                                 return timeZoneDirectory;
113                         }
114                         set {
115                                 ClearCachedData ();
116                                 timeZoneDirectory = value;
117                         }
118                 }
119 #endif
120                 private AdjustmentRule [] adjustmentRules;
121
122                 public static void ClearCachedData ()
123                 {
124                         local = null;
125                         utc = null;
126                         systemTimeZones = null;
127                 }
128
129                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
130                 {
131                         return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
132                 }
133
134                 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
135                 {
136                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
137                                 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
138
139                         if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
140                                 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
141
142                         if (sourceTimeZone.IsInvalidTime (dateTime))
143                                 throw new ArgumentException ("dateTime parameter is an invalid time");
144
145                         if (sourceTimeZone == null)
146                                 throw new ArgumentNullException ("sourceTimeZone");
147
148                         if (destinationTimeZone == null)
149                                 throw new ArgumentNullException ("destinationTimeZone");
150
151                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
152                                 return dateTime;
153
154                         DateTime utc = ConvertTimeToUtc (dateTime);
155
156                         if (destinationTimeZone == TimeZoneInfo.Utc)
157                                 return utc;
158
159                         return ConvertTimeFromUtc (utc, destinationTimeZone);   
160
161                 }
162
163                 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
164                 {
165                         throw new NotImplementedException ();
166                 }
167
168                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
169                 {
170                         return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
171                 }
172
173                 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
174                 {
175                         return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
176                 }
177
178                 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
179                 {
180                         return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
181                 }
182
183                 private DateTime ConvertTimeFromUtc (DateTime dateTime)
184                 {
185                         if (dateTime.Kind == DateTimeKind.Local)
186                                 throw new ArgumentException ("Kind property of dateTime is Local");
187
188                         if (this == TimeZoneInfo.Utc)
189                                 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
190
191                         //FIXME: do not rely on DateTime implementation !
192                         if (this == TimeZoneInfo.Local)
193                                 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
194
195                         AdjustmentRule rule = GetApplicableRule (dateTime);
196                 
197                         if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
198                                 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
199                         else
200                                 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
201                 }
202
203                 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
204                 {
205                         if (destinationTimeZone == null)
206                                 throw new ArgumentNullException ("destinationTimeZone");
207
208                         return destinationTimeZone.ConvertTimeFromUtc (dateTime);
209                 }
210
211                 public static DateTime ConvertTimeToUtc (DateTime dateTime)
212                 {
213                         if (dateTime.Kind == DateTimeKind.Utc)
214                                 return dateTime;
215
216                         //FIXME: do not rely on DateTime implementation !
217                         return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
218                 }
219
220                 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
221                 {
222                         if (sourceTimeZone == null)
223                                 throw new ArgumentNullException ("sourceTimeZone");
224
225                         if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
226                                 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
227
228                         if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
229                                 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
230
231                         if (sourceTimeZone.IsInvalidTime (dateTime))
232                                 throw new ArgumentException ("dateTime parameter is an invalid time");
233
234                         if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
235                                 return dateTime;
236
237                         if (dateTime.Kind == DateTimeKind.Utc)
238                                 return dateTime;
239
240                         if (dateTime.Kind == DateTimeKind.Local)
241                                 return ConvertTimeToUtc (dateTime);
242
243                         if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
244                                 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
245                         else {
246                                 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
247                                 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
248                         }
249                 }
250
251                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName) 
252                 {
253                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
254                 }
255
256                 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
257                 {
258                         return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
259                 }
260
261                 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
262                 {
263                         return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
264                 }
265
266                 public bool Equals (TimeZoneInfo other)
267                 {
268                         if (other == null)
269                                 return false;
270
271                         return other.Id == this.Id && HasSameRules (other);
272                 }
273
274                 public static TimeZoneInfo FindSystemTimeZoneById (string id)
275                 {
276                         //FIXME: this method should check for cached values in systemTimeZones
277                         if (id == null)
278                                 throw new ArgumentNullException ("id");
279 #if LIBC        
280                         string filepath = Path.Combine (TimeZoneDirectory, id);
281                         return FindSystemTimeZoneByFileName (id, filepath);
282 #else
283                         throw new NotImplementedException ();
284 #endif
285                 }
286
287 #if LIBC
288                 const int BUFFER_SIZE = 8192; //Big enough for any tz file
289                 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
290                 {
291                         if (!File.Exists (filepath))
292                                 throw new TimeZoneNotFoundException ();
293
294                         byte [] buffer = new byte [BUFFER_SIZE];
295                         int length;
296                         using (FileStream stream = File.OpenRead (filepath)) {
297                                 length = stream.Read (buffer, 0, BUFFER_SIZE);
298                         }
299
300                         if (!ValidTZFile (buffer, length))
301                                 throw new InvalidTimeZoneException ();
302
303                         try {
304                                 return ParseTZBuffer (id, buffer, length);
305                         } catch (Exception e) {
306                                 throw new InvalidTimeZoneException (e.Message);
307                         }
308                 }
309 #endif
310
311                 public static TimeZoneInfo FromSerializedString (string source)
312                 {
313                         throw new NotImplementedException ();
314                 }
315
316                 public AdjustmentRule [] GetAdjustmentRules ()
317                 {
318                         if (disableDaylightSavingTime)
319                                 return new AdjustmentRule [0];
320                         else
321                                 return (AdjustmentRule []) adjustmentRules.Clone ();
322                 }
323
324                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
325                 {
326                         if (!IsAmbiguousTime (dateTime))
327                                 throw new ArgumentException ("dateTime is not an ambiguous time");
328
329                         AdjustmentRule rule = GetApplicableRule (dateTime);
330                         return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
331                 }
332
333                 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
334                 {
335                         if (!IsAmbiguousTime (dateTimeOffset))
336                                 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
337
338                         throw new NotImplementedException ();
339                 }
340
341                 public override int GetHashCode ()
342                 {
343                         int hash_code = Id.GetHashCode ();
344                         foreach (AdjustmentRule rule in GetAdjustmentRules ())
345                                 hash_code ^= rule.GetHashCode ();
346                         return hash_code;
347                 }
348
349                 public void GetObjectData (SerializationInfo info, StreamingContext context)
350                 {
351                         throw new NotImplementedException ();
352                 }
353
354                 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
355                 private static List<TimeZoneInfo> systemTimeZones = null;
356                 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
357                 {
358                         if (systemTimeZones == null) {
359                                 systemTimeZones = new List<TimeZoneInfo> ();
360 #if LIBC
361                                 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
362                                 foreach (string continent in continents) {
363                                         try {
364                                                 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
365                                                         try {
366                                                                 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
367                                                                 systemTimeZones.Add (FindSystemTimeZoneById (id));
368                                                         } catch (ArgumentNullException) {
369                                                         } catch (TimeZoneNotFoundException) {
370                                                         } catch (InvalidTimeZoneException) {
371                                                         } catch (Exception e) {
372                                                                 if (e is OutOfMemoryException || e is System.Security.SecurityException)
373                                                                         throw;
374                                                                 else {
375                                                                         Console.WriteLine ("Unexpected Exception");
376                                                                         throw;
377                                                                 }
378                                                         }
379                                                 }
380                                         } catch {}
381                                 }
382 #else
383                                 throw new NotImplementedException ("This method is not implemented for this platform");
384 #endif
385                         }
386                         return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
387                 }
388
389                 public TimeSpan GetUtcOffset (DateTime dateTime)
390                 {
391                         if (IsDaylightSavingTime (dateTime)) {
392                                 AdjustmentRule rule = GetApplicableRule (dateTime);
393                                 return BaseUtcOffset + rule.DaylightDelta;
394                         }
395                         
396                         return BaseUtcOffset;
397                 }
398
399                 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
400                 {
401                         throw new NotImplementedException ();
402                 }
403
404                 public bool HasSameRules (TimeZoneInfo other)
405                 {
406                         if (other == null)
407                                 throw new ArgumentNullException ("other");
408
409                         if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
410                                 return false;
411
412                         if (this.adjustmentRules == null)
413                                 return true;
414
415                         if (this.BaseUtcOffset != other.BaseUtcOffset)
416                                 return false;
417
418                         if (this.adjustmentRules.Length != other.adjustmentRules.Length)
419                                 return false;
420
421                         for (int i = 0; i < adjustmentRules.Length; i++) {
422                                 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
423                                         return false;
424                         }
425                         
426                         return true;
427                 }
428
429                 public bool IsAmbiguousTime (DateTime dateTime)
430                 {
431                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
432                                 throw new ArgumentException ("Kind is Local and time is Invalid");
433
434                         if (this == TimeZoneInfo.Utc)
435                                 return false;
436                         
437                         if (dateTime.Kind == DateTimeKind.Utc)
438                                 dateTime = ConvertTimeFromUtc (dateTime);
439
440                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
441                                 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
442
443                         AdjustmentRule rule = GetApplicableRule (dateTime);
444                         DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
445                         if (dateTime > tpoint - rule.DaylightDelta  && dateTime <= tpoint)
446                                 return true;
447                                 
448                         return false;
449                 }
450
451                 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
452                 {
453                         throw new NotImplementedException ();
454                 }
455
456                 public bool IsDaylightSavingTime (DateTime dateTime)
457                 {
458                         if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
459                                 throw new ArgumentException ("dateTime is invalid and Kind is Local");
460
461                         if (this == TimeZoneInfo.Utc)
462                                 return false;
463
464                         if (!SupportsDaylightSavingTime)
465                                 return false;
466                         //FIXME: do not rely on DateTime implementation !
467                         if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
468                                 return dateTime.IsDaylightSavingTime ();
469
470                         //FIXME: do not rely on DateTime implementation !
471                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
472                                 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
473                                 
474                         AdjustmentRule rule = GetApplicableRule (dateTime.Date);
475                         if (rule == null)
476                                 return false;
477
478                         DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
479                         DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
480                         if (dateTime.Kind == DateTimeKind.Utc) {
481                                 DST_start -= BaseUtcOffset;
482                                 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
483                         }
484
485                         return (dateTime >= DST_start && dateTime < DST_end);
486                 }
487
488                 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
489                 {
490                         throw new NotImplementedException ();
491                 }
492
493                 public bool IsInvalidTime (DateTime dateTime)
494                 {
495                         if (dateTime.Kind == DateTimeKind.Utc)
496                                 return false;
497                         if (dateTime.Kind == DateTimeKind.Local && this != Local)
498                                 return false;
499
500                         AdjustmentRule rule = GetApplicableRule (dateTime);
501                         DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
502                         if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
503                                 return true;
504                                 
505                         return false;
506                 }
507
508                 public void OnDeserialization (object sender)
509                 {
510                         throw new NotImplementedException ();
511                 }
512                 
513                 public string ToSerializedString ()
514                 {
515                         throw new NotImplementedException ();
516                 }
517
518                 public override string ToString ()
519                 {
520                         return DisplayName;
521                 }
522
523                 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
524                 {
525                         if (id == null)
526                                 throw new ArgumentNullException ("id");
527
528                         if (id == String.Empty)
529                                 throw new ArgumentException ("id parameter is an empty string");
530
531                         if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
532                                 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
533
534                         if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
535                                 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
536
537 #if STRICT
538                         if (id.Length > 32)
539                                 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
540 #endif
541
542                         if (adjustmentRules != null && adjustmentRules.Length != 0) {
543                                 AdjustmentRule prev = null;
544                                 foreach (AdjustmentRule current in adjustmentRules) {
545                                         if (current == null)
546                                                 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
547
548                                         if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
549                                                         (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
550                                                 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;");
551
552                                         if (prev != null && prev.DateStart > current.DateStart)
553                                                 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
554                                         
555                                         if (prev != null && prev.DateEnd > current.DateStart)
556                                                 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
557
558                                         if (prev != null && prev.DateEnd == current.DateStart)
559                                                 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
560
561                                         prev = current;
562                                 }
563                         }
564                         
565                         this.id = id;
566                         this.baseUtcOffset = baseUtcOffset;
567                         this.displayName = displayName ?? id;
568                         this.standardDisplayName = standardDisplayName ?? id;
569                         this.daylightDisplayName = daylightDisplayName;
570                         this.disableDaylightSavingTime = disableDaylightSavingTime;
571                         this.adjustmentRules = adjustmentRules;
572                 }
573
574                 private AdjustmentRule GetApplicableRule (DateTime dateTime)
575                 {
576                         //Transitions are always in standard time
577                         DateTime date = dateTime;
578
579                         if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
580                                 date = date.ToUniversalTime () + BaseUtcOffset;
581
582                         if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
583                                 date = date + BaseUtcOffset;
584
585                         foreach (AdjustmentRule rule in adjustmentRules) {
586                                 if (rule.DateStart > date.Date)
587                                         return null;
588                                 if (rule.DateEnd < date.Date)
589                                         continue;
590                                 return rule;
591                         }
592                         return null;
593                 }
594
595                 private static DateTime TransitionPoint (TransitionTime transition, int year)
596                 {
597                         if (transition.IsFixedDateRule)
598                                 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
599
600                         DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
601                         int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
602                         if (day >  DateTime.DaysInMonth (year, transition.Month))
603                                 day -= 7;
604                         return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
605                 }
606
607 #if LIBC
608                 private static bool ValidTZFile (byte [] buffer, int length)
609                 {
610                         StringBuilder magic = new StringBuilder ();
611
612                         for (int i = 0; i < 4; i++)
613                                 magic.Append ((char)buffer [i]);
614                         
615                         if (magic.ToString () != "TZif")
616                                 return false;
617
618                         if (length >= BUFFER_SIZE)
619                                 return false;
620
621                         return true;
622                 }
623
624                 struct TimeType 
625                 {
626                         public readonly int Offset;
627                         public readonly bool IsDst;
628                         public string Name;
629
630                         public TimeType (int offset, bool is_dst, string abbrev)
631                         {
632                                 this.Offset = offset;
633                                 this.IsDst = is_dst;
634                                 this.Name = abbrev;
635                         }
636
637                         public override string ToString ()
638                         {
639                                 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
640                         }
641                 }
642
643                 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
644                 {
645                         DataConverter enc = DataConverter.BigEndian;
646
647                         //Reading the header. 4 bytes for magic, 16 are reserved
648                         int ttisgmtcnt = enc.GetInt32 (buffer, 20);
649                         int ttisstdcnt = enc.GetInt32 (buffer, 24);
650                         int leapcnt = enc.GetInt32 (buffer, 28);
651                         int timecnt = enc.GetInt32 (buffer, 32);
652                         int typecnt = enc.GetInt32 (buffer, 36);
653                         int charcnt = enc.GetInt32 (buffer, 40);
654
655                         if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
656                                 throw new InvalidTimeZoneException ();
657
658                         Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
659                         Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
660                         SortedList<DateTime, TimeType> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
661
662                         if (time_types.Count == 0)
663                                 throw new InvalidTimeZoneException ();
664
665                         if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
666                                 throw new InvalidTimeZoneException ();
667
668                         TimeSpan baseUtcOffset = new TimeSpan (0);
669                         TimeSpan dstDelta = new TimeSpan (0);
670                         string standardDisplayName = null;
671                         string daylightDisplayName = null;
672                         bool dst_observed = false;
673                         DateTime dst_start = DateTime.MinValue;
674                         List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
675
676                         for (int i = 0; i < transitions.Count; i++) {
677                                 DateTime ttime = transitions.Keys [i];
678                                 TimeType ttype = transitions [ttime];
679                                 if (!ttype.IsDst) {
680                                         if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
681                                                 standardDisplayName = ttype.Name;
682                                                 daylightDisplayName = null;
683                                                 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
684                                                 adjustmentRules = new List<AdjustmentRule> ();
685                                                 dst_observed = false;
686                                         }
687                                         if (dst_observed) {
688                                                 //FIXME: check additional fields for this:
689                                                 //most of the transitions are expressed in GMT 
690                                                 dst_start += baseUtcOffset;
691                                                 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
692
693                                                 //some weird timezone (America/Phoenix) have end dates on Jan 1st
694                                                 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
695                                                         dst_end -= new TimeSpan (24, 0, 0);
696
697                                                 DateTime dateStart, dateEnd;
698                                                 if (dst_start.Month < 7)
699                                                         dateStart = new DateTime (dst_start.Year, 1, 1);
700                                                 else
701                                                         dateStart = new DateTime (dst_start.Year, 7, 1);
702
703                                                 if (dst_end.Month >= 7)
704                                                         dateEnd = new DateTime (dst_end.Year, 12, 31);
705                                                 else
706                                                         dateEnd = new DateTime (dst_end.Year, 6, 30);
707
708                                                 
709                                                 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
710                                                 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
711                                                 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
712                                         }
713                                         dst_observed = false;
714                                 } else {
715                                         if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
716                                                 daylightDisplayName = ttype.Name;
717                                                 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
718                                         }
719                                         dst_start = ttime;
720                                         dst_observed = true;
721                                 }
722                         }
723
724                         if (adjustmentRules.Count == 0) {
725                                 TimeType t = (TimeType)time_types [0];
726                                 if (standardDisplayName == null) {
727                                         standardDisplayName = t.Name;
728                                         baseUtcOffset = new TimeSpan (0, 0, t.Offset);
729                                 }
730                                 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
731                         } else {
732                                 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, adjustmentRules.ToArray ());
733                         }
734                 }
735
736                 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
737                 {
738                         var abbrevs = new Dictionary<int, string> ();
739                         int abbrev_index = 0;
740                         var sb = new StringBuilder ();
741                         for (int i = 0; i < count; i++) {
742                                 char c = (char) buffer [index + i];
743                                 if (c != '\0')
744                                         sb.Append (c);
745                                 else {
746                                         abbrevs.Add (abbrev_index, sb.ToString ());
747                                         abbrev_index = i + 1;
748                                         sb = new StringBuilder ();
749                                 }
750                         }
751                         return abbrevs;
752                 }
753
754                 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
755                 {
756                         DataConverter enc = DataConverter.BigEndian;
757                         var types = new Dictionary<int, TimeType> (count);
758                         for (int i = 0; i < count; i++) {
759                                 int offset = enc.GetInt32 (buffer, index + 6 * i);
760                                 byte is_dst = buffer [index + 6 * i + 4];
761                                 byte abbrev = buffer [index + 6 * i + 5];
762                                 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
763                         }
764                         return types;
765                 }
766
767                 static SortedList<DateTime, TimeType> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
768                 {
769                         DataConverter enc = DataConverter.BigEndian;
770                         var trans = new SortedList<DateTime, TimeType> (count);
771                         for (int i = 0; i < count; i++) {
772                                 int unixtime = enc.GetInt32 (buffer, index + 4 * i);
773                                 DateTime ttime = DateTimeFromUnixTime (unixtime);
774                                 byte ttype = buffer [index + 4 * count + i];
775                                 trans.Add (ttime, time_types [(int)ttype]);
776                         }
777                         return trans;
778                 }
779
780                 static DateTime DateTimeFromUnixTime (long unix_time)
781                 {
782                         DateTime date_time = new DateTime (1970, 1, 1);
783                         return date_time.AddSeconds (unix_time);
784                 }
785 #endif
786         }
787 }