6 * Stephane Delcroix <stephane@delcroix.org>
8 * Copyright 2011 Xamarin Inc.
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.CompilerServices;
32 using System.Threading;
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Runtime.Serialization;
36 using System.Runtime.InteropServices;
38 using System.Globalization;
41 using Microsoft.Win32;
45 partial class TimeZoneInfo
47 TimeSpan baseUtcOffset;
48 public TimeSpan BaseUtcOffset {
49 get { return baseUtcOffset; }
52 string daylightDisplayName;
53 public string DaylightName {
55 return supportsDaylightSavingTime
62 public string DisplayName {
63 get { return displayName; }
71 static TimeZoneInfo local;
72 public static TimeZoneInfo Local {
78 throw new TimeZoneNotFoundException ();
80 if (Interlocked.CompareExchange (ref local, l, null) != null)
89 TimeZone transitions are stored when there is a change on the base offset.
91 private List<KeyValuePair<DateTime, TimeType>> transitions;
93 private static bool readlinkNotFound;
96 private static extern int readlink (string path, byte[] buffer, int buflen);
98 private static string readlink (string path)
100 if (readlinkNotFound)
103 byte[] buf = new byte [512];
107 ret = readlink (path, buf, buf.Length);
108 } catch (DllNotFoundException) {
109 readlinkNotFound = true;
111 } catch (EntryPointNotFoundException) {
112 readlinkNotFound = true;
116 if (ret == -1) return null;
117 char[] cbuf = new char [512];
118 int chars = System.Text.Encoding.Default.GetChars (buf, 0, ret, cbuf, 0);
119 return new String (cbuf, 0, chars);
122 private static bool TryGetNameFromPath (string path, out string name)
125 var linkPath = readlink (path);
126 if (linkPath != null) {
127 if (Path.IsPathRooted(linkPath))
130 path = Path.Combine(Path.GetDirectoryName(path), linkPath);
133 path = Path.GetFullPath (path);
135 if (string.IsNullOrEmpty (TimeZoneDirectory))
138 var baseDir = TimeZoneDirectory;
139 if (baseDir [baseDir.Length-1] != Path.DirectorySeparatorChar)
140 baseDir += Path.DirectorySeparatorChar;
142 if (!path.StartsWith (baseDir, StringComparison.InvariantCulture))
145 name = path.Substring (baseDir.Length);
146 if (name == "localtime")
152 #if !MONODROID && !MONOTOUCH && !XAMMAC
153 static TimeZoneInfo CreateLocal ()
156 if (IsWindows && LocalZoneKey != null) {
157 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
159 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
160 name = TrimSpecial (name);
162 return TimeZoneInfo.FindSystemTimeZoneById (name);
163 } else if (IsWindows) {
164 return GetLocalTimeZoneInfoWinRTFallback ();
168 var tz = Environment.GetEnvironmentVariable ("TZ");
170 if (tz == String.Empty)
173 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
179 var tzFilePaths = new string [] {
181 Path.Combine (TimeZoneDirectory, "localtime")};
183 foreach (var tzFilePath in tzFilePaths) {
185 string tzName = null;
186 if (!TryGetNameFromPath (tzFilePath, out tzName))
188 return FindSystemTimeZoneByFileName (tzName, tzFilePath);
189 } catch (TimeZoneNotFoundException) {
197 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
200 string filepath = Path.Combine (TimeZoneDirectory, id);
201 return FindSystemTimeZoneByFileName (id, filepath);
203 throw new NotImplementedException ();
207 static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
210 if (TimeZoneKey != null) {
211 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
213 systemTimeZones.Add (FindSystemTimeZoneById (id));
218 } else if (IsWindows) {
219 systemTimeZones.AddRange (GetSystemTimeZonesWinRTFallback ());
225 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
226 foreach (string continent in continents) {
228 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
230 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
231 systemTimeZones.Add (FindSystemTimeZoneById (id));
232 } catch (ArgumentNullException) {
233 } catch (TimeZoneNotFoundException) {
234 } catch (InvalidTimeZoneException) {
235 } catch (Exception) {
242 throw new NotImplementedException ("This method is not implemented for this platform");
245 #endif // !MONODROID && !MONOTOUCH && !XAMMAC
247 string standardDisplayName;
248 public string StandardName {
249 get { return standardDisplayName; }
252 bool supportsDaylightSavingTime;
253 public bool SupportsDaylightSavingTime {
254 get { return supportsDaylightSavingTime; }
257 static TimeZoneInfo utc;
258 public static TimeZoneInfo Utc {
261 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
266 static string timeZoneDirectory;
267 static string TimeZoneDirectory {
269 if (timeZoneDirectory == null)
270 timeZoneDirectory = "/usr/share/zoneinfo";
271 return timeZoneDirectory;
275 timeZoneDirectory = value;
279 private AdjustmentRule [] adjustmentRules;
281 #if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
283 /// Determine whether windows of not (taken Stephane Delcroix's code)
285 private static bool IsWindows
288 int platform = (int) Environment.OSVersion.Platform;
289 return ((platform != 4) && (platform != 6) && (platform != 128));
294 /// Needed to trim misc garbage in MS registry keys
296 private static string TrimSpecial (string str)
301 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
302 var Iend = str.Length - 1;
303 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend]) && str[Iend] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
306 return str.Substring (Istart, Iend-Istart+1);
309 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
310 static RegistryKey timeZoneKey;
311 static RegistryKey TimeZoneKey {
313 if (timeZoneKey != null)
319 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
320 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
328 static RegistryKey localZoneKey;
329 static RegistryKey LocalZoneKey {
331 if (localZoneKey != null)
338 return localZoneKey = Registry.LocalMachine.OpenSubKey (
339 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
346 #endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
348 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
350 var resultTicks = date.Ticks + ticks;
351 if (resultTicks < DateTime.MinValue.Ticks) {
352 result = DateTime.SpecifyKind (DateTime.MinValue, kind);
356 if (resultTicks > DateTime.MaxValue.Ticks) {
357 result = DateTime.SpecifyKind (DateTime.MaxValue, kind);
361 result = new DateTime (resultTicks, kind);
365 public static void ClearCachedData ()
369 systemTimeZones = null;
372 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
374 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
377 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
379 if (sourceTimeZone == null)
380 throw new ArgumentNullException ("sourceTimeZone");
382 if (destinationTimeZone == null)
383 throw new ArgumentNullException ("destinationTimeZone");
385 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
386 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
388 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
389 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
391 if (sourceTimeZone.IsInvalidTime (dateTime))
392 throw new ArgumentException ("dateTime parameter is an invalid time");
394 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
397 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
399 if (destinationTimeZone != TimeZoneInfo.Utc) {
400 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
401 if (dateTime.Kind == DateTimeKind.Unspecified)
402 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
408 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
410 if (destinationTimeZone == null)
411 throw new ArgumentNullException("destinationTimeZone");
413 var utcDateTime = dateTimeOffset.UtcDateTime;
416 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
418 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
421 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
423 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
426 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
428 TimeZoneInfo source_tz;
429 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZoneId == TimeZoneInfo.Utc.Id) {
432 source_tz = FindSystemTimeZoneById (sourceTimeZoneId);
435 return ConvertTime (dateTime, source_tz, FindSystemTimeZoneById (destinationTimeZoneId));
438 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
440 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
443 private DateTime ConvertTimeFromUtc (DateTime dateTime)
445 if (dateTime.Kind == DateTimeKind.Local)
446 throw new ArgumentException ("Kind property of dateTime is Local");
448 if (this == TimeZoneInfo.Utc)
449 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
451 var utcOffset = GetUtcOffset (dateTime);
453 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
456 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
457 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
462 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
464 if (destinationTimeZone == null)
465 throw new ArgumentNullException ("destinationTimeZone");
467 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
470 public static DateTime ConvertTimeToUtc (DateTime dateTime)
472 if (dateTime.Kind == DateTimeKind.Utc)
475 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
478 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
480 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
483 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
485 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
488 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
490 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
491 if (sourceTimeZone == null)
492 throw new ArgumentNullException ("sourceTimeZone");
494 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
495 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
497 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
498 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
500 if (sourceTimeZone.IsInvalidTime (dateTime))
501 throw new ArgumentException ("dateTime parameter is an invalid time");
504 if (dateTime.Kind == DateTimeKind.Utc)
508 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
510 DateTime utcDateTime;
511 TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc);
515 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
517 bool isDaylightSavings;
518 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
521 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
523 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
526 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
528 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
531 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
533 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
536 public override bool Equals (object obj)
538 return Equals (obj as TimeZoneInfo);
541 public bool Equals (TimeZoneInfo other)
546 return other.Id == this.Id && HasSameRules (other);
549 public static TimeZoneInfo FindSystemTimeZoneById (string id)
551 //FIXME: this method should check for cached values in systemTimeZones
553 throw new ArgumentNullException ("id");
555 if (TimeZoneKey != null)
557 if (id == "Coordinated Universal Time")
558 id = "UTC"; //windows xp exception for "StandardName" property
559 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
561 throw new TimeZoneNotFoundException ();
562 return FromRegistryKey(id, key);
563 } else if (IsWindows) {
564 return FindSystemTimeZoneByIdWinRTFallback (id);
567 // Local requires special logic that already exists in the Local property (bug #326)
571 return FindSystemTimeZoneByIdCore (id);
575 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
577 FileStream stream = null;
579 stream = File.OpenRead (filepath);
580 } catch (Exception ex) {
581 throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath, ex);
584 return BuildFromStream (id, stream);
593 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
595 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
598 throw new InvalidTimeZoneException ();
600 int bias = BitConverter.ToInt32 (reg_tzi, 0);
601 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
603 string display_name = (string) key.GetValue ("Display");
604 string standard_name = (string) key.GetValue ("Std");
605 string daylight_name = (string) key.GetValue ("Dlt");
607 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
609 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
610 if (dst_key != null) {
611 int first_year = (int) dst_key.GetValue ("FirstEntry");
612 int last_year = (int) dst_key.GetValue ("LastEntry");
615 for (year=first_year; year<=last_year; year++) {
616 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
617 if (dst_tzi != null) {
618 int start_year = year == first_year ? 1 : year;
619 int end_year = year == last_year ? 9999 : year;
620 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
625 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
627 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules));
630 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
632 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
633 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
635 int standard_year = BitConverter.ToInt16 (buffer, 12);
636 int standard_month = BitConverter.ToInt16 (buffer, 14);
637 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
638 int standard_day = BitConverter.ToInt16 (buffer, 18);
639 int standard_hour = BitConverter.ToInt16 (buffer, 20);
640 int standard_minute = BitConverter.ToInt16 (buffer, 22);
641 int standard_second = BitConverter.ToInt16 (buffer, 24);
642 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
644 int daylight_year = BitConverter.ToInt16 (buffer, 28);
645 int daylight_month = BitConverter.ToInt16 (buffer, 30);
646 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
647 int daylight_day = BitConverter.ToInt16 (buffer, 34);
648 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
649 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
650 int daylight_second = BitConverter.ToInt16 (buffer, 40);
651 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
653 if (standard_month == 0 || daylight_month == 0)
657 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
658 TransitionTime start_transition_time;
660 if (daylight_year == 0) {
661 start_date = new DateTime (start_year, 1, 1);
662 start_transition_time = TransitionTime.CreateFloatingDateRule (
663 start_timeofday, daylight_month, daylight_day,
664 (DayOfWeek) daylight_dayofweek);
667 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
668 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
669 start_transition_time = TransitionTime.CreateFixedDateRule (
670 start_timeofday, daylight_month, daylight_day);
674 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
675 TransitionTime end_transition_time;
677 if (standard_year == 0) {
678 end_date = new DateTime (end_year, 12, 31);
679 end_transition_time = TransitionTime.CreateFloatingDateRule (
680 end_timeofday, standard_month, standard_day,
681 (DayOfWeek) standard_dayofweek);
684 end_date = new DateTime (standard_year, standard_month, standard_day,
685 standard_hour, standard_minute, standard_second, standard_millisecond);
686 end_transition_time = TransitionTime.CreateFixedDateRule (
687 end_timeofday, standard_month, standard_day);
690 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
692 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
693 start_date, end_date, daylight_delta,
694 start_transition_time, end_transition_time));
698 public AdjustmentRule [] GetAdjustmentRules ()
700 if (!supportsDaylightSavingTime || adjustmentRules == null)
701 return new AdjustmentRule [0];
703 return (AdjustmentRule []) adjustmentRules.Clone ();
706 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
708 if (!IsAmbiguousTime (dateTime))
709 throw new ArgumentException ("dateTime is not an ambiguous time");
711 AdjustmentRule rule = GetApplicableRule (dateTime);
713 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
715 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
718 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
720 if (!IsAmbiguousTime (dateTimeOffset))
721 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
723 throw new NotImplementedException ();
726 public override int GetHashCode ()
728 int hash_code = Id.GetHashCode ();
729 foreach (AdjustmentRule rule in GetAdjustmentRules ())
730 hash_code ^= rule.GetHashCode ();
734 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
737 throw new ArgumentNullException ("info");
738 info.AddValue ("Id", id);
739 info.AddValue ("DisplayName", displayName);
740 info.AddValue ("StandardName", standardDisplayName);
741 info.AddValue ("DaylightName", daylightDisplayName);
742 info.AddValue ("BaseUtcOffset", baseUtcOffset);
743 info.AddValue ("AdjustmentRules", adjustmentRules);
744 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
747 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
749 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
751 if (systemTimeZones == null) {
752 var tz = new List<TimeZoneInfo> ();
753 GetSystemTimeZonesCore (tz);
754 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
757 return systemTimeZones;
760 public TimeSpan GetUtcOffset (DateTime dateTime)
763 return GetUtcOffset (dateTime, out isDST);
766 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
769 return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
772 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
776 TimeZoneInfo tz = this;
777 if (dateTime.Kind == DateTimeKind.Utc)
778 tz = TimeZoneInfo.Utc;
780 if (dateTime.Kind == DateTimeKind.Local)
781 tz = TimeZoneInfo.Local;
784 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
791 DateTime utcDateTime;
792 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
793 return BaseUtcOffset;
795 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
798 // This is an helper method used by the method above, do not use this on its own.
799 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
801 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
802 throw new Exception ();
806 if (tz == TimeZoneInfo.Utc)
807 return TimeSpan.Zero;
810 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
813 if (dateTime.Kind == DateTimeKind.Utc) {
814 var utcRule = tz.GetApplicableRule (dateTime);
815 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
817 return tz.BaseUtcOffset + utcRule.DaylightDelta;
820 return tz.BaseUtcOffset;
823 DateTime stdUtcDateTime;
824 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
825 return tz.BaseUtcOffset;
827 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
829 DateTime dstUtcDateTime = DateTime.MinValue;
830 if (tzRule != null) {
831 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
832 return tz.BaseUtcOffset;
835 if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
836 // Replicate what .NET does when given a time which falls into the hour which is lost when
837 // DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
838 // DST delta while in that hour.
840 if (tz.IsInDST (tzRule, dstUtcDateTime)) {
841 return tz.BaseUtcOffset + tzRule.DaylightDelta;
843 return tz.BaseUtcOffset;
847 return tz.BaseUtcOffset;
850 public bool HasSameRules (TimeZoneInfo other)
853 throw new ArgumentNullException ("other");
855 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
858 if (this.adjustmentRules == null)
861 if (this.BaseUtcOffset != other.BaseUtcOffset)
864 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
867 for (int i = 0; i < adjustmentRules.Length; i++) {
868 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
875 public bool IsAmbiguousTime (DateTime dateTime)
877 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
878 throw new ArgumentException ("Kind is Local and time is Invalid");
880 if (this == TimeZoneInfo.Utc)
883 if (dateTime.Kind == DateTimeKind.Utc)
884 dateTime = ConvertTimeFromUtc (dateTime);
886 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
887 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
889 AdjustmentRule rule = GetApplicableRule (dateTime);
891 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
892 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
899 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
901 throw new NotImplementedException ();
904 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
906 // Check whether we're in the dateTime year's DST period
907 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
910 // We might be in the dateTime previous year's DST period
911 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
914 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
916 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
917 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
918 if (dateTime.Kind == DateTimeKind.Utc) {
919 DST_start -= BaseUtcOffset;
920 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
922 return (dateTime >= DST_start && dateTime < DST_end);
925 public bool IsDaylightSavingTime (DateTime dateTime)
927 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
928 throw new ArgumentException ("dateTime is invalid and Kind is Local");
930 if (this == TimeZoneInfo.Utc)
933 if (!SupportsDaylightSavingTime)
937 GetUtcOffset (dateTime, out isDst);
942 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
944 return IsDaylightSavingTime (dateTime);
947 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
949 return IsDaylightSavingTime (dateTimeOffset.DateTime);
952 internal DaylightTime GetDaylightChanges (int year)
954 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
955 TimeSpan delta = new TimeSpan ();
957 if (transitions != null) {
958 end = DateTime.MaxValue;
959 for (var i = transitions.Count - 1; i >= 0; i--) {
960 var pair = transitions [i];
961 DateTime ttime = pair.Key;
962 TimeType ttype = pair.Value;
964 if (ttime.Year > year)
966 if (ttime.Year < year)
970 // DaylightTime.Delta is relative to the current BaseUtcOffset.
971 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
978 // DaylightTime.Start is relative to the Standard time.
979 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
980 start = DateTime.MinValue;
982 // DaylightTime.End is relative to the DST time.
983 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
984 end = DateTime.MinValue;
986 AdjustmentRule first = null, last = null;
988 // Rule start/end dates are either very specific or very broad depending on the platform
989 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
990 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
991 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
993 foreach (var rule in GetAdjustmentRules ()) {
994 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
996 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
998 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
1002 if (first == null || last == null)
1003 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1005 start = TransitionPoint (first.DaylightTransitionStart, year);
1006 end = TransitionPoint (last.DaylightTransitionEnd, year);
1007 delta = first.DaylightDelta;
1010 if (start == DateTime.MinValue || end == DateTime.MinValue)
1011 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1013 return new DaylightTime (start, end, delta);
1016 public bool IsInvalidTime (DateTime dateTime)
1018 if (dateTime.Kind == DateTimeKind.Utc)
1020 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1023 AdjustmentRule rule = GetApplicableRule (dateTime);
1025 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1026 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1033 void IDeserializationCallback.OnDeserialization (object sender)
1036 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1037 } catch (ArgumentException ex) {
1038 throw new SerializationException ("invalid serialization data", ex);
1042 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1045 throw new ArgumentNullException ("id");
1047 if (id == String.Empty)
1048 throw new ArgumentException ("id parameter is an empty string");
1050 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1051 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1053 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1054 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1058 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1061 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1062 AdjustmentRule prev = null;
1063 foreach (AdjustmentRule current in adjustmentRules) {
1064 if (current == null)
1065 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1067 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1068 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1069 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;");
1071 if (prev != null && prev.DateStart > current.DateStart)
1072 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1074 if (prev != null && prev.DateEnd > current.DateStart)
1075 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1077 if (prev != null && prev.DateEnd == current.DateStart)
1078 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1085 public override string ToString ()
1090 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1093 throw new ArgumentNullException ("info");
1094 id = (string) info.GetValue ("Id", typeof (string));
1095 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1096 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1097 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1098 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1099 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1100 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1103 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1106 throw new ArgumentNullException ("id");
1108 if (id == String.Empty)
1109 throw new ArgumentException ("id parameter is an empty string");
1111 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1112 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1114 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1115 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1119 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1122 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1124 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1125 AdjustmentRule prev = null;
1126 foreach (AdjustmentRule current in adjustmentRules) {
1127 if (current == null)
1128 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1130 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1131 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1132 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;");
1134 if (prev != null && prev.DateStart > current.DateStart)
1135 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1137 if (prev != null && prev.DateEnd > current.DateStart)
1138 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1140 if (prev != null && prev.DateEnd == current.DateStart)
1141 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1146 supportsDaylightSavingTime = false;
1150 this.baseUtcOffset = baseUtcOffset;
1151 this.displayName = displayName ?? id;
1152 this.standardDisplayName = standardDisplayName ?? id;
1153 this.daylightDisplayName = daylightDisplayName;
1154 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1155 this.adjustmentRules = adjustmentRules;
1158 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1160 //Applicable rules are in standard time
1161 DateTime date = dateTime;
1163 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1164 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1166 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1167 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1171 // get the date component of the datetime
1174 if (adjustmentRules != null) {
1175 foreach (AdjustmentRule rule in adjustmentRules) {
1176 if (rule.DateStart > date)
1178 if (rule.DateEnd < date)
1186 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1188 offset = BaseUtcOffset;
1191 if (transitions == null)
1194 //Transitions are in UTC
1195 DateTime date = dateTime;
1197 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1198 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1202 if (dateTime.Kind != DateTimeKind.Utc) {
1203 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1207 AdjustmentRule current = GetApplicableRule(date);
1208 if (current != null) {
1209 DateTime tStart = TransitionPoint(current.DaylightTransitionStart, date.Year);
1210 DateTime tEnd = TransitionPoint(current.DaylightTransitionEnd, date.Year);
1211 if ((date >= tStart) && (date <= tEnd)) {
1212 offset = baseUtcOffset + current.DaylightDelta;
1220 private static DateTime TransitionPoint (TransitionTime transition, int year)
1222 if (transition.IsFixedDateRule)
1223 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1225 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1226 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1227 if (day > DateTime.DaysInMonth (year, transition.Month))
1231 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1234 static AdjustmentRule[] ValidateRules (List<AdjustmentRule> adjustmentRules)
1236 if (adjustmentRules == null || adjustmentRules.Count == 0)
1239 AdjustmentRule prev = null;
1240 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1241 if (prev != null && prev.DateEnd > current.DateStart) {
1242 adjustmentRules.Remove (current);
1246 return adjustmentRules.ToArray ();
1249 #if LIBC || MONOTOUCH
1250 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1252 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1254 byte [] buffer = new byte [BUFFER_SIZE];
1255 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1257 if (!ValidTZFile (buffer, length))
1258 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1261 return ParseTZBuffer (id, buffer, length);
1262 } catch (InvalidTimeZoneException) {
1264 } catch (Exception e) {
1265 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1269 private static bool ValidTZFile (byte [] buffer, int length)
1271 StringBuilder magic = new StringBuilder ();
1273 for (int i = 0; i < 4; i++)
1274 magic.Append ((char)buffer [i]);
1276 if (magic.ToString () != "TZif")
1279 if (length >= BUFFER_SIZE)
1285 static int SwapInt32 (int i)
1287 return (((i >> 24) & 0xff)
1288 | ((i >> 8) & 0xff00)
1289 | ((i << 8) & 0xff0000)
1290 | (((i & 0xff) << 24)));
1293 static int ReadBigEndianInt32 (byte [] buffer, int start)
1295 int i = BitConverter.ToInt32 (buffer, start);
1296 if (!BitConverter.IsLittleEndian)
1299 return SwapInt32 (i);
1302 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1304 //Reading the header. 4 bytes for magic, 16 are reserved
1305 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1306 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1307 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1308 int timecnt = ReadBigEndianInt32 (buffer, 32);
1309 int typecnt = ReadBigEndianInt32 (buffer, 36);
1310 int charcnt = ReadBigEndianInt32 (buffer, 40);
1312 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1313 throw new InvalidTimeZoneException ();
1315 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1316 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1317 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1319 if (time_types.Count == 0)
1320 throw new InvalidTimeZoneException ();
1322 if (time_types.Count == 1 && time_types[0].IsDst)
1323 throw new InvalidTimeZoneException ();
1325 TimeSpan baseUtcOffset = new TimeSpan (0);
1326 TimeSpan dstDelta = new TimeSpan (0);
1327 string standardDisplayName = null;
1328 string daylightDisplayName = null;
1329 bool dst_observed = false;
1330 DateTime dst_start = DateTime.MinValue;
1331 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1332 bool storeTransition = false;
1334 for (int i = 0; i < transitions.Count; i++) {
1335 var pair = transitions [i];
1336 DateTime ttime = pair.Key;
1337 TimeType ttype = pair.Value;
1339 if (standardDisplayName != ttype.Name)
1340 standardDisplayName = ttype.Name;
1341 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1342 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1343 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1344 storeTransition = true;
1345 adjustmentRules = new List<AdjustmentRule> ();
1346 dst_observed = false;
1349 //FIXME: check additional fields for this:
1350 //most of the transitions are expressed in GMT
1351 dst_start += baseUtcOffset;
1352 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1354 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1355 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1356 dst_end -= new TimeSpan (24, 0, 0);
1359 * AdjustmentRule specifies a DST period that starts and ends within a year.
1360 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1361 * Thus we fallback to the transitions.
1363 if (dst_start.AddYears (1) < dst_end)
1364 storeTransition = true;
1366 DateTime dateStart, dateEnd;
1367 if (dst_start.Month < 7)
1368 dateStart = new DateTime (dst_start.Year, 1, 1);
1370 dateStart = new DateTime (dst_start.Year, 7, 1);
1372 if (dst_end.Month >= 7)
1373 dateEnd = new DateTime (dst_end.Year, 12, 31);
1375 dateEnd = new DateTime (dst_end.Year, 6, 30);
1378 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1379 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1380 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1381 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1383 dst_observed = false;
1385 if (daylightDisplayName != ttype.Name)
1386 daylightDisplayName = ttype.Name;
1387 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1388 // Round to nearest minute, since it's not possible to create an adjustment rule
1389 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1390 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1391 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1392 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1393 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1397 dst_observed = true;
1402 if (adjustmentRules.Count == 0 && !storeTransition) {
1403 if (standardDisplayName == null) {
1404 var t = time_types [0];
1405 standardDisplayName = t.Name;
1406 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1408 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1410 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules));
1413 if (storeTransition && transitions.Count > 0) {
1414 tz.transitions = transitions;
1416 tz.supportsDaylightSavingTime = adjustmentRules.Count > 0;
1421 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1423 var abbrevs = new Dictionary<int, string> ();
1424 int abbrev_index = 0;
1425 var sb = new StringBuilder ();
1426 for (int i = 0; i < count; i++) {
1427 char c = (char) buffer [index + i];
1431 abbrevs.Add (abbrev_index, sb.ToString ());
1432 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1433 //j == sb.Length empty substring also needs to be added #31432
1434 for (int j = 1; j <= sb.Length; j++)
1435 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1436 abbrev_index = i + 1;
1437 sb = new StringBuilder ();
1443 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1445 var types = new Dictionary<int, TimeType> (count);
1446 for (int i = 0; i < count; i++) {
1447 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1450 // The official tz database contains timezone with GMT offsets
1451 // not only in whole hours/minutes but in seconds. This happens for years
1452 // before 1901. For example
1454 // NAME GMTOFF RULES FORMAT UNTIL
1455 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1457 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1458 // we remove seconds to not crash later
1460 offset = (offset / 60) * 60;
1462 byte is_dst = buffer [index + 6 * i + 4];
1463 byte abbrev = buffer [index + 6 * i + 5];
1464 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1469 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1471 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1472 for (int i = 0; i < count; i++) {
1473 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1474 DateTime ttime = DateTimeFromUnixTime (unixtime);
1475 byte ttype = buffer [index + 4 * count + i];
1476 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1481 static DateTime DateTimeFromUnixTime (long unix_time)
1483 DateTime date_time = new DateTime (1970, 1, 1);
1484 return date_time.AddSeconds (unix_time);
1487 #region reference sources
1488 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1489 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1492 return Local.GetUtcOffset (dateTime, out dst);
1495 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1498 return GetUtcOffset (dateTime, out dst);
1501 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1503 isDaylightSavings = false;
1504 isAmbiguousLocalDst = false;
1505 TimeSpan baseOffset = zone.BaseUtcOffset;
1507 if (zone.IsAmbiguousTime (time)) {
1508 isAmbiguousLocalDst = true;
1509 // return baseOffset;
1512 return zone.GetUtcOffset (time, out isDaylightSavings);
1518 public readonly int Offset;
1519 public readonly bool IsDst;
1522 public TimeType (int offset, bool is_dst, string abbrev)
1524 this.Offset = offset;
1525 this.IsDst = is_dst;
1529 public override string ToString ()
1531 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;