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 || (MONOTOUCH && !INSIDE_CORLIB) || (MOONLIGHT && INSIDE_CORLIB)
36 using System.Collections.Generic;
37 using System.Collections.ObjectModel;
38 using System.Runtime.Serialization;
48 #if NET_4_0 || BOOTSTRAP_NET_4_0
49 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
51 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
53 [SerializableAttribute]
54 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
56 TimeSpan baseUtcOffset;
57 public TimeSpan BaseUtcOffset {
58 get { return baseUtcOffset; }
61 string daylightDisplayName;
62 public string DaylightName {
64 if (disableDaylightSavingTime)
66 return daylightDisplayName;
71 public string DisplayName {
72 get { return displayName; }
80 static TimeZoneInfo local;
81 public static TimeZoneInfo Local {
86 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
89 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
91 throw new TimeZoneNotFoundException ();
95 throw new TimeZoneNotFoundException ();
102 string standardDisplayName;
103 public string StandardName {
104 get { return standardDisplayName; }
107 bool disableDaylightSavingTime;
108 public bool SupportsDaylightSavingTime {
109 get { return !disableDaylightSavingTime; }
112 static TimeZoneInfo utc;
113 public static TimeZoneInfo Utc {
116 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
121 static string timeZoneDirectory = null;
122 static string TimeZoneDirectory {
124 if (timeZoneDirectory == null)
125 timeZoneDirectory = "/usr/share/zoneinfo";
126 return timeZoneDirectory;
130 timeZoneDirectory = value;
134 private AdjustmentRule [] adjustmentRules;
136 public static void ClearCachedData ()
140 systemTimeZones = null;
143 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
145 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
148 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
150 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
151 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
153 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
154 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
156 if (sourceTimeZone.IsInvalidTime (dateTime))
157 throw new ArgumentException ("dateTime parameter is an invalid time");
159 if (sourceTimeZone == null)
160 throw new ArgumentNullException ("sourceTimeZone");
162 if (destinationTimeZone == null)
163 throw new ArgumentNullException ("destinationTimeZone");
165 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
168 DateTime utc = ConvertTimeToUtc (dateTime);
170 if (destinationTimeZone == TimeZoneInfo.Utc)
173 return ConvertTimeFromUtc (utc, destinationTimeZone);
177 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
179 throw new NotImplementedException ();
182 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
184 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
187 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
189 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
192 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
194 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
197 private DateTime ConvertTimeFromUtc (DateTime dateTime)
199 if (dateTime.Kind == DateTimeKind.Local)
200 throw new ArgumentException ("Kind property of dateTime is Local");
202 if (this == TimeZoneInfo.Utc)
203 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
205 //FIXME: do not rely on DateTime implementation !
206 if (this == TimeZoneInfo.Local)
207 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
209 AdjustmentRule rule = GetApplicableRule (dateTime);
211 if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
212 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
214 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
217 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
219 if (destinationTimeZone == null)
220 throw new ArgumentNullException ("destinationTimeZone");
222 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
225 public static DateTime ConvertTimeToUtc (DateTime dateTime)
227 if (dateTime.Kind == DateTimeKind.Utc)
230 //FIXME: do not rely on DateTime implementation !
231 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
234 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
236 if (sourceTimeZone == null)
237 throw new ArgumentNullException ("sourceTimeZone");
239 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
240 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
242 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
243 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
245 if (sourceTimeZone.IsInvalidTime (dateTime))
246 throw new ArgumentException ("dateTime parameter is an invalid time");
248 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
251 if (dateTime.Kind == DateTimeKind.Utc)
254 if (dateTime.Kind == DateTimeKind.Local)
255 return ConvertTimeToUtc (dateTime);
257 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
258 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
260 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
261 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
265 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
267 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
270 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
272 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
275 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
277 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
280 public bool Equals (TimeZoneInfo other)
285 return other.Id == this.Id && HasSameRules (other);
288 public static TimeZoneInfo FindSystemTimeZoneById (string id)
290 //FIXME: this method should check for cached values in systemTimeZones
292 throw new ArgumentNullException ("id");
294 string filepath = Path.Combine (TimeZoneDirectory, id);
295 return FindSystemTimeZoneByFileName (id, filepath);
297 throw new NotImplementedException ();
302 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
303 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
305 if (!File.Exists (filepath))
306 throw new TimeZoneNotFoundException ();
308 byte [] buffer = new byte [BUFFER_SIZE];
310 using (FileStream stream = File.OpenRead (filepath)) {
311 length = stream.Read (buffer, 0, BUFFER_SIZE);
314 if (!ValidTZFile (buffer, length))
315 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
318 return ParseTZBuffer (id, buffer, length);
319 } catch (Exception e) {
320 throw new InvalidTimeZoneException (e.Message);
325 public static TimeZoneInfo FromSerializedString (string source)
327 throw new NotImplementedException ();
330 public AdjustmentRule [] GetAdjustmentRules ()
332 if (disableDaylightSavingTime)
333 return new AdjustmentRule [0];
335 return (AdjustmentRule []) adjustmentRules.Clone ();
338 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
340 if (!IsAmbiguousTime (dateTime))
341 throw new ArgumentException ("dateTime is not an ambiguous time");
343 AdjustmentRule rule = GetApplicableRule (dateTime);
344 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
347 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
349 if (!IsAmbiguousTime (dateTimeOffset))
350 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
352 throw new NotImplementedException ();
355 public override int GetHashCode ()
357 int hash_code = Id.GetHashCode ();
358 foreach (AdjustmentRule rule in GetAdjustmentRules ())
359 hash_code ^= rule.GetHashCode ();
364 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
366 public void GetObjectData (SerializationInfo info, StreamingContext context)
369 throw new NotImplementedException ();
372 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
373 private static List<TimeZoneInfo> systemTimeZones = null;
374 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
376 if (systemTimeZones == null) {
377 systemTimeZones = new List<TimeZoneInfo> ();
379 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
380 foreach (string continent in continents) {
382 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
384 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
385 systemTimeZones.Add (FindSystemTimeZoneById (id));
386 } catch (ArgumentNullException) {
387 } catch (TimeZoneNotFoundException) {
388 } catch (InvalidTimeZoneException) {
389 } catch (Exception) {
396 throw new NotImplementedException ("This method is not implemented for this platform");
399 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
402 public TimeSpan GetUtcOffset (DateTime dateTime)
404 if (IsDaylightSavingTime (dateTime)) {
405 AdjustmentRule rule = GetApplicableRule (dateTime);
406 return BaseUtcOffset + rule.DaylightDelta;
409 return BaseUtcOffset;
412 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
414 throw new NotImplementedException ();
417 public bool HasSameRules (TimeZoneInfo other)
420 throw new ArgumentNullException ("other");
422 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
425 if (this.adjustmentRules == null)
428 if (this.BaseUtcOffset != other.BaseUtcOffset)
431 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
434 for (int i = 0; i < adjustmentRules.Length; i++) {
435 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
442 public bool IsAmbiguousTime (DateTime dateTime)
444 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
445 throw new ArgumentException ("Kind is Local and time is Invalid");
447 if (this == TimeZoneInfo.Utc)
450 if (dateTime.Kind == DateTimeKind.Utc)
451 dateTime = ConvertTimeFromUtc (dateTime);
453 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
454 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
456 AdjustmentRule rule = GetApplicableRule (dateTime);
457 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
458 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
464 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
466 throw new NotImplementedException ();
469 public bool IsDaylightSavingTime (DateTime dateTime)
471 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
472 throw new ArgumentException ("dateTime is invalid and Kind is Local");
474 if (this == TimeZoneInfo.Utc)
477 if (!SupportsDaylightSavingTime)
479 //FIXME: do not rely on DateTime implementation !
480 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
481 return dateTime.IsDaylightSavingTime ();
483 //FIXME: do not rely on DateTime implementation !
484 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
485 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
487 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
491 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
492 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
493 if (dateTime.Kind == DateTimeKind.Utc) {
494 DST_start -= BaseUtcOffset;
495 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
498 return (dateTime >= DST_start && dateTime < DST_end);
501 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
503 throw new NotImplementedException ();
506 public bool IsInvalidTime (DateTime dateTime)
508 if (dateTime.Kind == DateTimeKind.Utc)
510 if (dateTime.Kind == DateTimeKind.Local && this != Local)
513 AdjustmentRule rule = GetApplicableRule (dateTime);
514 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
515 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
522 void IDeserializationCallback.OnDeserialization (object sender)
524 public void OnDeserialization (object sender)
527 throw new NotImplementedException ();
530 public string ToSerializedString ()
532 throw new NotImplementedException ();
535 public override string ToString ()
540 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
543 throw new ArgumentNullException ("id");
545 if (id == String.Empty)
546 throw new ArgumentException ("id parameter is an empty string");
548 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
549 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
551 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
552 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
556 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
559 if (adjustmentRules != null && adjustmentRules.Length != 0) {
560 AdjustmentRule prev = null;
561 foreach (AdjustmentRule current in adjustmentRules) {
563 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
565 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
566 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
567 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;");
569 if (prev != null && prev.DateStart > current.DateStart)
570 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
572 if (prev != null && prev.DateEnd > current.DateStart)
573 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
575 if (prev != null && prev.DateEnd == current.DateStart)
576 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
583 this.baseUtcOffset = baseUtcOffset;
584 this.displayName = displayName ?? id;
585 this.standardDisplayName = standardDisplayName ?? id;
586 this.daylightDisplayName = daylightDisplayName;
587 this.disableDaylightSavingTime = disableDaylightSavingTime;
588 this.adjustmentRules = adjustmentRules;
591 private AdjustmentRule GetApplicableRule (DateTime dateTime)
593 //Transitions are always in standard time
594 DateTime date = dateTime;
596 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
597 date = date.ToUniversalTime () + BaseUtcOffset;
599 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
600 date = date + BaseUtcOffset;
602 foreach (AdjustmentRule rule in adjustmentRules) {
603 if (rule.DateStart > date.Date)
605 if (rule.DateEnd < date.Date)
612 private static DateTime TransitionPoint (TransitionTime transition, int year)
614 if (transition.IsFixedDateRule)
615 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
617 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
618 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
619 if (day > DateTime.DaysInMonth (year, transition.Month))
621 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
625 private static bool ValidTZFile (byte [] buffer, int length)
627 StringBuilder magic = new StringBuilder ();
629 for (int i = 0; i < 4; i++)
630 magic.Append ((char)buffer [i]);
632 if (magic.ToString () != "TZif")
635 if (length >= BUFFER_SIZE)
643 public readonly int Offset;
644 public readonly bool IsDst;
647 public TimeType (int offset, bool is_dst, string abbrev)
649 this.Offset = offset;
654 public override string ToString ()
656 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
660 static int SwapInt32 (int i)
662 return (((i >> 24) & 0xff)
663 | ((i >> 8) & 0xff00)
664 | ((i << 8) & 0xff0000)
668 static int ReadBigEndianInt32 (byte [] buffer, int start)
670 int i = BitConverter.ToInt32 (buffer, start);
671 if (!BitConverter.IsLittleEndian)
674 return SwapInt32 (i);
677 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
679 //Reading the header. 4 bytes for magic, 16 are reserved
680 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
681 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
682 int leapcnt = ReadBigEndianInt32 (buffer, 28);
683 int timecnt = ReadBigEndianInt32 (buffer, 32);
684 int typecnt = ReadBigEndianInt32 (buffer, 36);
685 int charcnt = ReadBigEndianInt32 (buffer, 40);
687 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
688 throw new InvalidTimeZoneException ();
690 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
691 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
692 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
694 if (time_types.Count == 0)
695 throw new InvalidTimeZoneException ();
697 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
698 throw new InvalidTimeZoneException ();
700 TimeSpan baseUtcOffset = new TimeSpan (0);
701 TimeSpan dstDelta = new TimeSpan (0);
702 string standardDisplayName = null;
703 string daylightDisplayName = null;
704 bool dst_observed = false;
705 DateTime dst_start = DateTime.MinValue;
706 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
708 for (int i = 0; i < transitions.Count; i++) {
709 var pair = transitions [i];
710 DateTime ttime = pair.Key;
711 TimeType ttype = pair.Value;
713 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
714 standardDisplayName = ttype.Name;
715 daylightDisplayName = null;
716 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
717 adjustmentRules = new List<AdjustmentRule> ();
718 dst_observed = false;
721 //FIXME: check additional fields for this:
722 //most of the transitions are expressed in GMT
723 dst_start += baseUtcOffset;
724 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
726 //some weird timezone (America/Phoenix) have end dates on Jan 1st
727 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
728 dst_end -= new TimeSpan (24, 0, 0);
730 DateTime dateStart, dateEnd;
731 if (dst_start.Month < 7)
732 dateStart = new DateTime (dst_start.Year, 1, 1);
734 dateStart = new DateTime (dst_start.Year, 7, 1);
736 if (dst_end.Month >= 7)
737 dateEnd = new DateTime (dst_end.Year, 12, 31);
739 dateEnd = new DateTime (dst_end.Year, 6, 30);
742 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
743 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
744 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
745 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
747 dst_observed = false;
749 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
750 daylightDisplayName = ttype.Name;
751 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
758 if (adjustmentRules.Count == 0) {
759 TimeType t = (TimeType)time_types [0];
760 if (standardDisplayName == null) {
761 standardDisplayName = t.Name;
762 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
764 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
766 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
770 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
772 AdjustmentRule prev = null;
773 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
774 if (prev != null && prev.DateEnd > current.DateStart) {
775 adjustmentRules.Remove (current);
779 return adjustmentRules;
782 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
784 var abbrevs = new Dictionary<int, string> ();
785 int abbrev_index = 0;
786 var sb = new StringBuilder ();
787 for (int i = 0; i < count; i++) {
788 char c = (char) buffer [index + i];
792 abbrevs.Add (abbrev_index, sb.ToString ());
793 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
794 for (int j = 1; j < sb.Length; j++)
795 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
796 abbrev_index = i + 1;
797 sb = new StringBuilder ();
803 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
805 var types = new Dictionary<int, TimeType> (count);
806 for (int i = 0; i < count; i++) {
807 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
808 byte is_dst = buffer [index + 6 * i + 4];
809 byte abbrev = buffer [index + 6 * i + 5];
810 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
815 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
817 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
818 for (int i = 0; i < count; i++) {
819 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
820 DateTime ttime = DateTimeFromUnixTime (unixtime);
821 byte ttype = buffer [index + 4 * count + i];
822 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
827 static DateTime DateTimeFromUnixTime (long unix_time)
829 DateTime date_time = new DateTime (1970, 1, 1);
830 return date_time.AddSeconds (unix_time);