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