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 if (!File.Exists (filepath))
578 throw new TimeZoneNotFoundException ();
580 using (FileStream stream = File.OpenRead (filepath)) {
581 return BuildFromStream (id, stream);
587 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
589 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
592 throw new InvalidTimeZoneException ();
594 int bias = BitConverter.ToInt32 (reg_tzi, 0);
595 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
597 string display_name = (string) key.GetValue ("Display");
598 string standard_name = (string) key.GetValue ("Std");
599 string daylight_name = (string) key.GetValue ("Dlt");
601 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
603 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
604 if (dst_key != null) {
605 int first_year = (int) dst_key.GetValue ("FirstEntry");
606 int last_year = (int) dst_key.GetValue ("LastEntry");
609 for (year=first_year; year<=last_year; year++) {
610 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
611 if (dst_tzi != null) {
612 int start_year = year == first_year ? 1 : year;
613 int end_year = year == last_year ? 9999 : year;
614 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
619 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
621 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
624 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
626 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
627 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
629 int standard_year = BitConverter.ToInt16 (buffer, 12);
630 int standard_month = BitConverter.ToInt16 (buffer, 14);
631 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
632 int standard_day = BitConverter.ToInt16 (buffer, 18);
633 int standard_hour = BitConverter.ToInt16 (buffer, 20);
634 int standard_minute = BitConverter.ToInt16 (buffer, 22);
635 int standard_second = BitConverter.ToInt16 (buffer, 24);
636 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
638 int daylight_year = BitConverter.ToInt16 (buffer, 28);
639 int daylight_month = BitConverter.ToInt16 (buffer, 30);
640 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
641 int daylight_day = BitConverter.ToInt16 (buffer, 34);
642 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
643 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
644 int daylight_second = BitConverter.ToInt16 (buffer, 40);
645 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
647 if (standard_month == 0 || daylight_month == 0)
651 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
652 TransitionTime start_transition_time;
654 if (daylight_year == 0) {
655 start_date = new DateTime (start_year, 1, 1);
656 start_transition_time = TransitionTime.CreateFloatingDateRule (
657 start_timeofday, daylight_month, daylight_day,
658 (DayOfWeek) daylight_dayofweek);
661 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
662 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
663 start_transition_time = TransitionTime.CreateFixedDateRule (
664 start_timeofday, daylight_month, daylight_day);
668 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
669 TransitionTime end_transition_time;
671 if (standard_year == 0) {
672 end_date = new DateTime (end_year, 12, 31);
673 end_transition_time = TransitionTime.CreateFloatingDateRule (
674 end_timeofday, standard_month, standard_day,
675 (DayOfWeek) standard_dayofweek);
678 end_date = new DateTime (standard_year, standard_month, standard_day,
679 standard_hour, standard_minute, standard_second, standard_millisecond);
680 end_transition_time = TransitionTime.CreateFixedDateRule (
681 end_timeofday, standard_month, standard_day);
684 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
686 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
687 start_date, end_date, daylight_delta,
688 start_transition_time, end_transition_time));
692 public AdjustmentRule [] GetAdjustmentRules ()
694 if (!supportsDaylightSavingTime || adjustmentRules == null)
695 return new AdjustmentRule [0];
697 return (AdjustmentRule []) adjustmentRules.Clone ();
700 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
702 if (!IsAmbiguousTime (dateTime))
703 throw new ArgumentException ("dateTime is not an ambiguous time");
705 AdjustmentRule rule = GetApplicableRule (dateTime);
707 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
709 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
712 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
714 if (!IsAmbiguousTime (dateTimeOffset))
715 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
717 throw new NotImplementedException ();
720 public override int GetHashCode ()
722 int hash_code = Id.GetHashCode ();
723 foreach (AdjustmentRule rule in GetAdjustmentRules ())
724 hash_code ^= rule.GetHashCode ();
728 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
731 throw new ArgumentNullException ("info");
732 info.AddValue ("Id", id);
733 info.AddValue ("DisplayName", displayName);
734 info.AddValue ("StandardName", standardDisplayName);
735 info.AddValue ("DaylightName", daylightDisplayName);
736 info.AddValue ("BaseUtcOffset", baseUtcOffset);
737 info.AddValue ("AdjustmentRules", adjustmentRules);
738 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
741 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
743 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
745 if (systemTimeZones == null) {
746 var tz = new List<TimeZoneInfo> ();
747 GetSystemTimeZonesCore (tz);
748 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
751 return systemTimeZones;
754 public TimeSpan GetUtcOffset (DateTime dateTime)
757 return GetUtcOffset (dateTime, out isDST);
760 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
763 return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
766 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
770 TimeZoneInfo tz = this;
771 if (dateTime.Kind == DateTimeKind.Utc)
772 tz = TimeZoneInfo.Utc;
774 if (dateTime.Kind == DateTimeKind.Local)
775 tz = TimeZoneInfo.Local;
778 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
785 DateTime utcDateTime;
786 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
787 return BaseUtcOffset;
789 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
792 // This is an helper method used by the method above, do not use this on its own.
793 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
795 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
796 throw new Exception ();
800 if (tz == TimeZoneInfo.Utc)
801 return TimeSpan.Zero;
804 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
807 if (dateTime.Kind == DateTimeKind.Utc) {
808 var utcRule = tz.GetApplicableRule (dateTime);
809 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
811 return tz.BaseUtcOffset + utcRule.DaylightDelta;
814 return tz.BaseUtcOffset;
817 DateTime stdUtcDateTime;
818 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
819 return tz.BaseUtcOffset;
821 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
823 DateTime dstUtcDateTime = DateTime.MinValue;
824 if (tzRule != null) {
825 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
826 return tz.BaseUtcOffset;
829 if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
830 // Replicate what .NET does when given a time which falls into the hour which is lost when
831 // DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
832 // DST delta while in that hour.
834 if (tz.IsInDST (tzRule, dstUtcDateTime)) {
835 return tz.BaseUtcOffset + tzRule.DaylightDelta;
837 return tz.BaseUtcOffset;
841 return tz.BaseUtcOffset;
844 public bool HasSameRules (TimeZoneInfo other)
847 throw new ArgumentNullException ("other");
849 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
852 if (this.adjustmentRules == null)
855 if (this.BaseUtcOffset != other.BaseUtcOffset)
858 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
861 for (int i = 0; i < adjustmentRules.Length; i++) {
862 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
869 public bool IsAmbiguousTime (DateTime dateTime)
871 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
872 throw new ArgumentException ("Kind is Local and time is Invalid");
874 if (this == TimeZoneInfo.Utc)
877 if (dateTime.Kind == DateTimeKind.Utc)
878 dateTime = ConvertTimeFromUtc (dateTime);
880 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
881 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
883 AdjustmentRule rule = GetApplicableRule (dateTime);
885 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
886 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
893 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
895 throw new NotImplementedException ();
898 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
900 // Check whether we're in the dateTime year's DST period
901 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
904 // We might be in the dateTime previous year's DST period
905 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
908 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
910 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
911 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
912 if (dateTime.Kind == DateTimeKind.Utc) {
913 DST_start -= BaseUtcOffset;
914 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
916 return (dateTime >= DST_start && dateTime < DST_end);
919 public bool IsDaylightSavingTime (DateTime dateTime)
921 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
922 throw new ArgumentException ("dateTime is invalid and Kind is Local");
924 if (this == TimeZoneInfo.Utc)
927 if (!SupportsDaylightSavingTime)
931 GetUtcOffset (dateTime, out isDst);
936 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
938 return IsDaylightSavingTime (dateTime);
941 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
943 return IsDaylightSavingTime (dateTimeOffset.DateTime);
946 internal DaylightTime GetDaylightChanges (int year)
948 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
949 TimeSpan delta = new TimeSpan ();
951 if (transitions != null) {
952 end = DateTime.MaxValue;
953 for (var i = transitions.Count - 1; i >= 0; i--) {
954 var pair = transitions [i];
955 DateTime ttime = pair.Key;
956 TimeType ttype = pair.Value;
958 if (ttime.Year > year)
960 if (ttime.Year < year)
964 // DaylightTime.Delta is relative to the current BaseUtcOffset.
965 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
972 // DaylightTime.Start is relative to the Standard time.
973 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
974 start = DateTime.MinValue;
976 // DaylightTime.End is relative to the DST time.
977 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
978 end = DateTime.MinValue;
980 AdjustmentRule first = null, last = null;
982 // Rule start/end dates are either very specific or very broad depending on the platform
983 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
984 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
985 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
987 foreach (var rule in GetAdjustmentRules ()) {
988 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
990 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
992 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
996 if (first == null || last == null)
997 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
999 start = TransitionPoint (first.DaylightTransitionStart, year);
1000 end = TransitionPoint (last.DaylightTransitionEnd, year);
1001 delta = first.DaylightDelta;
1004 if (start == DateTime.MinValue || end == DateTime.MinValue)
1005 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1007 return new DaylightTime (start, end, delta);
1010 public bool IsInvalidTime (DateTime dateTime)
1012 if (dateTime.Kind == DateTimeKind.Utc)
1014 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1017 AdjustmentRule rule = GetApplicableRule (dateTime);
1019 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1020 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1027 void IDeserializationCallback.OnDeserialization (object sender)
1030 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1031 } catch (ArgumentException ex) {
1032 throw new SerializationException ("invalid serialization data", ex);
1036 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1039 throw new ArgumentNullException ("id");
1041 if (id == String.Empty)
1042 throw new ArgumentException ("id parameter is an empty string");
1044 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1045 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1047 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1048 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1052 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1055 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1056 AdjustmentRule prev = null;
1057 foreach (AdjustmentRule current in adjustmentRules) {
1058 if (current == null)
1059 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1061 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1062 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1063 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;");
1065 if (prev != null && prev.DateStart > current.DateStart)
1066 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1068 if (prev != null && prev.DateEnd > current.DateStart)
1069 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1071 if (prev != null && prev.DateEnd == current.DateStart)
1072 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1079 public override string ToString ()
1084 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1087 throw new ArgumentNullException ("info");
1088 id = (string) info.GetValue ("Id", typeof (string));
1089 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1090 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1091 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1092 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1093 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1094 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1097 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1100 throw new ArgumentNullException ("id");
1102 if (id == String.Empty)
1103 throw new ArgumentException ("id parameter is an empty string");
1105 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1106 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1108 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1109 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1113 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1116 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1118 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1119 AdjustmentRule prev = null;
1120 foreach (AdjustmentRule current in adjustmentRules) {
1121 if (current == null)
1122 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1124 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1125 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1126 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;");
1128 if (prev != null && prev.DateStart > current.DateStart)
1129 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1131 if (prev != null && prev.DateEnd > current.DateStart)
1132 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1134 if (prev != null && prev.DateEnd == current.DateStart)
1135 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1140 supportsDaylightSavingTime = false;
1144 this.baseUtcOffset = baseUtcOffset;
1145 this.displayName = displayName ?? id;
1146 this.standardDisplayName = standardDisplayName ?? id;
1147 this.daylightDisplayName = daylightDisplayName;
1148 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1149 this.adjustmentRules = adjustmentRules;
1152 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1154 //Applicable rules are in standard time
1155 DateTime date = dateTime;
1157 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1158 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1160 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1161 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1165 // get the date component of the datetime
1168 if (adjustmentRules != null) {
1169 foreach (AdjustmentRule rule in adjustmentRules) {
1170 if (rule.DateStart > date)
1172 if (rule.DateEnd < date)
1180 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1182 offset = BaseUtcOffset;
1185 if (transitions == null)
1188 //Transitions are in UTC
1189 DateTime date = dateTime;
1191 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1192 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1196 if (dateTime.Kind != DateTimeKind.Utc) {
1197 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1201 AdjustmentRule current = GetApplicableRule(date);
1202 if (current != null) {
1203 DateTime tStart = TransitionPoint(current.DaylightTransitionStart, date.Year);
1204 DateTime tEnd = TransitionPoint(current.DaylightTransitionEnd, date.Year);
1205 if ((date >= tStart) && (date <= tEnd)) {
1206 offset = baseUtcOffset + current.DaylightDelta;
1214 private static DateTime TransitionPoint (TransitionTime transition, int year)
1216 if (transition.IsFixedDateRule)
1217 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1219 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1220 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1221 if (day > DateTime.DaysInMonth (year, transition.Month))
1225 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1228 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1230 AdjustmentRule prev = null;
1231 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1232 if (prev != null && prev.DateEnd > current.DateStart) {
1233 adjustmentRules.Remove (current);
1237 return adjustmentRules;
1240 #if LIBC || MONOTOUCH
1241 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1243 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1245 byte [] buffer = new byte [BUFFER_SIZE];
1246 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1248 if (!ValidTZFile (buffer, length))
1249 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1252 return ParseTZBuffer (id, buffer, length);
1253 } catch (InvalidTimeZoneException) {
1255 } catch (Exception e) {
1256 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1260 private static bool ValidTZFile (byte [] buffer, int length)
1262 StringBuilder magic = new StringBuilder ();
1264 for (int i = 0; i < 4; i++)
1265 magic.Append ((char)buffer [i]);
1267 if (magic.ToString () != "TZif")
1270 if (length >= BUFFER_SIZE)
1276 static int SwapInt32 (int i)
1278 return (((i >> 24) & 0xff)
1279 | ((i >> 8) & 0xff00)
1280 | ((i << 8) & 0xff0000)
1281 | (((i & 0xff) << 24)));
1284 static int ReadBigEndianInt32 (byte [] buffer, int start)
1286 int i = BitConverter.ToInt32 (buffer, start);
1287 if (!BitConverter.IsLittleEndian)
1290 return SwapInt32 (i);
1293 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1295 //Reading the header. 4 bytes for magic, 16 are reserved
1296 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1297 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1298 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1299 int timecnt = ReadBigEndianInt32 (buffer, 32);
1300 int typecnt = ReadBigEndianInt32 (buffer, 36);
1301 int charcnt = ReadBigEndianInt32 (buffer, 40);
1303 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1304 throw new InvalidTimeZoneException ();
1306 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1307 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1308 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1310 if (time_types.Count == 0)
1311 throw new InvalidTimeZoneException ();
1313 if (time_types.Count == 1 && time_types[0].IsDst)
1314 throw new InvalidTimeZoneException ();
1316 TimeSpan baseUtcOffset = new TimeSpan (0);
1317 TimeSpan dstDelta = new TimeSpan (0);
1318 string standardDisplayName = null;
1319 string daylightDisplayName = null;
1320 bool dst_observed = false;
1321 DateTime dst_start = DateTime.MinValue;
1322 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1323 bool storeTransition = false;
1325 for (int i = 0; i < transitions.Count; i++) {
1326 var pair = transitions [i];
1327 DateTime ttime = pair.Key;
1328 TimeType ttype = pair.Value;
1330 if (standardDisplayName != ttype.Name)
1331 standardDisplayName = ttype.Name;
1332 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1333 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1334 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1335 storeTransition = true;
1336 adjustmentRules = new List<AdjustmentRule> ();
1337 dst_observed = false;
1340 //FIXME: check additional fields for this:
1341 //most of the transitions are expressed in GMT
1342 dst_start += baseUtcOffset;
1343 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1345 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1346 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1347 dst_end -= new TimeSpan (24, 0, 0);
1350 * AdjustmentRule specifies a DST period that starts and ends within a year.
1351 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1352 * Thus we fallback to the transitions.
1354 if (dst_start.AddYears (1) < dst_end)
1355 storeTransition = true;
1357 DateTime dateStart, dateEnd;
1358 if (dst_start.Month < 7)
1359 dateStart = new DateTime (dst_start.Year, 1, 1);
1361 dateStart = new DateTime (dst_start.Year, 7, 1);
1363 if (dst_end.Month >= 7)
1364 dateEnd = new DateTime (dst_end.Year, 12, 31);
1366 dateEnd = new DateTime (dst_end.Year, 6, 30);
1369 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1370 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1371 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1372 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1374 dst_observed = false;
1376 if (daylightDisplayName != ttype.Name)
1377 daylightDisplayName = ttype.Name;
1378 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1379 // Round to nearest minute, since it's not possible to create an adjustment rule
1380 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1381 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1382 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1383 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1384 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1388 dst_observed = true;
1393 if (adjustmentRules.Count == 0 && !storeTransition) {
1394 if (standardDisplayName == null) {
1395 var t = time_types [0];
1396 standardDisplayName = t.Name;
1397 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1399 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1401 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1404 if (storeTransition && transitions.Count > 0) {
1405 tz.transitions = transitions;
1406 tz.supportsDaylightSavingTime = true;
1412 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1414 var abbrevs = new Dictionary<int, string> ();
1415 int abbrev_index = 0;
1416 var sb = new StringBuilder ();
1417 for (int i = 0; i < count; i++) {
1418 char c = (char) buffer [index + i];
1422 abbrevs.Add (abbrev_index, sb.ToString ());
1423 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1424 //j == sb.Length empty substring also needs to be added #31432
1425 for (int j = 1; j <= sb.Length; j++)
1426 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1427 abbrev_index = i + 1;
1428 sb = new StringBuilder ();
1434 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1436 var types = new Dictionary<int, TimeType> (count);
1437 for (int i = 0; i < count; i++) {
1438 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1441 // The official tz database contains timezone with GMT offsets
1442 // not only in whole hours/minutes but in seconds. This happens for years
1443 // before 1901. For example
1445 // NAME GMTOFF RULES FORMAT UNTIL
1446 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1448 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1449 // we remove seconds to not crash later
1451 offset = (offset / 60) * 60;
1453 byte is_dst = buffer [index + 6 * i + 4];
1454 byte abbrev = buffer [index + 6 * i + 5];
1455 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1460 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1462 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1463 for (int i = 0; i < count; i++) {
1464 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1465 DateTime ttime = DateTimeFromUnixTime (unixtime);
1466 byte ttype = buffer [index + 4 * count + i];
1467 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1472 static DateTime DateTimeFromUnixTime (long unix_time)
1474 DateTime date_time = new DateTime (1970, 1, 1);
1475 return date_time.AddSeconds (unix_time);
1478 #region reference sources
1479 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1480 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1483 return Local.GetUtcOffset (dateTime, out dst);
1486 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1489 return GetUtcOffset (dateTime, out dst);
1492 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1494 isDaylightSavings = false;
1495 isAmbiguousLocalDst = false;
1496 TimeSpan baseOffset = zone.BaseUtcOffset;
1498 if (zone.IsAmbiguousTime (time)) {
1499 isAmbiguousLocalDst = true;
1500 // return baseOffset;
1503 return zone.GetUtcOffset (time, out isDaylightSavings);
1509 public readonly int Offset;
1510 public readonly bool IsDst;
1513 public TimeType (int offset, bool is_dst, string abbrev)
1515 this.Offset = offset;
1516 this.IsDst = is_dst;
1520 public override string ToString ()
1522 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;