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.Collections.Generic;
29 using System.Collections.ObjectModel;
30 using System.Runtime.Serialization;
40 [SerializableAttribute]
41 public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
43 TimeSpan baseUtcOffset;
44 public TimeSpan BaseUtcOffset {
45 get { return baseUtcOffset; }
48 string daylightDisplayName;
49 public string DaylightName {
51 if (disableDaylightSavingTime)
53 return daylightDisplayName;
58 public string DisplayName {
59 get { return displayName; }
67 static TimeZoneInfo local;
68 public static TimeZoneInfo Local {
73 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
76 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
78 throw new TimeZoneNotFoundException ();
82 throw new TimeZoneNotFoundException ();
89 string standardDisplayName;
90 public string StandardName {
91 get { return standardDisplayName; }
94 bool disableDaylightSavingTime;
95 public bool SupportsDaylightSavingTime {
96 get { return !disableDaylightSavingTime; }
99 static TimeZoneInfo utc;
100 public static TimeZoneInfo Utc {
103 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
108 static string timeZoneDirectory = null;
109 public static string TimeZoneDirectory {
111 if (timeZoneDirectory == null)
112 timeZoneDirectory = "/usr/share/zoneinfo";
113 return timeZoneDirectory;
117 timeZoneDirectory = value;
121 private AdjustmentRule [] adjustmentRules;
123 public static void ClearCachedData ()
127 systemTimeZones = null;
130 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
132 return ConvertTime (dateTime, TimeZoneInfo.Local, destinationTimeZone);
135 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
137 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
138 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
140 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
141 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
143 if (sourceTimeZone.IsInvalidTime (dateTime))
144 throw new ArgumentException ("dateTime parameter is an invalid time");
146 if (sourceTimeZone == null)
147 throw new ArgumentNullException ("sourceTimeZone");
149 if (destinationTimeZone == null)
150 throw new ArgumentNullException ("destinationTimeZone");
152 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
155 DateTime utc = ConvertTimeToUtc (dateTime);
157 if (destinationTimeZone == TimeZoneInfo.Utc)
160 return ConvertTimeFromUtc (utc, destinationTimeZone);
164 public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
166 throw new NotImplementedException ();
169 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
171 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
174 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
176 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
179 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
181 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
184 private DateTime ConvertTimeFromUtc (DateTime dateTime)
186 if (dateTime.Kind == DateTimeKind.Local)
187 throw new ArgumentException ("Kind property of dateTime is Local");
189 if (this == TimeZoneInfo.Utc)
190 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
192 //FIXME: do not rely on DateTime implementation !
193 if (this == TimeZoneInfo.Local)
194 return DateTime.SpecifyKind (dateTime.ToLocalTime (), DateTimeKind.Unspecified);
196 AdjustmentRule rule = GetApplicableRule (dateTime);
198 if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
199 return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
201 return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
204 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
206 if (destinationTimeZone == null)
207 throw new ArgumentNullException ("destinationTimeZone");
209 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
212 public static DateTime ConvertTimeToUtc (DateTime dateTime)
214 if (dateTime.Kind == DateTimeKind.Utc)
217 //FIXME: do not rely on DateTime implementation !
218 return DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc);
221 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
223 if (sourceTimeZone == null)
224 throw new ArgumentNullException ("sourceTimeZone");
226 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
227 throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
229 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
230 throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
232 if (sourceTimeZone.IsInvalidTime (dateTime))
233 throw new ArgumentException ("dateTime parameter is an invalid time");
235 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone == TimeZoneInfo.Utc)
238 if (dateTime.Kind == DateTimeKind.Utc)
241 if (dateTime.Kind == DateTimeKind.Local)
242 return ConvertTimeToUtc (dateTime);
244 if (sourceTimeZone.IsAmbiguousTime (dateTime) || !sourceTimeZone.IsDaylightSavingTime (dateTime))
245 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
247 AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
248 return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
252 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
254 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
257 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
259 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
262 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
264 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
267 public bool Equals (TimeZoneInfo other)
272 return other.Id == this.Id && HasSameRules (other);
275 public static TimeZoneInfo FindSystemTimeZoneById (string id)
277 //FIXME: this method should check for cached values in systemTimeZones
279 throw new ArgumentNullException ("id");
281 string filepath = Path.Combine (TimeZoneDirectory, id);
282 return FindSystemTimeZoneByFileName (id, filepath);
284 throw new NotImplementedException ();
289 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
290 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
292 if (!File.Exists (filepath))
293 throw new TimeZoneNotFoundException ();
295 byte [] buffer = new byte [BUFFER_SIZE];
297 using (FileStream stream = File.OpenRead (filepath)) {
298 length = stream.Read (buffer, 0, BUFFER_SIZE);
301 if (!ValidTZFile (buffer, length))
302 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
305 return ParseTZBuffer (id, buffer, length);
306 } catch (Exception e) {
307 throw new InvalidTimeZoneException (e.Message);
312 public static TimeZoneInfo FromSerializedString (string source)
314 throw new NotImplementedException ();
317 public AdjustmentRule [] GetAdjustmentRules ()
319 if (disableDaylightSavingTime)
320 return new AdjustmentRule [0];
322 return (AdjustmentRule []) adjustmentRules.Clone ();
325 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
327 if (!IsAmbiguousTime (dateTime))
328 throw new ArgumentException ("dateTime is not an ambiguous time");
330 AdjustmentRule rule = GetApplicableRule (dateTime);
331 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
334 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
336 if (!IsAmbiguousTime (dateTimeOffset))
337 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
339 throw new NotImplementedException ();
342 public override int GetHashCode ()
344 int hash_code = Id.GetHashCode ();
345 foreach (AdjustmentRule rule in GetAdjustmentRules ())
346 hash_code ^= rule.GetHashCode ();
350 public void GetObjectData (SerializationInfo info, StreamingContext context)
352 throw new NotImplementedException ();
355 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
356 private static List<TimeZoneInfo> systemTimeZones = null;
357 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
359 if (systemTimeZones == null) {
360 systemTimeZones = new List<TimeZoneInfo> ();
362 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
363 foreach (string continent in continents) {
365 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
367 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
368 systemTimeZones.Add (FindSystemTimeZoneById (id));
369 } catch (ArgumentNullException) {
370 } catch (TimeZoneNotFoundException) {
371 } catch (InvalidTimeZoneException) {
372 } catch (Exception) {
379 throw new NotImplementedException ("This method is not implemented for this platform");
382 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
385 public TimeSpan GetUtcOffset (DateTime dateTime)
387 if (IsDaylightSavingTime (dateTime)) {
388 AdjustmentRule rule = GetApplicableRule (dateTime);
389 return BaseUtcOffset + rule.DaylightDelta;
392 return BaseUtcOffset;
395 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
397 throw new NotImplementedException ();
400 public bool HasSameRules (TimeZoneInfo other)
403 throw new ArgumentNullException ("other");
405 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
408 if (this.adjustmentRules == null)
411 if (this.BaseUtcOffset != other.BaseUtcOffset)
414 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
417 for (int i = 0; i < adjustmentRules.Length; i++) {
418 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
425 public bool IsAmbiguousTime (DateTime dateTime)
427 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
428 throw new ArgumentException ("Kind is Local and time is Invalid");
430 if (this == TimeZoneInfo.Utc)
433 if (dateTime.Kind == DateTimeKind.Utc)
434 dateTime = ConvertTimeFromUtc (dateTime);
436 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
437 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
439 AdjustmentRule rule = GetApplicableRule (dateTime);
440 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
441 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
447 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
449 throw new NotImplementedException ();
452 public bool IsDaylightSavingTime (DateTime dateTime)
454 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
455 throw new ArgumentException ("dateTime is invalid and Kind is Local");
457 if (this == TimeZoneInfo.Utc)
460 if (!SupportsDaylightSavingTime)
462 //FIXME: do not rely on DateTime implementation !
463 if ((dateTime.Kind == DateTimeKind.Local || dateTime.Kind == DateTimeKind.Unspecified) && this == TimeZoneInfo.Local)
464 return dateTime.IsDaylightSavingTime ();
466 //FIXME: do not rely on DateTime implementation !
467 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Utc)
468 return IsDaylightSavingTime (DateTime.SpecifyKind (dateTime.ToUniversalTime (), DateTimeKind.Utc));
470 AdjustmentRule rule = GetApplicableRule (dateTime.Date);
474 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
475 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
476 if (dateTime.Kind == DateTimeKind.Utc) {
477 DST_start -= BaseUtcOffset;
478 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
481 return (dateTime >= DST_start && dateTime < DST_end);
484 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
486 throw new NotImplementedException ();
489 public bool IsInvalidTime (DateTime dateTime)
491 if (dateTime.Kind == DateTimeKind.Utc)
493 if (dateTime.Kind == DateTimeKind.Local && this != Local)
496 AdjustmentRule rule = GetApplicableRule (dateTime);
497 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
498 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
504 public void OnDeserialization (object sender)
506 throw new NotImplementedException ();
509 public string ToSerializedString ()
511 throw new NotImplementedException ();
514 public override string ToString ()
519 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
522 throw new ArgumentNullException ("id");
524 if (id == String.Empty)
525 throw new ArgumentException ("id parameter is an empty string");
527 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
528 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
530 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
531 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
535 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
538 if (adjustmentRules != null && adjustmentRules.Length != 0) {
539 AdjustmentRule prev = null;
540 foreach (AdjustmentRule current in adjustmentRules) {
542 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
544 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
545 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
546 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;");
548 if (prev != null && prev.DateStart > current.DateStart)
549 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
551 if (prev != null && prev.DateEnd > current.DateStart)
552 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
554 if (prev != null && prev.DateEnd == current.DateStart)
555 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
562 this.baseUtcOffset = baseUtcOffset;
563 this.displayName = displayName ?? id;
564 this.standardDisplayName = standardDisplayName ?? id;
565 this.daylightDisplayName = daylightDisplayName;
566 this.disableDaylightSavingTime = disableDaylightSavingTime;
567 this.adjustmentRules = adjustmentRules;
570 private AdjustmentRule GetApplicableRule (DateTime dateTime)
572 //Transitions are always in standard time
573 DateTime date = dateTime;
575 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
576 date = date.ToUniversalTime () + BaseUtcOffset;
578 if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
579 date = date + BaseUtcOffset;
581 foreach (AdjustmentRule rule in adjustmentRules) {
582 if (rule.DateStart > date.Date)
584 if (rule.DateEnd < date.Date)
591 private static DateTime TransitionPoint (TransitionTime transition, int year)
593 if (transition.IsFixedDateRule)
594 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
596 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
597 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
598 if (day > DateTime.DaysInMonth (year, transition.Month))
600 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
604 private static bool ValidTZFile (byte [] buffer, int length)
606 StringBuilder magic = new StringBuilder ();
608 for (int i = 0; i < 4; i++)
609 magic.Append ((char)buffer [i]);
611 if (magic.ToString () != "TZif")
614 if (length >= BUFFER_SIZE)
622 public readonly int Offset;
623 public readonly bool IsDst;
626 public TimeType (int offset, bool is_dst, string abbrev)
628 this.Offset = offset;
633 public override string ToString ()
635 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
639 static int SwapInt32 (int i)
641 return (((i >> 24) & 0xff)
642 | ((i >> 8) & 0xff00)
643 | ((i << 8) & 0xff0000)
647 static int ReadBigEndianInt32 (byte [] buffer, int start)
649 int i = BitConverter.ToInt32 (buffer, start);
650 if (!BitConverter.IsLittleEndian)
653 return SwapInt32 (i);
656 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
658 //Reading the header. 4 bytes for magic, 16 are reserved
659 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
660 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
661 int leapcnt = ReadBigEndianInt32 (buffer, 28);
662 int timecnt = ReadBigEndianInt32 (buffer, 32);
663 int typecnt = ReadBigEndianInt32 (buffer, 36);
664 int charcnt = ReadBigEndianInt32 (buffer, 40);
666 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
667 throw new InvalidTimeZoneException ();
669 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
670 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
671 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
673 if (time_types.Count == 0)
674 throw new InvalidTimeZoneException ();
676 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
677 throw new InvalidTimeZoneException ();
679 TimeSpan baseUtcOffset = new TimeSpan (0);
680 TimeSpan dstDelta = new TimeSpan (0);
681 string standardDisplayName = null;
682 string daylightDisplayName = null;
683 bool dst_observed = false;
684 DateTime dst_start = DateTime.MinValue;
685 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
687 for (int i = 0; i < transitions.Count; i++) {
688 var pair = transitions [i];
689 DateTime ttime = pair.Key;
690 TimeType ttype = pair.Value;
692 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
693 standardDisplayName = ttype.Name;
694 daylightDisplayName = null;
695 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
696 adjustmentRules = new List<AdjustmentRule> ();
697 dst_observed = false;
700 //FIXME: check additional fields for this:
701 //most of the transitions are expressed in GMT
702 dst_start += baseUtcOffset;
703 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
705 //some weird timezone (America/Phoenix) have end dates on Jan 1st
706 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
707 dst_end -= new TimeSpan (24, 0, 0);
709 DateTime dateStart, dateEnd;
710 if (dst_start.Month < 7)
711 dateStart = new DateTime (dst_start.Year, 1, 1);
713 dateStart = new DateTime (dst_start.Year, 7, 1);
715 if (dst_end.Month >= 7)
716 dateEnd = new DateTime (dst_end.Year, 12, 31);
718 dateEnd = new DateTime (dst_end.Year, 6, 30);
721 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
722 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
723 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
724 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
726 dst_observed = false;
728 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
729 daylightDisplayName = ttype.Name;
730 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
737 if (adjustmentRules.Count == 0) {
738 TimeType t = (TimeType)time_types [0];
739 if (standardDisplayName == null) {
740 standardDisplayName = t.Name;
741 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
743 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
745 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
749 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
751 AdjustmentRule prev = null;
752 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
753 if (prev != null && prev.DateEnd > current.DateStart) {
754 adjustmentRules.Remove (current);
758 return adjustmentRules;
761 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
763 var abbrevs = new Dictionary<int, string> ();
764 int abbrev_index = 0;
765 var sb = new StringBuilder ();
766 for (int i = 0; i < count; i++) {
767 char c = (char) buffer [index + i];
771 abbrevs.Add (abbrev_index, sb.ToString ());
772 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
773 for (int j = 1; j < sb.Length; j++)
774 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
775 abbrev_index = i + 1;
776 sb = new StringBuilder ();
782 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
784 var types = new Dictionary<int, TimeType> (count);
785 for (int i = 0; i < count; i++) {
786 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
787 byte is_dst = buffer [index + 6 * i + 4];
788 byte abbrev = buffer [index + 6 * i + 5];
789 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
794 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
796 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
797 for (int i = 0; i < count; i++) {
798 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
799 DateTime ttime = DateTimeFromUnixTime (unixtime);
800 byte ttype = buffer [index + 4 * count + i];
801 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
806 static DateTime DateTimeFromUnixTime (long unix_time)
808 DateTime date_time = new DateTime (1970, 1, 1);
809 return date_time.AddSeconds (unix_time);