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