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 ()
155 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
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)
209 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
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
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, stdUtcDateTime)) {
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);
917 return (dateTime >= DST_start && dateTime < DST_end);
920 public bool IsDaylightSavingTime (DateTime dateTime)
922 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
923 throw new ArgumentException ("dateTime is invalid and Kind is Local");
925 if (this == TimeZoneInfo.Utc)
928 if (!SupportsDaylightSavingTime)
932 GetUtcOffset (dateTime, out isDst);
937 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
939 return IsDaylightSavingTime (dateTime);
942 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
944 return IsDaylightSavingTime (dateTimeOffset.DateTime);
947 internal DaylightTime GetDaylightChanges (int year)
949 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
950 TimeSpan delta = new TimeSpan ();
952 if (transitions != null) {
953 end = DateTime.MaxValue;
954 for (var i = transitions.Count - 1; i >= 0; i--) {
955 var pair = transitions [i];
956 DateTime ttime = pair.Key;
957 TimeType ttype = pair.Value;
959 if (ttime.Year > year)
961 if (ttime.Year < year)
965 // DaylightTime.Delta is relative to the current BaseUtcOffset.
966 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
973 // DaylightTime.Start is relative to the Standard time.
974 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
975 start = DateTime.MinValue;
977 // DaylightTime.End is relative to the DST time.
978 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
979 end = DateTime.MinValue;
981 AdjustmentRule first = null, last = null;
983 // Rule start/end dates are either very specific or very broad depending on the platform
984 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
985 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
986 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
988 foreach (var rule in GetAdjustmentRules ()) {
989 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
991 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
993 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
997 if (first == null || last == null)
998 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1000 start = TransitionPoint (first.DaylightTransitionStart, year);
1001 end = TransitionPoint (last.DaylightTransitionEnd, year);
1002 delta = first.DaylightDelta;
1005 if (start == DateTime.MinValue || end == DateTime.MinValue)
1006 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1008 return new DaylightTime (start, end, delta);
1011 public bool IsInvalidTime (DateTime dateTime)
1013 if (dateTime.Kind == DateTimeKind.Utc)
1015 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1018 AdjustmentRule rule = GetApplicableRule (dateTime);
1020 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1021 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1028 void IDeserializationCallback.OnDeserialization (object sender)
1031 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1032 } catch (ArgumentException ex) {
1033 throw new SerializationException ("invalid serialization data", ex);
1037 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1040 throw new ArgumentNullException ("id");
1042 if (id == String.Empty)
1043 throw new ArgumentException ("id parameter is an empty string");
1045 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1046 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1048 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1049 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1053 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1056 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1057 AdjustmentRule prev = null;
1058 foreach (AdjustmentRule current in adjustmentRules) {
1059 if (current == null)
1060 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1062 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1063 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1064 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;");
1066 if (prev != null && prev.DateStart > current.DateStart)
1067 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1069 if (prev != null && prev.DateEnd > current.DateStart)
1070 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1072 if (prev != null && prev.DateEnd == current.DateStart)
1073 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1080 public override string ToString ()
1085 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1088 throw new ArgumentNullException ("info");
1089 id = (string) info.GetValue ("Id", typeof (string));
1090 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1091 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1092 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1093 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1094 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1095 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1098 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1101 throw new ArgumentNullException ("id");
1103 if (id == String.Empty)
1104 throw new ArgumentException ("id parameter is an empty string");
1106 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1107 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1109 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1110 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1114 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1117 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1119 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1120 AdjustmentRule prev = null;
1121 foreach (AdjustmentRule current in adjustmentRules) {
1122 if (current == null)
1123 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1125 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1126 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1127 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;");
1129 if (prev != null && prev.DateStart > current.DateStart)
1130 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1132 if (prev != null && prev.DateEnd > current.DateStart)
1133 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1135 if (prev != null && prev.DateEnd == current.DateStart)
1136 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1141 supportsDaylightSavingTime = false;
1145 this.baseUtcOffset = baseUtcOffset;
1146 this.displayName = displayName ?? id;
1147 this.standardDisplayName = standardDisplayName ?? id;
1148 this.daylightDisplayName = daylightDisplayName;
1149 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1150 this.adjustmentRules = adjustmentRules;
1153 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1155 //Applicable rules are in standard time
1156 DateTime date = dateTime;
1158 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1159 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1161 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1162 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1166 // get the date component of the datetime
1169 if (adjustmentRules != null) {
1170 foreach (AdjustmentRule rule in adjustmentRules) {
1171 if (rule.DateStart > date)
1173 if (rule.DateEnd < date)
1181 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1183 offset = BaseUtcOffset;
1186 if (transitions == null)
1189 //Transitions are in UTC
1190 DateTime date = dateTime;
1192 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1193 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1197 if (dateTime.Kind != DateTimeKind.Utc) {
1198 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1202 var inDelta = false;
1203 for (var i = transitions.Count - 1; i >= 0; i--) {
1204 var pair = transitions [i];
1205 DateTime ttime = pair.Key;
1206 TimeType ttype = pair.Value;
1208 var delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
1210 if ((ttime + delta) > date) {
1211 inDelta = ttime <= date;
1215 offset = new TimeSpan (0, 0, ttype.Offset);
1217 // Replicate what .NET does when given a time which falls into the hour which is lost when
1218 // DST starts. isDST should be true but the offset should be the non-DST offset.
1219 isDst = transitions [i - 1].Value.IsDst;
1221 isDst = ttype.IsDst;
1230 private static DateTime TransitionPoint (TransitionTime transition, int year)
1232 if (transition.IsFixedDateRule)
1233 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1235 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1236 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1237 if (day > DateTime.DaysInMonth (year, transition.Month))
1241 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1244 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1246 AdjustmentRule prev = null;
1247 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1248 if (prev != null && prev.DateEnd > current.DateStart) {
1249 adjustmentRules.Remove (current);
1253 return adjustmentRules;
1256 #if LIBC || MONOTOUCH
1257 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1259 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1261 byte [] buffer = new byte [BUFFER_SIZE];
1262 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1264 if (!ValidTZFile (buffer, length))
1265 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1268 return ParseTZBuffer (id, buffer, length);
1269 } catch (InvalidTimeZoneException) {
1271 } catch (Exception e) {
1272 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1276 private static bool ValidTZFile (byte [] buffer, int length)
1278 StringBuilder magic = new StringBuilder ();
1280 for (int i = 0; i < 4; i++)
1281 magic.Append ((char)buffer [i]);
1283 if (magic.ToString () != "TZif")
1286 if (length >= BUFFER_SIZE)
1292 static int SwapInt32 (int i)
1294 return (((i >> 24) & 0xff)
1295 | ((i >> 8) & 0xff00)
1296 | ((i << 8) & 0xff0000)
1297 | (((i & 0xff) << 24)));
1300 static int ReadBigEndianInt32 (byte [] buffer, int start)
1302 int i = BitConverter.ToInt32 (buffer, start);
1303 if (!BitConverter.IsLittleEndian)
1306 return SwapInt32 (i);
1309 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1311 //Reading the header. 4 bytes for magic, 16 are reserved
1312 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1313 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1314 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1315 int timecnt = ReadBigEndianInt32 (buffer, 32);
1316 int typecnt = ReadBigEndianInt32 (buffer, 36);
1317 int charcnt = ReadBigEndianInt32 (buffer, 40);
1319 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1320 throw new InvalidTimeZoneException ();
1322 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1323 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1324 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1326 if (time_types.Count == 0)
1327 throw new InvalidTimeZoneException ();
1329 if (time_types.Count == 1 && time_types[0].IsDst)
1330 throw new InvalidTimeZoneException ();
1332 TimeSpan baseUtcOffset = new TimeSpan (0);
1333 TimeSpan dstDelta = new TimeSpan (0);
1334 string standardDisplayName = null;
1335 string daylightDisplayName = null;
1336 bool dst_observed = false;
1337 DateTime dst_start = DateTime.MinValue;
1338 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1339 bool storeTransition = false;
1341 for (int i = 0; i < transitions.Count; i++) {
1342 var pair = transitions [i];
1343 DateTime ttime = pair.Key;
1344 TimeType ttype = pair.Value;
1346 if (standardDisplayName != ttype.Name)
1347 standardDisplayName = ttype.Name;
1348 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1349 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1350 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1351 storeTransition = true;
1352 adjustmentRules = new List<AdjustmentRule> ();
1353 dst_observed = false;
1356 //FIXME: check additional fields for this:
1357 //most of the transitions are expressed in GMT
1358 dst_start += baseUtcOffset;
1359 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1361 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1362 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1363 dst_end -= new TimeSpan (24, 0, 0);
1366 * AdjustmentRule specifies a DST period that starts and ends within a year.
1367 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1368 * Thus we fallback to the transitions.
1370 if (dst_start.AddYears (1) < dst_end)
1371 storeTransition = true;
1373 DateTime dateStart, dateEnd;
1374 if (dst_start.Month < 7)
1375 dateStart = new DateTime (dst_start.Year, 1, 1);
1377 dateStart = new DateTime (dst_start.Year, 7, 1);
1379 if (dst_end.Month >= 7)
1380 dateEnd = new DateTime (dst_end.Year, 12, 31);
1382 dateEnd = new DateTime (dst_end.Year, 6, 30);
1385 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1386 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1387 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1388 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1390 dst_observed = false;
1392 if (daylightDisplayName != ttype.Name)
1393 daylightDisplayName = ttype.Name;
1394 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1395 // Round to nearest minute, since it's not possible to create an adjustment rule
1396 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1397 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1398 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1399 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1400 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1404 dst_observed = true;
1409 if (adjustmentRules.Count == 0 && !storeTransition) {
1410 if (standardDisplayName == null) {
1411 var t = time_types [0];
1412 standardDisplayName = t.Name;
1413 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1415 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1417 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1420 if (storeTransition && transitions.Count > 0) {
1421 tz.transitions = transitions;
1422 tz.supportsDaylightSavingTime = true;
1428 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1430 var abbrevs = new Dictionary<int, string> ();
1431 int abbrev_index = 0;
1432 var sb = new StringBuilder ();
1433 for (int i = 0; i < count; i++) {
1434 char c = (char) buffer [index + i];
1438 abbrevs.Add (abbrev_index, sb.ToString ());
1439 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1440 //j == sb.Length empty substring also needs to be added #31432
1441 for (int j = 1; j <= sb.Length; j++)
1442 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1443 abbrev_index = i + 1;
1444 sb = new StringBuilder ();
1450 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1452 var types = new Dictionary<int, TimeType> (count);
1453 for (int i = 0; i < count; i++) {
1454 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1457 // The official tz database contains timezone with GMT offsets
1458 // not only in whole hours/minutes but in seconds. This happens for years
1459 // before 1901. For example
1461 // NAME GMTOFF RULES FORMAT UNTIL
1462 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1464 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1465 // we remove seconds to not crash later
1467 offset = (offset / 60) * 60;
1469 byte is_dst = buffer [index + 6 * i + 4];
1470 byte abbrev = buffer [index + 6 * i + 5];
1471 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1476 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1478 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1479 for (int i = 0; i < count; i++) {
1480 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1481 DateTime ttime = DateTimeFromUnixTime (unixtime);
1482 byte ttype = buffer [index + 4 * count + i];
1483 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1488 static DateTime DateTimeFromUnixTime (long unix_time)
1490 DateTime date_time = new DateTime (1970, 1, 1);
1491 return date_time.AddSeconds (unix_time);
1494 #region reference sources
1495 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1496 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1499 return Local.GetUtcOffset (dateTime, out dst);
1502 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1505 return GetUtcOffset (dateTime, out dst);
1508 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1510 isDaylightSavings = false;
1511 isAmbiguousLocalDst = false;
1512 TimeSpan baseOffset = zone.BaseUtcOffset;
1514 if (zone.IsAmbiguousTime (time)) {
1515 isAmbiguousLocalDst = true;
1519 return zone.GetUtcOffset (time, out isDaylightSavings);
1525 public readonly int Offset;
1526 public readonly bool IsDst;
1529 public TimeType (int offset, bool is_dst, string abbrev)
1531 this.Offset = offset;
1532 this.IsDst = is_dst;
1536 public override string ToString ()
1538 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;