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