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