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 ();
362 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
364 public void GetObjectData (SerializationInfo info, StreamingContext context)
367 throw new NotImplementedException ();
370 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
371 private static List<TimeZoneInfo> systemTimeZones = null;
372 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
374 if (systemTimeZones == null) {
375 systemTimeZones = new List<TimeZoneInfo> ();
377 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
378 foreach (string continent in continents) {
380 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
382 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
383 systemTimeZones.Add (FindSystemTimeZoneById (id));
384 } catch (ArgumentNullException) {
385 } catch (TimeZoneNotFoundException) {
386 } catch (InvalidTimeZoneException) {
387 } catch (Exception) {
394 throw new NotImplementedException ("This method is not implemented for this platform");
397 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
400 public TimeSpan GetUtcOffset (DateTime dateTime)
402 if (IsDaylightSavingTime (dateTime)) {
403 AdjustmentRule rule = GetApplicableRule (dateTime);
404 return BaseUtcOffset + rule.DaylightDelta;
407 return BaseUtcOffset;
410 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
412 throw new NotImplementedException ();
415 public bool HasSameRules (TimeZoneInfo other)
418 throw new ArgumentNullException ("other");
420 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
423 if (this.adjustmentRules == null)
426 if (this.BaseUtcOffset != other.BaseUtcOffset)
429 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
432 for (int i = 0; i < adjustmentRules.Length; i++) {
433 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
440 public bool IsAmbiguousTime (DateTime dateTime)
442 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
443 throw new ArgumentException ("Kind is Local and time is Invalid");
445 if (this == TimeZoneInfo.Utc)
448 if (dateTime.Kind == DateTimeKind.Utc)
449 dateTime = ConvertTimeFromUtc (dateTime);
451 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
452 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
454 AdjustmentRule rule = GetApplicableRule (dateTime);
455 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
456 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
462 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
464 throw new NotImplementedException ();
467 public bool IsDaylightSavingTime (DateTime dateTime)
469 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
470 throw new ArgumentException ("dateTime is invalid and Kind is Local");
472 if (this == TimeZoneInfo.Utc)
475 if (!SupportsDaylightSavingTime)
477 //FIXME: do not rely on DateTime implementation !
478 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
479 return dateTime.IsDaylightSavingTime ();
481 //FIXME: do not rely on DateTime implementation !
482 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
483 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
485 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
489 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
490 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
491 if (dateTime.Kind == DateTimeKind.Utc) {
492 DST_start -= BaseUtcOffset;
493 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
496 return (dateTime >= DST_start && dateTime < DST_end);
499 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
501 throw new NotImplementedException ();
504 public bool IsInvalidTime (DateTime dateTime)
506 if (dateTime.Kind == DateTimeKind.Utc)
508 if (dateTime.Kind == DateTimeKind.Local && this != Local)
511 AdjustmentRule rule = GetApplicableRule (dateTime);
512 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
513 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
520 void IDeserializationCallback.OnDeserialization (object sender)
522 public void OnDeserialization (object sender)
525 throw new NotImplementedException ();
528 public string ToSerializedString ()
530 throw new NotImplementedException ();
533 public override string ToString ()
538 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
541 throw new ArgumentNullException ("id");
543 if (id == String.Empty)
544 throw new ArgumentException ("id parameter is an empty string");
546 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
547 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
549 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
550 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
554 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
557 if (adjustmentRules != null && adjustmentRules.Length != 0) {
558 AdjustmentRule prev = null;
559 foreach (AdjustmentRule current in adjustmentRules) {
561 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
563 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
564 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
565 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;");
567 if (prev != null && prev.DateStart > current.DateStart)
568 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
570 if (prev != null && prev.DateEnd > current.DateStart)
571 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
573 if (prev != null && prev.DateEnd == current.DateStart)
574 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
581 this.baseUtcOffset = baseUtcOffset;
582 this.displayName = displayName ?? id;
583 this.standardDisplayName = standardDisplayName ?? id;
584 this.daylightDisplayName = daylightDisplayName;
585 this.disableDaylightSavingTime = disableDaylightSavingTime;
586 this.adjustmentRules = adjustmentRules;
589 private AdjustmentRule GetApplicableRule (DateTime dateTime)
591 //Transitions are always in standard time
592 DateTime date = dateTime;
594 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
595 date = date.ToUniversalTime () + BaseUtcOffset;
597 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
598 date = date + BaseUtcOffset;
600 foreach (AdjustmentRule rule in adjustmentRules) {
601 if (rule.DateStart > date.Date)
603 if (rule.DateEnd < date.Date)
610 private static DateTime TransitionPoint (TransitionTime transition, int year)
612 if (transition.IsFixedDateRule)
613 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
615 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
616 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
617 if (day > DateTime.DaysInMonth (year, transition.Month))
619 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
623 private static bool ValidTZFile (byte [] buffer, int length)
625 StringBuilder magic = new StringBuilder ();
627 for (int i = 0; i < 4; i++)
628 magic.Append ((char)buffer [i]);
630 if (magic.ToString () != "TZif")
633 if (length >= BUFFER_SIZE)
641 public readonly int Offset;
642 public readonly bool IsDst;
645 public TimeType (int offset, bool is_dst, string abbrev)
647 this.Offset = offset;
652 public override string ToString ()
654 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
658 static int SwapInt32 (int i)
660 return (((i >> 24) & 0xff)
661 | ((i >> 8) & 0xff00)
662 | ((i << 8) & 0xff0000)
666 static int ReadBigEndianInt32 (byte [] buffer, int start)
668 int i = BitConverter.ToInt32 (buffer, start);
669 if (!BitConverter.IsLittleEndian)
672 return SwapInt32 (i);
675 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
677 //Reading the header. 4 bytes for magic, 16 are reserved
678 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
679 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
680 int leapcnt = ReadBigEndianInt32 (buffer, 28);
681 int timecnt = ReadBigEndianInt32 (buffer, 32);
682 int typecnt = ReadBigEndianInt32 (buffer, 36);
683 int charcnt = ReadBigEndianInt32 (buffer, 40);
685 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
686 throw new InvalidTimeZoneException ();
688 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
689 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
690 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
692 if (time_types.Count == 0)
693 throw new InvalidTimeZoneException ();
695 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
696 throw new InvalidTimeZoneException ();
698 TimeSpan baseUtcOffset = new TimeSpan (0);
699 TimeSpan dstDelta = new TimeSpan (0);
700 string standardDisplayName = null;
701 string daylightDisplayName = null;
702 bool dst_observed = false;
703 DateTime dst_start = DateTime.MinValue;
704 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
706 for (int i = 0; i < transitions.Count; i++) {
707 var pair = transitions [i];
708 DateTime ttime = pair.Key;
709 TimeType ttype = pair.Value;
711 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
712 standardDisplayName = ttype.Name;
713 daylightDisplayName = null;
714 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
715 adjustmentRules = new List<AdjustmentRule> ();
716 dst_observed = false;
719 //FIXME: check additional fields for this:
720 //most of the transitions are expressed in GMT
721 dst_start += baseUtcOffset;
722 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
724 //some weird timezone (America/Phoenix) have end dates on Jan 1st
725 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
726 dst_end -= new TimeSpan (24, 0, 0);
728 DateTime dateStart, dateEnd;
729 if (dst_start.Month < 7)
730 dateStart = new DateTime (dst_start.Year, 1, 1);
732 dateStart = new DateTime (dst_start.Year, 7, 1);
734 if (dst_end.Month >= 7)
735 dateEnd = new DateTime (dst_end.Year, 12, 31);
737 dateEnd = new DateTime (dst_end.Year, 6, 30);
740 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
741 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
742 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
743 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
745 dst_observed = false;
747 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
748 daylightDisplayName = ttype.Name;
749 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
756 if (adjustmentRules.Count == 0) {
757 TimeType t = (TimeType)time_types [0];
758 if (standardDisplayName == null) {
759 standardDisplayName = t.Name;
760 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
762 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
764 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
768 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
770 AdjustmentRule prev = null;
771 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
772 if (prev != null && prev.DateEnd > current.DateStart) {
773 adjustmentRules.Remove (current);
777 return adjustmentRules;
780 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
782 var abbrevs = new Dictionary<int, string> ();
783 int abbrev_index = 0;
784 var sb = new StringBuilder ();
785 for (int i = 0; i < count; i++) {
786 char c = (char) buffer [index + i];
790 abbrevs.Add (abbrev_index, sb.ToString ());
791 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
792 for (int j = 1; j < sb.Length; j++)
793 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
794 abbrev_index = i + 1;
795 sb = new StringBuilder ();
801 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
803 var types = new Dictionary<int, TimeType> (count);
804 for (int i = 0; i < count; i++) {
805 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
806 byte is_dst = buffer [index + 6 * i + 4];
807 byte abbrev = buffer [index + 6 * i + 5];
808 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
813 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
815 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
816 for (int i = 0; i < count; i++) {
817 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
818 DateTime ttime = DateTimeFromUnixTime (unixtime);
819 byte ttype = buffer [index + 4 * count + i];
820 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
825 static DateTime DateTimeFromUnixTime (long unix_time)
827 DateTime date_time = new DateTime (1970, 1, 1);
828 return date_time.AddSeconds (unix_time);