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 e) {
109 readlinkNotFound = true;
111 } catch (EntryPointNotFoundException e) {
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 foreach (var rule in GetAdjustmentRules ()) {
969 if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
971 if (rule.DateStart.Year == year)
973 if (rule.DateEnd.Year == year)
977 if (first == null || last == null)
978 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
980 start = TransitionPoint (first.DaylightTransitionStart, year);
981 end = TransitionPoint (last.DaylightTransitionEnd, year);
982 delta = first.DaylightDelta;
985 if (start == DateTime.MinValue || end == DateTime.MinValue)
986 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
988 return new DaylightTime (start, end, delta);
991 public bool IsInvalidTime (DateTime dateTime)
993 if (dateTime.Kind == DateTimeKind.Utc)
995 if (dateTime.Kind == DateTimeKind.Local && this != Local)
998 AdjustmentRule rule = GetApplicableRule (dateTime);
1000 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1001 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1008 void IDeserializationCallback.OnDeserialization (object sender)
1011 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1012 } catch (ArgumentException ex) {
1013 throw new SerializationException ("invalid serialization data", ex);
1017 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1020 throw new ArgumentNullException ("id");
1022 if (id == String.Empty)
1023 throw new ArgumentException ("id parameter is an empty string");
1025 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1026 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1028 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1029 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1033 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1036 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1037 AdjustmentRule prev = null;
1038 foreach (AdjustmentRule current in adjustmentRules) {
1039 if (current == null)
1040 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1042 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1043 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1044 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;");
1046 if (prev != null && prev.DateStart > current.DateStart)
1047 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1049 if (prev != null && prev.DateEnd > current.DateStart)
1050 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1052 if (prev != null && prev.DateEnd == current.DateStart)
1053 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1060 public override string ToString ()
1065 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1068 throw new ArgumentNullException ("info");
1069 id = (string) info.GetValue ("Id", typeof (string));
1070 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1071 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1072 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1073 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1074 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1075 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1078 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1081 throw new ArgumentNullException ("id");
1083 if (id == String.Empty)
1084 throw new ArgumentException ("id parameter is an empty string");
1086 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1087 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1089 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1090 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1094 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1097 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1099 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1100 AdjustmentRule prev = null;
1101 foreach (AdjustmentRule current in adjustmentRules) {
1102 if (current == null)
1103 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1105 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1106 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1107 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;");
1109 if (prev != null && prev.DateStart > current.DateStart)
1110 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1112 if (prev != null && prev.DateEnd > current.DateStart)
1113 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1115 if (prev != null && prev.DateEnd == current.DateStart)
1116 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1121 supportsDaylightSavingTime = false;
1125 this.baseUtcOffset = baseUtcOffset;
1126 this.displayName = displayName ?? id;
1127 this.standardDisplayName = standardDisplayName ?? id;
1128 this.daylightDisplayName = daylightDisplayName;
1129 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1130 this.adjustmentRules = adjustmentRules;
1133 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1135 //Applicable rules are in standard time
1136 DateTime date = dateTime;
1138 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1139 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1141 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1142 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1146 // get the date component of the datetime
1149 if (adjustmentRules != null) {
1150 foreach (AdjustmentRule rule in adjustmentRules) {
1151 if (rule.DateStart > date)
1153 if (rule.DateEnd < date)
1161 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1163 offset = BaseUtcOffset;
1166 if (transitions == null)
1169 //Transitions are in UTC
1170 DateTime date = dateTime;
1172 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1173 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1177 if (dateTime.Kind != DateTimeKind.Utc) {
1178 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1182 var inDelta = false;
1183 for (var i = transitions.Count - 1; i >= 0; i--) {
1184 var pair = transitions [i];
1185 DateTime ttime = pair.Key;
1186 TimeType ttype = pair.Value;
1188 var delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
1190 if ((ttime + delta) > date) {
1191 inDelta = ttime <= date;
1195 offset = new TimeSpan (0, 0, ttype.Offset);
1197 // Replicate what .NET does when given a time which falls into the hour which is lost when
1198 // DST starts. isDST should be true but the offset should be the non-DST offset.
1199 isDst = transitions [i - 1].Value.IsDst;
1201 isDst = ttype.IsDst;
1210 private static DateTime TransitionPoint (TransitionTime transition, int year)
1212 if (transition.IsFixedDateRule)
1213 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1215 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1216 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1217 if (day > DateTime.DaysInMonth (year, transition.Month))
1221 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1224 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1226 AdjustmentRule prev = null;
1227 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1228 if (prev != null && prev.DateEnd > current.DateStart) {
1229 adjustmentRules.Remove (current);
1233 return adjustmentRules;
1236 #if LIBC || MONOTOUCH
1237 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1239 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1241 byte [] buffer = new byte [BUFFER_SIZE];
1242 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1244 if (!ValidTZFile (buffer, length))
1245 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1248 return ParseTZBuffer (id, buffer, length);
1249 } catch (InvalidTimeZoneException) {
1251 } catch (Exception e) {
1252 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1256 private static bool ValidTZFile (byte [] buffer, int length)
1258 StringBuilder magic = new StringBuilder ();
1260 for (int i = 0; i < 4; i++)
1261 magic.Append ((char)buffer [i]);
1263 if (magic.ToString () != "TZif")
1266 if (length >= BUFFER_SIZE)
1272 static int SwapInt32 (int i)
1274 return (((i >> 24) & 0xff)
1275 | ((i >> 8) & 0xff00)
1276 | ((i << 8) & 0xff0000)
1277 | (((i & 0xff) << 24)));
1280 static int ReadBigEndianInt32 (byte [] buffer, int start)
1282 int i = BitConverter.ToInt32 (buffer, start);
1283 if (!BitConverter.IsLittleEndian)
1286 return SwapInt32 (i);
1289 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1291 //Reading the header. 4 bytes for magic, 16 are reserved
1292 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1293 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1294 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1295 int timecnt = ReadBigEndianInt32 (buffer, 32);
1296 int typecnt = ReadBigEndianInt32 (buffer, 36);
1297 int charcnt = ReadBigEndianInt32 (buffer, 40);
1299 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1300 throw new InvalidTimeZoneException ();
1302 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1303 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1304 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1306 if (time_types.Count == 0)
1307 throw new InvalidTimeZoneException ();
1309 if (time_types.Count == 1 && time_types[0].IsDst)
1310 throw new InvalidTimeZoneException ();
1312 TimeSpan baseUtcOffset = new TimeSpan (0);
1313 TimeSpan dstDelta = new TimeSpan (0);
1314 string standardDisplayName = null;
1315 string daylightDisplayName = null;
1316 bool dst_observed = false;
1317 DateTime dst_start = DateTime.MinValue;
1318 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1319 bool storeTransition = false;
1321 for (int i = 0; i < transitions.Count; i++) {
1322 var pair = transitions [i];
1323 DateTime ttime = pair.Key;
1324 TimeType ttype = pair.Value;
1326 if (standardDisplayName != ttype.Name)
1327 standardDisplayName = ttype.Name;
1328 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1329 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1330 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1331 storeTransition = true;
1332 adjustmentRules = new List<AdjustmentRule> ();
1333 dst_observed = false;
1336 //FIXME: check additional fields for this:
1337 //most of the transitions are expressed in GMT
1338 dst_start += baseUtcOffset;
1339 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1341 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1342 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1343 dst_end -= new TimeSpan (24, 0, 0);
1346 * AdjustmentRule specifies a DST period that starts and ends within a year.
1347 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1348 * Thus we fallback to the transitions.
1350 if (dst_start.AddYears (1) < dst_end)
1351 storeTransition = true;
1353 DateTime dateStart, dateEnd;
1354 if (dst_start.Month < 7)
1355 dateStart = new DateTime (dst_start.Year, 1, 1);
1357 dateStart = new DateTime (dst_start.Year, 7, 1);
1359 if (dst_end.Month >= 7)
1360 dateEnd = new DateTime (dst_end.Year, 12, 31);
1362 dateEnd = new DateTime (dst_end.Year, 6, 30);
1365 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1366 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1367 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1368 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1370 dst_observed = false;
1372 if (daylightDisplayName != ttype.Name)
1373 daylightDisplayName = ttype.Name;
1374 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1375 // Round to nearest minute, since it's not possible to create an adjustment rule
1376 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1377 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1378 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1379 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1380 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1384 dst_observed = true;
1389 if (adjustmentRules.Count == 0 && !storeTransition) {
1390 if (standardDisplayName == null) {
1391 var t = time_types [0];
1392 standardDisplayName = t.Name;
1393 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1395 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1397 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1400 if (storeTransition && transitions.Count > 0) {
1401 tz.transitions = transitions;
1402 tz.supportsDaylightSavingTime = true;
1408 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1410 var abbrevs = new Dictionary<int, string> ();
1411 int abbrev_index = 0;
1412 var sb = new StringBuilder ();
1413 for (int i = 0; i < count; i++) {
1414 char c = (char) buffer [index + i];
1418 abbrevs.Add (abbrev_index, sb.ToString ());
1419 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1420 //j == sb.Length empty substring also needs to be added #31432
1421 for (int j = 1; j <= sb.Length; j++)
1422 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1423 abbrev_index = i + 1;
1424 sb = new StringBuilder ();
1430 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1432 var types = new Dictionary<int, TimeType> (count);
1433 for (int i = 0; i < count; i++) {
1434 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1437 // The official tz database contains timezone with GMT offsets
1438 // not only in whole hours/minutes but in seconds. This happens for years
1439 // before 1901. For example
1441 // NAME GMTOFF RULES FORMAT UNTIL
1442 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1444 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1445 // we remove seconds to not crash later
1447 offset = (offset / 60) * 60;
1449 byte is_dst = buffer [index + 6 * i + 4];
1450 byte abbrev = buffer [index + 6 * i + 5];
1451 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1456 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1458 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1459 for (int i = 0; i < count; i++) {
1460 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1461 DateTime ttime = DateTimeFromUnixTime (unixtime);
1462 byte ttype = buffer [index + 4 * count + i];
1463 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1468 static DateTime DateTimeFromUnixTime (long unix_time)
1470 DateTime date_time = new DateTime (1970, 1, 1);
1471 return date_time.AddSeconds (unix_time);
1474 #region reference sources
1475 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1476 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1479 return Local.GetUtcOffset (dateTime, out dst);
1482 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1485 return GetUtcOffset (dateTime, out dst);
1488 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1490 isDaylightSavings = false;
1491 isAmbiguousLocalDst = false;
1492 TimeSpan baseOffset = zone.BaseUtcOffset;
1494 if (zone.IsAmbiguousTime (time)) {
1495 isAmbiguousLocalDst = true;
1499 return zone.GetUtcOffset (time, out isDaylightSavings);
1505 public readonly int Offset;
1506 public readonly bool IsDst;
1509 public TimeType (int offset, bool is_dst, string abbrev)
1511 this.Offset = offset;
1512 this.IsDst = is_dst;
1516 public override string ToString ()
1518 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;