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