5 * Stephane Delcroix <stephane@delcroix.org>
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:
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
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.
28 using System.Runtime.CompilerServices;
30 #if !INSIDE_CORLIB && (NET_4_0 || BOOTSTRAP_NET_4_0 || MOONLIGHT)
32 [assembly:TypeForwardedTo (typeof(TimeZoneInfo))]
34 #elif NET_3_5 || (MOBILE && !INSIDE_CORLIB) || (MOONLIGHT && INSIDE_CORLIB)
36 using System.Collections.Generic;
37 using System.Collections.ObjectModel;
39 using System.Runtime.Serialization;
47 using Microsoft.Win32;
51 #if NET_4_0 || BOOTSTRAP_NET_4_0
52 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
54 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
56 [SerializableAttribute]
57 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
59 TimeSpan baseUtcOffset;
60 public TimeSpan BaseUtcOffset {
61 get { return baseUtcOffset; }
64 string daylightDisplayName;
65 public string DaylightName {
67 if (disableDaylightSavingTime)
69 return daylightDisplayName;
74 public string DisplayName {
75 get { return displayName; }
83 static TimeZoneInfo local;
84 public static TimeZoneInfo Local {
88 local = ZoneInfoDB.Default;
91 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
94 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
96 throw new TimeZoneNotFoundException ();
100 throw new TimeZoneNotFoundException ();
107 string standardDisplayName;
108 public string StandardName {
109 get { return standardDisplayName; }
112 bool disableDaylightSavingTime;
113 public bool SupportsDaylightSavingTime {
114 get { return !disableDaylightSavingTime; }
117 static TimeZoneInfo utc;
118 public static TimeZoneInfo Utc {
121 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
126 static string timeZoneDirectory = null;
127 static string TimeZoneDirectory {
129 if (timeZoneDirectory == null)
130 timeZoneDirectory = "/usr/share/zoneinfo";
131 return timeZoneDirectory;
135 timeZoneDirectory = value;
139 private AdjustmentRule [] adjustmentRules;
142 static RegistryKey timeZoneKey = null;
143 static bool timeZoneKeySet = false;
144 static RegistryKey TimeZoneKey {
146 if (!timeZoneKeySet) {
147 int p = (int) Environment.OSVersion.Platform;
148 /* Only use the registry on non-Unix platforms. */
149 if ((p != 4) && (p != 6) && (p != 128))
150 timeZoneKey = Registry.LocalMachine.OpenSubKey (
151 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
153 timeZoneKeySet = true;
160 public static void ClearCachedData ()
164 systemTimeZones = null;
167 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
169 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
172 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
174 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
175 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
177 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
178 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
180 if (sourceTimeZone.IsInvalidTime (dateTime))
181 throw new ArgumentException ("dateTime parameter is an invalid time");
183 if (sourceTimeZone == null)
184 throw new ArgumentNullException ("sourceTimeZone");
186 if (destinationTimeZone == null)
187 throw new ArgumentNullException ("destinationTimeZone");
189 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
192 DateTime utc = ConvertTimeToUtc (dateTime);
194 if (destinationTimeZone == TimeZoneInfo.Utc)
197 return ConvertTimeFromUtc (utc, destinationTimeZone);
201 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
203 throw new NotImplementedException ();
206 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
208 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
211 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
213 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
216 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
218 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
221 private DateTime ConvertTimeFromUtc (DateTime dateTime)
223 if (dateTime.Kind == DateTimeKind.Local)
224 throw new ArgumentException ("Kind property of dateTime is Local");
226 if (this == TimeZoneInfo.Utc)
227 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
229 //FIXME: do not rely on DateTime implementation !
230 if (this == TimeZoneInfo.Local)
231 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
233 AdjustmentRule rule = GetApplicableRule (dateTime);
235 if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
236 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
238 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
241 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
243 if (destinationTimeZone == null)
244 throw new ArgumentNullException ("destinationTimeZone");
246 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
249 public static DateTime ConvertTimeToUtc (DateTime dateTime)
251 if (dateTime.Kind == DateTimeKind.Utc)
254 //FIXME: do not rely on DateTime implementation !
255 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
258 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
260 if (sourceTimeZone == null)
261 throw new ArgumentNullException ("sourceTimeZone");
263 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
264 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
266 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
267 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
269 if (sourceTimeZone.IsInvalidTime (dateTime))
270 throw new ArgumentException ("dateTime parameter is an invalid time");
272 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
275 if (dateTime.Kind == DateTimeKind.Utc)
278 if (dateTime.Kind == DateTimeKind.Local)
279 return ConvertTimeToUtc (dateTime);
281 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
282 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
284 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
285 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
289 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
291 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
294 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
296 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
299 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
301 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
304 public bool Equals (TimeZoneInfo other)
309 return other.Id == this.Id && HasSameRules (other);
312 public static TimeZoneInfo FindSystemTimeZoneById (string id)
314 //FIXME: this method should check for cached values in systemTimeZones
316 throw new ArgumentNullException ("id");
318 if (TimeZoneKey != null)
320 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
322 throw new TimeZoneNotFoundException ();
323 return FromRegistryKey(id, key);
327 return ZoneInfoDB.GetTimeZone (id);
329 string filepath = Path.Combine (TimeZoneDirectory, id);
330 return FindSystemTimeZoneByFileName (id, filepath);
332 throw new NotImplementedException ();
337 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
338 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
340 if (!File.Exists (filepath))
341 throw new TimeZoneNotFoundException ();
343 byte [] buffer = new byte [BUFFER_SIZE];
345 using (FileStream stream = File.OpenRead (filepath)) {
346 length = stream.Read (buffer, 0, BUFFER_SIZE);
349 if (!ValidTZFile (buffer, length))
350 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
353 return ParseTZBuffer (id, buffer, length);
354 } catch (Exception e) {
355 throw new InvalidTimeZoneException (e.Message);
361 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
363 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
366 throw new InvalidTimeZoneException ();
368 int bias = BitConverter.ToInt32 (reg_tzi, 0);
369 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
371 string display_name = (string) key.GetValue ("Display");
372 string standard_name = (string) key.GetValue ("Std");
373 string daylight_name = (string) key.GetValue ("Dlt");
375 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
377 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
378 if (dst_key != null) {
379 int first_year = (int) dst_key.GetValue ("FirstEntry");
380 int last_year = (int) dst_key.GetValue ("LastEntry");
383 for (year=first_year; year<=last_year; year++) {
384 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
385 if (dst_tzi != null) {
386 int start_year = year == first_year ? 1 : year;
387 int end_year = year == last_year ? 9999 : year;
388 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
393 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
395 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
398 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
400 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
401 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
403 int standard_year = BitConverter.ToInt16 (buffer, 12);
404 int standard_month = BitConverter.ToInt16 (buffer, 14);
405 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
406 int standard_day = BitConverter.ToInt16 (buffer, 18);
407 int standard_hour = BitConverter.ToInt16 (buffer, 20);
408 int standard_minute = BitConverter.ToInt16 (buffer, 22);
409 int standard_second = BitConverter.ToInt16 (buffer, 24);
410 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
412 int daylight_year = BitConverter.ToInt16 (buffer, 28);
413 int daylight_month = BitConverter.ToInt16 (buffer, 30);
414 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
415 int daylight_day = BitConverter.ToInt16 (buffer, 34);
416 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
417 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
418 int daylight_second = BitConverter.ToInt16 (buffer, 40);
419 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
421 if (standard_month == 0 || daylight_month == 0)
425 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
426 TransitionTime start_transition_time;
428 if (daylight_year == 0) {
429 start_date = new DateTime (start_year, 1, 1);
430 start_transition_time = TransitionTime.CreateFloatingDateRule (
431 start_timeofday, daylight_month, daylight_day,
432 (DayOfWeek) daylight_dayofweek);
435 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
436 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
437 start_transition_time = TransitionTime.CreateFixedDateRule (
438 start_timeofday, daylight_month, daylight_day);
442 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
443 TransitionTime end_transition_time;
445 if (standard_year == 0) {
446 end_date = new DateTime (end_year, 12, 31);
447 end_transition_time = TransitionTime.CreateFloatingDateRule (
448 end_timeofday, standard_month, standard_day,
449 (DayOfWeek) standard_dayofweek);
452 end_date = new DateTime (standard_year, standard_month, standard_day,
453 standard_hour, standard_minute, standard_second, standard_millisecond);
454 end_transition_time = TransitionTime.CreateFixedDateRule (
455 end_timeofday, standard_month, standard_day);
458 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
460 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
461 start_date, end_date, daylight_delta,
462 start_transition_time, end_transition_time));
466 public static TimeZoneInfo FromSerializedString (string source)
468 throw new NotImplementedException ();
471 public AdjustmentRule [] GetAdjustmentRules ()
473 if (disableDaylightSavingTime)
474 return new AdjustmentRule [0];
476 return (AdjustmentRule []) adjustmentRules.Clone ();
479 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
481 if (!IsAmbiguousTime (dateTime))
482 throw new ArgumentException ("dateTime is not an ambiguous time");
484 AdjustmentRule rule = GetApplicableRule (dateTime);
485 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
488 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
490 if (!IsAmbiguousTime (dateTimeOffset))
491 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
493 throw new NotImplementedException ();
496 public override int GetHashCode ()
498 int hash_code = Id.GetHashCode ();
499 foreach (AdjustmentRule rule in GetAdjustmentRules ())
500 hash_code ^= rule.GetHashCode ();
505 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
507 public void GetObjectData (SerializationInfo info, StreamingContext context)
510 throw new NotImplementedException ();
513 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
514 private static List<TimeZoneInfo> systemTimeZones = null;
515 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
517 if (systemTimeZones == null) {
518 systemTimeZones = new List<TimeZoneInfo> ();
520 if (TimeZoneKey != null) {
521 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
523 systemTimeZones.Add (FindSystemTimeZoneById (id));
527 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
531 systemTimeZones.AddRange (ZoneInfoDB.GetAvailableIds ()
532 .Select (id => ZoneInfoDB.GetTimeZone (id)));
534 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
535 foreach (string continent in continents) {
537 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
539 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
540 systemTimeZones.Add (FindSystemTimeZoneById (id));
541 } catch (ArgumentNullException) {
542 } catch (TimeZoneNotFoundException) {
543 } catch (InvalidTimeZoneException) {
544 } catch (Exception) {
551 throw new NotImplementedException ("This method is not implemented for this platform");
554 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
557 public TimeSpan GetUtcOffset (DateTime dateTime)
559 if (IsDaylightSavingTime (dateTime)) {
560 AdjustmentRule rule = GetApplicableRule (dateTime);
561 return BaseUtcOffset + rule.DaylightDelta;
564 return BaseUtcOffset;
567 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
569 throw new NotImplementedException ();
572 public bool HasSameRules (TimeZoneInfo other)
575 throw new ArgumentNullException ("other");
577 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
580 if (this.adjustmentRules == null)
583 if (this.BaseUtcOffset != other.BaseUtcOffset)
586 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
589 for (int i = 0; i < adjustmentRules.Length; i++) {
590 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
597 public bool IsAmbiguousTime (DateTime dateTime)
599 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
600 throw new ArgumentException ("Kind is Local and time is Invalid");
602 if (this == TimeZoneInfo.Utc)
605 if (dateTime.Kind == DateTimeKind.Utc)
606 dateTime = ConvertTimeFromUtc (dateTime);
608 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
609 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
611 AdjustmentRule rule = GetApplicableRule (dateTime);
612 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
613 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
619 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
621 throw new NotImplementedException ();
624 public bool IsDaylightSavingTime (DateTime dateTime)
626 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
627 throw new ArgumentException ("dateTime is invalid and Kind is Local");
629 if (this == TimeZoneInfo.Utc)
632 if (!SupportsDaylightSavingTime)
634 //FIXME: do not rely on DateTime implementation !
635 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
636 return dateTime.IsDaylightSavingTime ();
638 //FIXME: do not rely on DateTime implementation !
639 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
640 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
642 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
646 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
647 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
648 if (dateTime.Kind == DateTimeKind.Utc) {
649 DST_start -= BaseUtcOffset;
650 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
653 return (dateTime >= DST_start && dateTime < DST_end);
656 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
658 throw new NotImplementedException ();
661 public bool IsInvalidTime (DateTime dateTime)
663 if (dateTime.Kind == DateTimeKind.Utc)
665 if (dateTime.Kind == DateTimeKind.Local && this != Local)
668 AdjustmentRule rule = GetApplicableRule (dateTime);
669 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
670 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
677 void IDeserializationCallback.OnDeserialization (object sender)
679 public void OnDeserialization (object sender)
682 throw new NotImplementedException ();
685 public string ToSerializedString ()
687 throw new NotImplementedException ();
690 public override string ToString ()
695 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
698 throw new ArgumentNullException ("id");
700 if (id == String.Empty)
701 throw new ArgumentException ("id parameter is an empty string");
703 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
704 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
706 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
707 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
711 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
714 if (adjustmentRules != null && adjustmentRules.Length != 0) {
715 AdjustmentRule prev = null;
716 foreach (AdjustmentRule current in adjustmentRules) {
718 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
720 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
721 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
722 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;");
724 if (prev != null && prev.DateStart > current.DateStart)
725 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
727 if (prev != null && prev.DateEnd > current.DateStart)
728 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
730 if (prev != null && prev.DateEnd == current.DateStart)
731 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
738 this.baseUtcOffset = baseUtcOffset;
739 this.displayName = displayName ?? id;
740 this.standardDisplayName = standardDisplayName ?? id;
741 this.daylightDisplayName = daylightDisplayName;
742 this.disableDaylightSavingTime = disableDaylightSavingTime;
743 this.adjustmentRules = adjustmentRules;
746 private AdjustmentRule GetApplicableRule (DateTime dateTime)
748 //Transitions are always in standard time
749 DateTime date = dateTime;
751 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
752 date = date.ToUniversalTime () + BaseUtcOffset;
754 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
755 date = date + BaseUtcOffset;
757 if (adjustmentRules != null) {
758 foreach (AdjustmentRule rule in adjustmentRules) {
759 if (rule.DateStart > date.Date)
761 if (rule.DateEnd < date.Date)
769 private static DateTime TransitionPoint (TransitionTime transition, int year)
771 if (transition.IsFixedDateRule)
772 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
774 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
775 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
776 if (day > DateTime.DaysInMonth (year, transition.Month))
778 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
781 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
783 AdjustmentRule prev = null;
784 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
785 if (prev != null && prev.DateEnd > current.DateStart) {
786 adjustmentRules.Remove (current);
790 return adjustmentRules;
793 #if LIBC || MONODROID
794 private static bool ValidTZFile (byte [] buffer, int length)
796 StringBuilder magic = new StringBuilder ();
798 for (int i = 0; i < 4; i++)
799 magic.Append ((char)buffer [i]);
801 if (magic.ToString () != "TZif")
804 if (length >= BUFFER_SIZE)
812 public readonly int Offset;
813 public readonly bool IsDst;
816 public TimeType (int offset, bool is_dst, string abbrev)
818 this.Offset = offset;
823 public override string ToString ()
825 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
829 static int SwapInt32 (int i)
831 return (((i >> 24) & 0xff)
832 | ((i >> 8) & 0xff00)
833 | ((i << 8) & 0xff0000)
837 static int ReadBigEndianInt32 (byte [] buffer, int start)
839 int i = BitConverter.ToInt32 (buffer, start);
840 if (!BitConverter.IsLittleEndian)
843 return SwapInt32 (i);
846 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
848 //Reading the header. 4 bytes for magic, 16 are reserved
849 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
850 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
851 int leapcnt = ReadBigEndianInt32 (buffer, 28);
852 int timecnt = ReadBigEndianInt32 (buffer, 32);
853 int typecnt = ReadBigEndianInt32 (buffer, 36);
854 int charcnt = ReadBigEndianInt32 (buffer, 40);
856 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
857 throw new InvalidTimeZoneException ();
859 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
860 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
861 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
863 if (time_types.Count == 0)
864 throw new InvalidTimeZoneException ();
866 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
867 throw new InvalidTimeZoneException ();
869 TimeSpan baseUtcOffset = new TimeSpan (0);
870 TimeSpan dstDelta = new TimeSpan (0);
871 string standardDisplayName = null;
872 string daylightDisplayName = null;
873 bool dst_observed = false;
874 DateTime dst_start = DateTime.MinValue;
875 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
877 for (int i = 0; i < transitions.Count; i++) {
878 var pair = transitions [i];
879 DateTime ttime = pair.Key;
880 TimeType ttype = pair.Value;
882 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
883 standardDisplayName = ttype.Name;
884 daylightDisplayName = null;
885 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
886 adjustmentRules = new List<AdjustmentRule> ();
887 dst_observed = false;
890 //FIXME: check additional fields for this:
891 //most of the transitions are expressed in GMT
892 dst_start += baseUtcOffset;
893 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
895 //some weird timezone (America/Phoenix) have end dates on Jan 1st
896 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
897 dst_end -= new TimeSpan (24, 0, 0);
899 DateTime dateStart, dateEnd;
900 if (dst_start.Month < 7)
901 dateStart = new DateTime (dst_start.Year, 1, 1);
903 dateStart = new DateTime (dst_start.Year, 7, 1);
905 if (dst_end.Month >= 7)
906 dateEnd = new DateTime (dst_end.Year, 12, 31);
908 dateEnd = new DateTime (dst_end.Year, 6, 30);
911 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
912 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
913 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
914 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
916 dst_observed = false;
918 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
919 daylightDisplayName = ttype.Name;
920 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
927 if (adjustmentRules.Count == 0) {
928 TimeType t = (TimeType)time_types [0];
929 if (standardDisplayName == null) {
930 standardDisplayName = t.Name;
931 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
933 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
935 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
939 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
941 var abbrevs = new Dictionary<int, string> ();
942 int abbrev_index = 0;
943 var sb = new StringBuilder ();
944 for (int i = 0; i < count; i++) {
945 char c = (char) buffer [index + i];
949 abbrevs.Add (abbrev_index, sb.ToString ());
950 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
951 for (int j = 1; j < sb.Length; j++)
952 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
953 abbrev_index = i + 1;
954 sb = new StringBuilder ();
960 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
962 var types = new Dictionary<int, TimeType> (count);
963 for (int i = 0; i < count; i++) {
964 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
965 byte is_dst = buffer [index + 6 * i + 4];
966 byte abbrev = buffer [index + 6 * i + 5];
967 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
972 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
974 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
975 for (int i = 0; i < count; i++) {
976 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
977 DateTime ttime = DateTimeFromUnixTime (unixtime);
978 byte ttype = buffer [index + 4 * count + i];
979 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
984 static DateTime DateTimeFromUnixTime (long unix_time)
986 DateTime date_time = new DateTime (1970, 1, 1);
987 return date_time.AddSeconds (unix_time);