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 !MOBILE || MOBILE_STATIC
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);
166 var tz = Environment.GetEnvironmentVariable ("TZ");
168 if (tz == String.Empty)
171 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
177 var tzFilePaths = new string [] {
179 Path.Combine (TimeZoneDirectory, "localtime")};
181 foreach (var tzFilePath in tzFilePaths) {
183 string tzName = null;
184 if (!TryGetNameFromPath (tzFilePath, out tzName))
186 return FindSystemTimeZoneByFileName (tzName, tzFilePath);
187 } catch (TimeZoneNotFoundException) {
195 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
198 string filepath = Path.Combine (TimeZoneDirectory, id);
199 return FindSystemTimeZoneByFileName (id, filepath);
201 throw new NotImplementedException ();
205 static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
208 if (TimeZoneKey != null) {
209 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
211 systemTimeZones.Add (FindSystemTimeZoneById (id));
220 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
221 foreach (string continent in continents) {
223 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
225 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
226 systemTimeZones.Add (FindSystemTimeZoneById (id));
227 } catch (ArgumentNullException) {
228 } catch (TimeZoneNotFoundException) {
229 } catch (InvalidTimeZoneException) {
230 } catch (Exception) {
237 throw new NotImplementedException ("This method is not implemented for this platform");
242 string standardDisplayName;
243 public string StandardName {
244 get { return standardDisplayName; }
247 bool supportsDaylightSavingTime;
248 public bool SupportsDaylightSavingTime {
249 get { return supportsDaylightSavingTime; }
252 static TimeZoneInfo utc;
253 public static TimeZoneInfo Utc {
256 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
261 static string timeZoneDirectory;
262 static string TimeZoneDirectory {
264 if (timeZoneDirectory == null)
265 timeZoneDirectory = "/usr/share/zoneinfo";
266 return timeZoneDirectory;
270 timeZoneDirectory = value;
274 private AdjustmentRule [] adjustmentRules;
276 #if !MOBILE || MOBILE_STATIC
278 /// Determine whether windows of not (taken Stephane Delcroix's code)
280 private static bool IsWindows
283 int platform = (int) Environment.OSVersion.Platform;
284 return ((platform != 4) && (platform != 6) && (platform != 128));
289 /// Needed to trim misc garbage in MS registry keys
291 private static string TrimSpecial (string str)
296 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
297 var Iend = str.Length - 1;
298 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend]) && str[Iend] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
301 return str.Substring (Istart, Iend-Istart+1);
305 static RegistryKey timeZoneKey;
306 static RegistryKey TimeZoneKey {
308 if (timeZoneKey != null)
313 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
314 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
319 static RegistryKey localZoneKey;
320 static RegistryKey LocalZoneKey {
322 if (localZoneKey != null)
328 return localZoneKey = Registry.LocalMachine.OpenSubKey (
329 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
335 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
337 var resultTicks = date.Ticks + ticks;
338 if (resultTicks < DateTime.MinValue.Ticks) {
339 result = DateTime.SpecifyKind (DateTime.MinValue, kind);
343 if (resultTicks > DateTime.MaxValue.Ticks) {
344 result = DateTime.SpecifyKind (DateTime.MaxValue, kind);
348 result = new DateTime (resultTicks, kind);
352 public static void ClearCachedData ()
356 systemTimeZones = null;
359 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
361 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
364 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
366 if (sourceTimeZone == null)
367 throw new ArgumentNullException ("sourceTimeZone");
369 if (destinationTimeZone == null)
370 throw new ArgumentNullException ("destinationTimeZone");
372 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
373 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
375 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
376 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
378 if (sourceTimeZone.IsInvalidTime (dateTime))
379 throw new ArgumentException ("dateTime parameter is an invalid time");
381 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
384 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
386 if (destinationTimeZone != TimeZoneInfo.Utc) {
387 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
388 if (dateTime.Kind == DateTimeKind.Unspecified)
389 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
395 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
397 if (destinationTimeZone == null)
398 throw new ArgumentNullException("destinationTimeZone");
400 var utcDateTime = dateTimeOffset.UtcDateTime;
403 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
405 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
408 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
410 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
413 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
415 TimeZoneInfo source_tz;
416 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZoneId == TimeZoneInfo.Utc.Id) {
419 source_tz = FindSystemTimeZoneById (sourceTimeZoneId);
422 return ConvertTime (dateTime, source_tz, FindSystemTimeZoneById (destinationTimeZoneId));
425 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
427 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
430 private DateTime ConvertTimeFromUtc (DateTime dateTime)
432 if (dateTime.Kind == DateTimeKind.Local)
433 throw new ArgumentException ("Kind property of dateTime is Local");
435 if (this == TimeZoneInfo.Utc)
436 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
438 var utcOffset = GetUtcOffset (dateTime);
440 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
443 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
444 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
449 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
451 if (destinationTimeZone == null)
452 throw new ArgumentNullException ("destinationTimeZone");
454 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
457 public static DateTime ConvertTimeToUtc (DateTime dateTime)
459 if (dateTime.Kind == DateTimeKind.Utc)
462 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
465 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
467 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
470 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
472 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
475 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
477 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
478 if (sourceTimeZone == null)
479 throw new ArgumentNullException ("sourceTimeZone");
481 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
482 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
484 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
485 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
487 if (sourceTimeZone.IsInvalidTime (dateTime))
488 throw new ArgumentException ("dateTime parameter is an invalid time");
491 if (dateTime.Kind == DateTimeKind.Utc)
495 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
497 DateTime utcDateTime;
498 TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc);
502 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
504 bool isDaylightSavings;
505 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
508 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
510 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
513 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
515 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
518 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
520 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
523 public override bool Equals (object obj)
525 return Equals (obj as TimeZoneInfo);
528 public bool Equals (TimeZoneInfo other)
533 return other.Id == this.Id && HasSameRules (other);
536 public static TimeZoneInfo FindSystemTimeZoneById (string id)
538 //FIXME: this method should check for cached values in systemTimeZones
540 throw new ArgumentNullException ("id");
542 if (TimeZoneKey != null)
544 if (id == "Coordinated Universal Time")
545 id = "UTC"; //windows xp exception for "StandardName" property
546 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
548 throw new TimeZoneNotFoundException ();
549 return FromRegistryKey(id, key);
552 // Local requires special logic that already exists in the Local property (bug #326)
556 return FindSystemTimeZoneByIdCore (id);
560 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
562 if (!File.Exists (filepath))
563 throw new TimeZoneNotFoundException ();
565 using (FileStream stream = File.OpenRead (filepath)) {
566 return BuildFromStream (id, stream);
572 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
574 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
577 throw new InvalidTimeZoneException ();
579 int bias = BitConverter.ToInt32 (reg_tzi, 0);
580 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
582 string display_name = (string) key.GetValue ("Display");
583 string standard_name = (string) key.GetValue ("Std");
584 string daylight_name = (string) key.GetValue ("Dlt");
586 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
588 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
589 if (dst_key != null) {
590 int first_year = (int) dst_key.GetValue ("FirstEntry");
591 int last_year = (int) dst_key.GetValue ("LastEntry");
594 for (year=first_year; year<=last_year; year++) {
595 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
596 if (dst_tzi != null) {
597 int start_year = year == first_year ? 1 : year;
598 int end_year = year == last_year ? 9999 : year;
599 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
604 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
606 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
609 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
611 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
612 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
614 int standard_year = BitConverter.ToInt16 (buffer, 12);
615 int standard_month = BitConverter.ToInt16 (buffer, 14);
616 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
617 int standard_day = BitConverter.ToInt16 (buffer, 18);
618 int standard_hour = BitConverter.ToInt16 (buffer, 20);
619 int standard_minute = BitConverter.ToInt16 (buffer, 22);
620 int standard_second = BitConverter.ToInt16 (buffer, 24);
621 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
623 int daylight_year = BitConverter.ToInt16 (buffer, 28);
624 int daylight_month = BitConverter.ToInt16 (buffer, 30);
625 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
626 int daylight_day = BitConverter.ToInt16 (buffer, 34);
627 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
628 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
629 int daylight_second = BitConverter.ToInt16 (buffer, 40);
630 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
632 if (standard_month == 0 || daylight_month == 0)
636 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
637 TransitionTime start_transition_time;
639 if (daylight_year == 0) {
640 start_date = new DateTime (start_year, 1, 1);
641 start_transition_time = TransitionTime.CreateFloatingDateRule (
642 start_timeofday, daylight_month, daylight_day,
643 (DayOfWeek) daylight_dayofweek);
646 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
647 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
648 start_transition_time = TransitionTime.CreateFixedDateRule (
649 start_timeofday, daylight_month, daylight_day);
653 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
654 TransitionTime end_transition_time;
656 if (standard_year == 0) {
657 end_date = new DateTime (end_year, 12, 31);
658 end_transition_time = TransitionTime.CreateFloatingDateRule (
659 end_timeofday, standard_month, standard_day,
660 (DayOfWeek) standard_dayofweek);
663 end_date = new DateTime (standard_year, standard_month, standard_day,
664 standard_hour, standard_minute, standard_second, standard_millisecond);
665 end_transition_time = TransitionTime.CreateFixedDateRule (
666 end_timeofday, standard_month, standard_day);
669 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
671 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
672 start_date, end_date, daylight_delta,
673 start_transition_time, end_transition_time));
677 public AdjustmentRule [] GetAdjustmentRules ()
679 if (!supportsDaylightSavingTime || adjustmentRules == null)
680 return new AdjustmentRule [0];
682 return (AdjustmentRule []) adjustmentRules.Clone ();
685 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
687 if (!IsAmbiguousTime (dateTime))
688 throw new ArgumentException ("dateTime is not an ambiguous time");
690 AdjustmentRule rule = GetApplicableRule (dateTime);
692 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
694 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
697 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
699 if (!IsAmbiguousTime (dateTimeOffset))
700 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
702 throw new NotImplementedException ();
705 public override int GetHashCode ()
707 int hash_code = Id.GetHashCode ();
708 foreach (AdjustmentRule rule in GetAdjustmentRules ())
709 hash_code ^= rule.GetHashCode ();
713 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
716 throw new ArgumentNullException ("info");
717 info.AddValue ("Id", id);
718 info.AddValue ("DisplayName", displayName);
719 info.AddValue ("StandardName", standardDisplayName);
720 info.AddValue ("DaylightName", daylightDisplayName);
721 info.AddValue ("BaseUtcOffset", baseUtcOffset);
722 info.AddValue ("AdjustmentRules", adjustmentRules);
723 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
726 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
728 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
730 if (systemTimeZones == null) {
731 var tz = new List<TimeZoneInfo> ();
732 GetSystemTimeZonesCore (tz);
733 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
736 return systemTimeZones;
739 public TimeSpan GetUtcOffset (DateTime dateTime)
742 return GetUtcOffset (dateTime, out isDST);
745 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
748 return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
751 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
755 TimeZoneInfo tz = this;
756 if (dateTime.Kind == DateTimeKind.Utc)
757 tz = TimeZoneInfo.Utc;
759 if (dateTime.Kind == DateTimeKind.Local)
760 tz = TimeZoneInfo.Local;
763 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
770 DateTime utcDateTime;
771 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
772 return BaseUtcOffset;
774 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
777 // This is an helper method used by the method above, do not use this on its own.
778 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
780 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
781 throw new Exception ();
785 if (tz == TimeZoneInfo.Utc)
786 return TimeSpan.Zero;
789 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
792 if (dateTime.Kind == DateTimeKind.Utc) {
793 var utcRule = tz.GetApplicableRule (dateTime);
794 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
796 return tz.BaseUtcOffset + utcRule.DaylightDelta;
799 return tz.BaseUtcOffset;
802 DateTime stdUtcDateTime;
803 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
804 return tz.BaseUtcOffset;
806 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
808 DateTime dstUtcDateTime = DateTime.MinValue;
809 if (tzRule != null) {
810 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
811 return tz.BaseUtcOffset;
814 if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime)) {
815 // Replicate what .NET does when given a time which falls into the hour which is lost when
816 // DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
817 // DST delta while in that hour.
819 if (tz.IsInDST (tzRule, dstUtcDateTime)) {
820 return tz.BaseUtcOffset + tzRule.DaylightDelta;
822 return tz.BaseUtcOffset;
826 return tz.BaseUtcOffset;
829 public bool HasSameRules (TimeZoneInfo other)
832 throw new ArgumentNullException ("other");
834 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
837 if (this.adjustmentRules == null)
840 if (this.BaseUtcOffset != other.BaseUtcOffset)
843 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
846 for (int i = 0; i < adjustmentRules.Length; i++) {
847 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
854 public bool IsAmbiguousTime (DateTime dateTime)
856 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
857 throw new ArgumentException ("Kind is Local and time is Invalid");
859 if (this == TimeZoneInfo.Utc)
862 if (dateTime.Kind == DateTimeKind.Utc)
863 dateTime = ConvertTimeFromUtc (dateTime);
865 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
866 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
868 AdjustmentRule rule = GetApplicableRule (dateTime);
870 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
871 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
878 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
880 throw new NotImplementedException ();
883 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
885 // Check whether we're in the dateTime year's DST period
886 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
889 // We might be in the dateTime previous year's DST period
890 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
893 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
895 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
896 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
897 if (dateTime.Kind == DateTimeKind.Utc) {
898 DST_start -= BaseUtcOffset;
899 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
902 return (dateTime >= DST_start && dateTime < DST_end);
905 public bool IsDaylightSavingTime (DateTime dateTime)
907 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
908 throw new ArgumentException ("dateTime is invalid and Kind is Local");
910 if (this == TimeZoneInfo.Utc)
913 if (!SupportsDaylightSavingTime)
917 GetUtcOffset (dateTime, out isDst);
922 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
924 return IsDaylightSavingTime (dateTime);
927 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
929 return IsDaylightSavingTime (dateTimeOffset.DateTime);
932 internal DaylightTime GetDaylightChanges (int year)
934 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
935 TimeSpan delta = new TimeSpan ();
937 if (transitions != null) {
938 end = DateTime.MaxValue;
939 for (var i = transitions.Count - 1; i >= 0; i--) {
940 var pair = transitions [i];
941 DateTime ttime = pair.Key;
942 TimeType ttype = pair.Value;
944 if (ttime.Year > year)
946 if (ttime.Year < year)
950 // DaylightTime.Delta is relative to the current BaseUtcOffset.
951 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
958 // DaylightTime.Start is relative to the Standard time.
959 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
960 start = DateTime.MinValue;
962 // DaylightTime.End is relative to the DST time.
963 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
964 end = DateTime.MinValue;
966 AdjustmentRule first = null, last = null;
968 // Rule start/end dates are either very specific or very broad depending on the platform
969 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
970 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
971 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
973 foreach (var rule in GetAdjustmentRules ()) {
974 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
976 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
978 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
982 if (first == null || last == null)
983 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
985 start = TransitionPoint (first.DaylightTransitionStart, year);
986 end = TransitionPoint (last.DaylightTransitionEnd, year);
987 delta = first.DaylightDelta;
990 if (start == DateTime.MinValue || end == DateTime.MinValue)
991 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
993 return new DaylightTime (start, end, delta);
996 public bool IsInvalidTime (DateTime dateTime)
998 if (dateTime.Kind == DateTimeKind.Utc)
1000 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1003 AdjustmentRule rule = GetApplicableRule (dateTime);
1005 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1006 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1013 void IDeserializationCallback.OnDeserialization (object sender)
1016 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1017 } catch (ArgumentException ex) {
1018 throw new SerializationException ("invalid serialization data", ex);
1022 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1025 throw new ArgumentNullException ("id");
1027 if (id == String.Empty)
1028 throw new ArgumentException ("id parameter is an empty string");
1030 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1031 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1033 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1034 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1038 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1041 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1042 AdjustmentRule prev = null;
1043 foreach (AdjustmentRule current in adjustmentRules) {
1044 if (current == null)
1045 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1047 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1048 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1049 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;");
1051 if (prev != null && prev.DateStart > current.DateStart)
1052 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1054 if (prev != null && prev.DateEnd > current.DateStart)
1055 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1057 if (prev != null && prev.DateEnd == current.DateStart)
1058 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1065 public override string ToString ()
1070 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1073 throw new ArgumentNullException ("info");
1074 id = (string) info.GetValue ("Id", typeof (string));
1075 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1076 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1077 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1078 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1079 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1080 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1083 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1086 throw new ArgumentNullException ("id");
1088 if (id == String.Empty)
1089 throw new ArgumentException ("id parameter is an empty string");
1091 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1092 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1094 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1095 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1099 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1102 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1104 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1105 AdjustmentRule prev = null;
1106 foreach (AdjustmentRule current in adjustmentRules) {
1107 if (current == null)
1108 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1110 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1111 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1112 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;");
1114 if (prev != null && prev.DateStart > current.DateStart)
1115 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1117 if (prev != null && prev.DateEnd > current.DateStart)
1118 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1120 if (prev != null && prev.DateEnd == current.DateStart)
1121 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1126 supportsDaylightSavingTime = false;
1130 this.baseUtcOffset = baseUtcOffset;
1131 this.displayName = displayName ?? id;
1132 this.standardDisplayName = standardDisplayName ?? id;
1133 this.daylightDisplayName = daylightDisplayName;
1134 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1135 this.adjustmentRules = adjustmentRules;
1138 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1140 //Applicable rules are in standard time
1141 DateTime date = dateTime;
1143 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1144 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1146 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1147 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1151 // get the date component of the datetime
1154 if (adjustmentRules != null) {
1155 foreach (AdjustmentRule rule in adjustmentRules) {
1156 if (rule.DateStart > date)
1158 if (rule.DateEnd < date)
1166 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1168 offset = BaseUtcOffset;
1171 if (transitions == null)
1174 //Transitions are in UTC
1175 DateTime date = dateTime;
1177 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1178 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1182 if (dateTime.Kind != DateTimeKind.Utc) {
1183 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1187 var inDelta = false;
1188 for (var i = transitions.Count - 1; i >= 0; i--) {
1189 var pair = transitions [i];
1190 DateTime ttime = pair.Key;
1191 TimeType ttype = pair.Value;
1193 var delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
1195 if ((ttime + delta) > date) {
1196 inDelta = ttime <= date;
1200 offset = new TimeSpan (0, 0, ttype.Offset);
1202 // Replicate what .NET does when given a time which falls into the hour which is lost when
1203 // DST starts. isDST should be true but the offset should be the non-DST offset.
1204 isDst = transitions [i - 1].Value.IsDst;
1206 isDst = ttype.IsDst;
1215 private static DateTime TransitionPoint (TransitionTime transition, int year)
1217 if (transition.IsFixedDateRule)
1218 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1220 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1221 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1222 if (day > DateTime.DaysInMonth (year, transition.Month))
1226 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1229 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1231 AdjustmentRule prev = null;
1232 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1233 if (prev != null && prev.DateEnd > current.DateStart) {
1234 adjustmentRules.Remove (current);
1238 return adjustmentRules;
1241 #if LIBC || MONOTOUCH
1242 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1244 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1246 byte [] buffer = new byte [BUFFER_SIZE];
1247 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1249 if (!ValidTZFile (buffer, length))
1250 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1253 return ParseTZBuffer (id, buffer, length);
1254 } catch (InvalidTimeZoneException) {
1256 } catch (Exception e) {
1257 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1261 private static bool ValidTZFile (byte [] buffer, int length)
1263 StringBuilder magic = new StringBuilder ();
1265 for (int i = 0; i < 4; i++)
1266 magic.Append ((char)buffer [i]);
1268 if (magic.ToString () != "TZif")
1271 if (length >= BUFFER_SIZE)
1277 static int SwapInt32 (int i)
1279 return (((i >> 24) & 0xff)
1280 | ((i >> 8) & 0xff00)
1281 | ((i << 8) & 0xff0000)
1282 | (((i & 0xff) << 24)));
1285 static int ReadBigEndianInt32 (byte [] buffer, int start)
1287 int i = BitConverter.ToInt32 (buffer, start);
1288 if (!BitConverter.IsLittleEndian)
1291 return SwapInt32 (i);
1294 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1296 //Reading the header. 4 bytes for magic, 16 are reserved
1297 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1298 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1299 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1300 int timecnt = ReadBigEndianInt32 (buffer, 32);
1301 int typecnt = ReadBigEndianInt32 (buffer, 36);
1302 int charcnt = ReadBigEndianInt32 (buffer, 40);
1304 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1305 throw new InvalidTimeZoneException ();
1307 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1308 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1309 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1311 if (time_types.Count == 0)
1312 throw new InvalidTimeZoneException ();
1314 if (time_types.Count == 1 && time_types[0].IsDst)
1315 throw new InvalidTimeZoneException ();
1317 TimeSpan baseUtcOffset = new TimeSpan (0);
1318 TimeSpan dstDelta = new TimeSpan (0);
1319 string standardDisplayName = null;
1320 string daylightDisplayName = null;
1321 bool dst_observed = false;
1322 DateTime dst_start = DateTime.MinValue;
1323 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1324 bool storeTransition = false;
1326 for (int i = 0; i < transitions.Count; i++) {
1327 var pair = transitions [i];
1328 DateTime ttime = pair.Key;
1329 TimeType ttype = pair.Value;
1331 if (standardDisplayName != ttype.Name)
1332 standardDisplayName = ttype.Name;
1333 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1334 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1335 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1336 storeTransition = true;
1337 adjustmentRules = new List<AdjustmentRule> ();
1338 dst_observed = false;
1341 //FIXME: check additional fields for this:
1342 //most of the transitions are expressed in GMT
1343 dst_start += baseUtcOffset;
1344 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1346 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1347 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1348 dst_end -= new TimeSpan (24, 0, 0);
1351 * AdjustmentRule specifies a DST period that starts and ends within a year.
1352 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1353 * Thus we fallback to the transitions.
1355 if (dst_start.AddYears (1) < dst_end)
1356 storeTransition = true;
1358 DateTime dateStart, dateEnd;
1359 if (dst_start.Month < 7)
1360 dateStart = new DateTime (dst_start.Year, 1, 1);
1362 dateStart = new DateTime (dst_start.Year, 7, 1);
1364 if (dst_end.Month >= 7)
1365 dateEnd = new DateTime (dst_end.Year, 12, 31);
1367 dateEnd = new DateTime (dst_end.Year, 6, 30);
1370 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1371 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1372 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1373 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1375 dst_observed = false;
1377 if (daylightDisplayName != ttype.Name)
1378 daylightDisplayName = ttype.Name;
1379 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1380 // Round to nearest minute, since it's not possible to create an adjustment rule
1381 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1382 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1383 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1384 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1385 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1389 dst_observed = true;
1394 if (adjustmentRules.Count == 0 && !storeTransition) {
1395 if (standardDisplayName == null) {
1396 var t = time_types [0];
1397 standardDisplayName = t.Name;
1398 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1400 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1402 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1405 if (storeTransition && transitions.Count > 0) {
1406 tz.transitions = transitions;
1407 tz.supportsDaylightSavingTime = true;
1413 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1415 var abbrevs = new Dictionary<int, string> ();
1416 int abbrev_index = 0;
1417 var sb = new StringBuilder ();
1418 for (int i = 0; i < count; i++) {
1419 char c = (char) buffer [index + i];
1423 abbrevs.Add (abbrev_index, sb.ToString ());
1424 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1425 //j == sb.Length empty substring also needs to be added #31432
1426 for (int j = 1; j <= sb.Length; j++)
1427 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1428 abbrev_index = i + 1;
1429 sb = new StringBuilder ();
1435 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1437 var types = new Dictionary<int, TimeType> (count);
1438 for (int i = 0; i < count; i++) {
1439 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1442 // The official tz database contains timezone with GMT offsets
1443 // not only in whole hours/minutes but in seconds. This happens for years
1444 // before 1901. For example
1446 // NAME GMTOFF RULES FORMAT UNTIL
1447 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1449 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1450 // we remove seconds to not crash later
1452 offset = (offset / 60) * 60;
1454 byte is_dst = buffer [index + 6 * i + 4];
1455 byte abbrev = buffer [index + 6 * i + 5];
1456 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1461 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1463 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1464 for (int i = 0; i < count; i++) {
1465 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1466 DateTime ttime = DateTimeFromUnixTime (unixtime);
1467 byte ttype = buffer [index + 4 * count + i];
1468 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1473 static DateTime DateTimeFromUnixTime (long unix_time)
1475 DateTime date_time = new DateTime (1970, 1, 1);
1476 return date_time.AddSeconds (unix_time);
1479 #region reference sources
1480 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1481 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1484 return Local.GetUtcOffset (dateTime, out dst);
1487 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1490 return GetUtcOffset (dateTime, out dst);
1493 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1495 isDaylightSavings = false;
1496 isAmbiguousLocalDst = false;
1497 TimeSpan baseOffset = zone.BaseUtcOffset;
1499 if (zone.IsAmbiguousTime (time)) {
1500 isAmbiguousLocalDst = true;
1504 return zone.GetUtcOffset (time, out isDaylightSavings);
1510 public readonly int Offset;
1511 public readonly bool IsDst;
1514 public TimeType (int offset, bool is_dst, string abbrev)
1516 this.Offset = offset;
1517 this.IsDst = is_dst;
1521 public override string ToString ()
1523 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;