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)
32 [assembly:TypeForwardedTo (typeof(TimeZoneInfo))]
34 #elif NET_3_5 || (NET_2_1 && !INSIDE_CORLIB)
36 using System.Collections.Generic;
37 using System.Collections.ObjectModel;
38 using System.Runtime.Serialization;
48 #if NET_4_0 || BOOTSRAP_NET_4_0
49 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
51 [SerializableAttribute]
52 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
54 TimeSpan baseUtcOffset;
55 public TimeSpan BaseUtcOffset {
56 get { return baseUtcOffset; }
59 string daylightDisplayName;
60 public string DaylightName {
62 if (disableDaylightSavingTime)
64 return daylightDisplayName;
69 public string DisplayName {
70 get { return displayName; }
78 static TimeZoneInfo local;
79 public static TimeZoneInfo Local {
84 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
87 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
89 throw new TimeZoneNotFoundException ();
93 throw new TimeZoneNotFoundException ();
100 string standardDisplayName;
101 public string StandardName {
102 get { return standardDisplayName; }
105 bool disableDaylightSavingTime;
106 public bool SupportsDaylightSavingTime {
107 get { return !disableDaylightSavingTime; }
110 static TimeZoneInfo utc;
111 public static TimeZoneInfo Utc {
114 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
119 static string timeZoneDirectory = null;
120 static string TimeZoneDirectory {
122 if (timeZoneDirectory == null)
123 timeZoneDirectory = "/usr/share/zoneinfo";
124 return timeZoneDirectory;
128 timeZoneDirectory = value;
132 private AdjustmentRule [] adjustmentRules;
134 public static void ClearCachedData ()
138 systemTimeZones = null;
141 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
143 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
146 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
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");
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");
154 if (sourceTimeZone.IsInvalidTime (dateTime))
155 throw new ArgumentException ("dateTime parameter is an invalid time");
157 if (sourceTimeZone == null)
158 throw new ArgumentNullException ("sourceTimeZone");
160 if (destinationTimeZone == null)
161 throw new ArgumentNullException ("destinationTimeZone");
163 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
166 DateTime utc = ConvertTimeToUtc (dateTime);
168 if (destinationTimeZone == TimeZoneInfo.Utc)
171 return ConvertTimeFromUtc (utc, destinationTimeZone);
175 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
177 throw new NotImplementedException ();
180 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
182 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
185 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
187 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
190 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
192 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
195 private DateTime ConvertTimeFromUtc (DateTime dateTime)
197 if (dateTime.Kind == DateTimeKind.Local)
198 throw new ArgumentException ("Kind property of dateTime is Local");
200 if (this == TimeZoneInfo.Utc)
201 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
203 //FIXME: do not rely on DateTime implementation !
204 if (this == TimeZoneInfo.Local)
205 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
207 AdjustmentRule rule = GetApplicableRule (dateTime);
209 if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
210 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
212 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
215 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
217 if (destinationTimeZone == null)
218 throw new ArgumentNullException ("destinationTimeZone");
220 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
223 public static DateTime ConvertTimeToUtc (DateTime dateTime)
225 if (dateTime.Kind == DateTimeKind.Utc)
228 //FIXME: do not rely on DateTime implementation !
229 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
232 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
234 if (sourceTimeZone == null)
235 throw new ArgumentNullException ("sourceTimeZone");
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");
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");
243 if (sourceTimeZone.IsInvalidTime (dateTime))
244 throw new ArgumentException ("dateTime parameter is an invalid time");
246 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
249 if (dateTime.Kind == DateTimeKind.Utc)
252 if (dateTime.Kind == DateTimeKind.Local)
253 return ConvertTimeToUtc (dateTime);
255 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
256 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
258 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
259 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
263 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
265 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
268 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
270 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
273 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
275 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
278 public bool Equals (TimeZoneInfo other)
283 return other.Id == this.Id && HasSameRules (other);
286 public static TimeZoneInfo FindSystemTimeZoneById (string id)
288 //FIXME: this method should check for cached values in systemTimeZones
290 throw new ArgumentNullException ("id");
292 string filepath = Path.Combine (TimeZoneDirectory, id);
293 return FindSystemTimeZoneByFileName (id, filepath);
295 throw new NotImplementedException ();
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)
303 if (!File.Exists (filepath))
304 throw new TimeZoneNotFoundException ();
306 byte [] buffer = new byte [BUFFER_SIZE];
308 using (FileStream stream = File.OpenRead (filepath)) {
309 length = stream.Read (buffer, 0, BUFFER_SIZE);
312 if (!ValidTZFile (buffer, length))
313 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
316 return ParseTZBuffer (id, buffer, length);
317 } catch (Exception e) {
318 throw new InvalidTimeZoneException (e.Message);
323 public static TimeZoneInfo FromSerializedString (string source)
325 throw new NotImplementedException ();
328 public AdjustmentRule [] GetAdjustmentRules ()
330 if (disableDaylightSavingTime)
331 return new AdjustmentRule [0];
333 return (AdjustmentRule []) adjustmentRules.Clone ();
336 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
338 if (!IsAmbiguousTime (dateTime))
339 throw new ArgumentException ("dateTime is not an ambiguous time");
341 AdjustmentRule rule = GetApplicableRule (dateTime);
342 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
345 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
347 if (!IsAmbiguousTime (dateTimeOffset))
348 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
350 throw new NotImplementedException ();
353 public override int GetHashCode ()
355 int hash_code = Id.GetHashCode ();
356 foreach (AdjustmentRule rule in GetAdjustmentRules ())
357 hash_code ^= rule.GetHashCode ();
361 public void GetObjectData (SerializationInfo info, StreamingContext context)
363 throw new NotImplementedException ();
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 ()
370 if (systemTimeZones == null) {
371 systemTimeZones = new List<TimeZoneInfo> ();
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) {
376 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
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) {
390 throw new NotImplementedException ("This method is not implemented for this platform");
393 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
396 public TimeSpan GetUtcOffset (DateTime dateTime)
398 if (IsDaylightSavingTime (dateTime)) {
399 AdjustmentRule rule = GetApplicableRule (dateTime);
400 return BaseUtcOffset + rule.DaylightDelta;
403 return BaseUtcOffset;
406 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
408 throw new NotImplementedException ();
411 public bool HasSameRules (TimeZoneInfo other)
414 throw new ArgumentNullException ("other");
416 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
419 if (this.adjustmentRules == null)
422 if (this.BaseUtcOffset != other.BaseUtcOffset)
425 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
428 for (int i = 0; i < adjustmentRules.Length; i++) {
429 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
436 public bool IsAmbiguousTime (DateTime dateTime)
438 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
439 throw new ArgumentException ("Kind is Local and time is Invalid");
441 if (this == TimeZoneInfo.Utc)
444 if (dateTime.Kind == DateTimeKind.Utc)
445 dateTime = ConvertTimeFromUtc (dateTime);
447 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
448 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
450 AdjustmentRule rule = GetApplicableRule (dateTime);
451 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
452 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
458 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
460 throw new NotImplementedException ();
463 public bool IsDaylightSavingTime (DateTime dateTime)
465 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
466 throw new ArgumentException ("dateTime is invalid and Kind is Local");
468 if (this == TimeZoneInfo.Utc)
471 if (!SupportsDaylightSavingTime)
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 ();
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));
481 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
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);
492 return (dateTime >= DST_start && dateTime < DST_end);
495 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
497 throw new NotImplementedException ();
500 public bool IsInvalidTime (DateTime dateTime)
502 if (dateTime.Kind == DateTimeKind.Utc)
504 if (dateTime.Kind == DateTimeKind.Local && this != Local)
507 AdjustmentRule rule = GetApplicableRule (dateTime);
508 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
509 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
515 public void OnDeserialization (object sender)
517 throw new NotImplementedException ();
520 public string ToSerializedString ()
522 throw new NotImplementedException ();
525 public override string ToString ()
530 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
533 throw new ArgumentNullException ("id");
535 if (id == String.Empty)
536 throw new ArgumentException ("id parameter is an empty string");
538 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
539 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
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");
546 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
549 if (adjustmentRules != null && adjustmentRules.Length != 0) {
550 AdjustmentRule prev = null;
551 foreach (AdjustmentRule current in adjustmentRules) {
553 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
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;");
559 if (prev != null && prev.DateStart > current.DateStart)
560 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
562 if (prev != null && prev.DateEnd > current.DateStart)
563 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
565 if (prev != null && prev.DateEnd == current.DateStart)
566 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
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;
581 private AdjustmentRule GetApplicableRule (DateTime dateTime)
583 //Transitions are always in standard time
584 DateTime date = dateTime;
586 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
587 date = date.ToUniversalTime () + BaseUtcOffset;
589 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
590 date = date + BaseUtcOffset;
592 foreach (AdjustmentRule rule in adjustmentRules) {
593 if (rule.DateStart > date.Date)
595 if (rule.DateEnd < date.Date)
602 private static DateTime TransitionPoint (TransitionTime transition, int year)
604 if (transition.IsFixedDateRule)
605 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
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))
611 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
615 private static bool ValidTZFile (byte [] buffer, int length)
617 StringBuilder magic = new StringBuilder ();
619 for (int i = 0; i < 4; i++)
620 magic.Append ((char)buffer [i]);
622 if (magic.ToString () != "TZif")
625 if (length >= BUFFER_SIZE)
633 public readonly int Offset;
634 public readonly bool IsDst;
637 public TimeType (int offset, bool is_dst, string abbrev)
639 this.Offset = offset;
644 public override string ToString ()
646 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
650 static int SwapInt32 (int i)
652 return (((i >> 24) & 0xff)
653 | ((i >> 8) & 0xff00)
654 | ((i << 8) & 0xff0000)
658 static int ReadBigEndianInt32 (byte [] buffer, int start)
660 int i = BitConverter.ToInt32 (buffer, start);
661 if (!BitConverter.IsLittleEndian)
664 return SwapInt32 (i);
667 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
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);
677 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
678 throw new InvalidTimeZoneException ();
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);
684 if (time_types.Count == 0)
685 throw new InvalidTimeZoneException ();
687 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
688 throw new InvalidTimeZoneException ();
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> ();
698 for (int i = 0; i < transitions.Count; i++) {
699 var pair = transitions [i];
700 DateTime ttime = pair.Key;
701 TimeType ttype = pair.Value;
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;
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;
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);
720 DateTime dateStart, dateEnd;
721 if (dst_start.Month < 7)
722 dateStart = new DateTime (dst_start.Year, 1, 1);
724 dateStart = new DateTime (dst_start.Year, 7, 1);
726 if (dst_end.Month >= 7)
727 dateEnd = new DateTime (dst_end.Year, 12, 31);
729 dateEnd = new DateTime (dst_end.Year, 6, 30);
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));
737 dst_observed = false;
739 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
740 daylightDisplayName = ttype.Name;
741 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
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);
754 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
756 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
760 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
762 AdjustmentRule prev = null;
763 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
764 if (prev != null && prev.DateEnd > current.DateStart) {
765 adjustmentRules.Remove (current);
769 return adjustmentRules;
772 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
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];
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 ();
793 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
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]));
805 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
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]));
817 static DateTime DateTimeFromUnixTime (long unix_time)
819 DateTime date_time = new DateTime (1970, 1, 1);
820 return date_time.AddSeconds (unix_time);