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