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) && tz.IsInDST (tzRule, dstUtcDateTime)) {
816 return tz.BaseUtcOffset + tzRule.DaylightDelta;
819 return tz.BaseUtcOffset;
822 public bool HasSameRules (TimeZoneInfo other)
825 throw new ArgumentNullException ("other");
827 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
830 if (this.adjustmentRules == null)
833 if (this.BaseUtcOffset != other.BaseUtcOffset)
836 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
839 for (int i = 0; i < adjustmentRules.Length; i++) {
840 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
847 public bool IsAmbiguousTime (DateTime dateTime)
849 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
850 throw new ArgumentException ("Kind is Local and time is Invalid");
852 if (this == TimeZoneInfo.Utc)
855 if (dateTime.Kind == DateTimeKind.Utc)
856 dateTime = ConvertTimeFromUtc (dateTime);
858 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
859 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
861 AdjustmentRule rule = GetApplicableRule (dateTime);
863 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
864 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
871 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
873 throw new NotImplementedException ();
876 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
878 // Check whether we're in the dateTime year's DST period
879 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
882 // We might be in the dateTime previous year's DST period
883 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
886 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
888 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
889 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
890 if (dateTime.Kind == DateTimeKind.Utc) {
891 DST_start -= BaseUtcOffset;
892 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
895 return (dateTime >= DST_start && dateTime < DST_end);
898 public bool IsDaylightSavingTime (DateTime dateTime)
900 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
901 throw new ArgumentException ("dateTime is invalid and Kind is Local");
903 if (this == TimeZoneInfo.Utc)
906 if (!SupportsDaylightSavingTime)
910 GetUtcOffset (dateTime, out isDst);
915 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
917 return IsDaylightSavingTime (dateTime);
920 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
922 return IsDaylightSavingTime (dateTimeOffset.DateTime);
925 internal DaylightTime GetDaylightChanges (int year)
927 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
928 TimeSpan delta = new TimeSpan ();
930 if (transitions != null) {
931 end = DateTime.MaxValue;
932 for (var i = transitions.Count - 1; i >= 0; i--) {
933 var pair = transitions [i];
934 DateTime ttime = pair.Key;
935 TimeType ttype = pair.Value;
937 if (ttime.Year > year)
939 if (ttime.Year < year)
943 // DaylightTime.Delta is relative to the current BaseUtcOffset.
944 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
951 // DaylightTime.Start is relative to the Standard time.
952 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
953 start = DateTime.MinValue;
955 // DaylightTime.End is relative to the DST time.
956 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
957 end = DateTime.MinValue;
959 AdjustmentRule first = null, last = null;
961 foreach (var rule in GetAdjustmentRules ()) {
962 if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
964 if (rule.DateStart.Year == year)
966 if (rule.DateEnd.Year == year)
970 if (first == null || last == null)
971 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
973 start = TransitionPoint (first.DaylightTransitionStart, year);
974 end = TransitionPoint (last.DaylightTransitionEnd, year);
975 delta = first.DaylightDelta;
978 if (start == DateTime.MinValue || end == DateTime.MinValue)
979 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
981 return new DaylightTime (start, end, delta);
984 public bool IsInvalidTime (DateTime dateTime)
986 if (dateTime.Kind == DateTimeKind.Utc)
988 if (dateTime.Kind == DateTimeKind.Local && this != Local)
991 AdjustmentRule rule = GetApplicableRule (dateTime);
993 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
994 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1001 void IDeserializationCallback.OnDeserialization (object sender)
1004 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1005 } catch (ArgumentException ex) {
1006 throw new SerializationException ("invalid serialization data", ex);
1010 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1013 throw new ArgumentNullException ("id");
1015 if (id == String.Empty)
1016 throw new ArgumentException ("id parameter is an empty string");
1018 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1019 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1021 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1022 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1026 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1029 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1030 AdjustmentRule prev = null;
1031 foreach (AdjustmentRule current in adjustmentRules) {
1032 if (current == null)
1033 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1035 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1036 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1037 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;");
1039 if (prev != null && prev.DateStart > current.DateStart)
1040 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1042 if (prev != null && prev.DateEnd > current.DateStart)
1043 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1045 if (prev != null && prev.DateEnd == current.DateStart)
1046 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1053 public override string ToString ()
1058 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1061 throw new ArgumentNullException ("info");
1062 id = (string) info.GetValue ("Id", typeof (string));
1063 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1064 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1065 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1066 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1067 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1068 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1071 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1074 throw new ArgumentNullException ("id");
1076 if (id == String.Empty)
1077 throw new ArgumentException ("id parameter is an empty string");
1079 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1080 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1082 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1083 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1087 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1090 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1092 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1093 AdjustmentRule prev = null;
1094 foreach (AdjustmentRule current in adjustmentRules) {
1095 if (current == null)
1096 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1098 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1099 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1100 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;");
1102 if (prev != null && prev.DateStart > current.DateStart)
1103 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1105 if (prev != null && prev.DateEnd > current.DateStart)
1106 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1108 if (prev != null && prev.DateEnd == current.DateStart)
1109 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1114 supportsDaylightSavingTime = false;
1118 this.baseUtcOffset = baseUtcOffset;
1119 this.displayName = displayName ?? id;
1120 this.standardDisplayName = standardDisplayName ?? id;
1121 this.daylightDisplayName = daylightDisplayName;
1122 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1123 this.adjustmentRules = adjustmentRules;
1126 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1128 //Applicable rules are in standard time
1129 DateTime date = dateTime;
1131 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1132 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1134 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1135 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1139 // get the date component of the datetime
1142 if (adjustmentRules != null) {
1143 foreach (AdjustmentRule rule in adjustmentRules) {
1144 if (rule.DateStart > date)
1146 if (rule.DateEnd < date)
1154 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1156 offset = BaseUtcOffset;
1159 if (transitions == null)
1162 //Transitions are in UTC
1163 DateTime date = dateTime;
1165 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1166 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1170 if (dateTime.Kind != DateTimeKind.Utc) {
1171 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1175 for (var i = transitions.Count - 1; i >= 0; i--) {
1176 var pair = transitions [i];
1177 DateTime ttime = pair.Key;
1178 TimeType ttype = pair.Value;
1183 offset = new TimeSpan (0, 0, ttype.Offset);
1184 isDst = ttype.IsDst;
1192 private static DateTime TransitionPoint (TransitionTime transition, int year)
1194 if (transition.IsFixedDateRule)
1195 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1197 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1198 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1199 if (day > DateTime.DaysInMonth (year, transition.Month))
1203 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1206 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1208 AdjustmentRule prev = null;
1209 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1210 if (prev != null && prev.DateEnd > current.DateStart) {
1211 adjustmentRules.Remove (current);
1215 return adjustmentRules;
1218 #if LIBC || MONOTOUCH
1219 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1221 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1223 byte [] buffer = new byte [BUFFER_SIZE];
1224 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1226 if (!ValidTZFile (buffer, length))
1227 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1230 return ParseTZBuffer (id, buffer, length);
1231 } catch (InvalidTimeZoneException) {
1233 } catch (Exception e) {
1234 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1238 private static bool ValidTZFile (byte [] buffer, int length)
1240 StringBuilder magic = new StringBuilder ();
1242 for (int i = 0; i < 4; i++)
1243 magic.Append ((char)buffer [i]);
1245 if (magic.ToString () != "TZif")
1248 if (length >= BUFFER_SIZE)
1254 static int SwapInt32 (int i)
1256 return (((i >> 24) & 0xff)
1257 | ((i >> 8) & 0xff00)
1258 | ((i << 8) & 0xff0000)
1259 | (((i & 0xff) << 24)));
1262 static int ReadBigEndianInt32 (byte [] buffer, int start)
1264 int i = BitConverter.ToInt32 (buffer, start);
1265 if (!BitConverter.IsLittleEndian)
1268 return SwapInt32 (i);
1271 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1273 //Reading the header. 4 bytes for magic, 16 are reserved
1274 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1275 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1276 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1277 int timecnt = ReadBigEndianInt32 (buffer, 32);
1278 int typecnt = ReadBigEndianInt32 (buffer, 36);
1279 int charcnt = ReadBigEndianInt32 (buffer, 40);
1281 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1282 throw new InvalidTimeZoneException ();
1284 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1285 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1286 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1288 if (time_types.Count == 0)
1289 throw new InvalidTimeZoneException ();
1291 if (time_types.Count == 1 && time_types[0].IsDst)
1292 throw new InvalidTimeZoneException ();
1294 TimeSpan baseUtcOffset = new TimeSpan (0);
1295 TimeSpan dstDelta = new TimeSpan (0);
1296 string standardDisplayName = null;
1297 string daylightDisplayName = null;
1298 bool dst_observed = false;
1299 DateTime dst_start = DateTime.MinValue;
1300 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1301 bool storeTransition = false;
1303 for (int i = 0; i < transitions.Count; i++) {
1304 var pair = transitions [i];
1305 DateTime ttime = pair.Key;
1306 TimeType ttype = pair.Value;
1308 if (standardDisplayName != ttype.Name)
1309 standardDisplayName = ttype.Name;
1310 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1311 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1312 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1313 storeTransition = true;
1314 adjustmentRules = new List<AdjustmentRule> ();
1315 dst_observed = false;
1318 //FIXME: check additional fields for this:
1319 //most of the transitions are expressed in GMT
1320 dst_start += baseUtcOffset;
1321 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1323 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1324 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1325 dst_end -= new TimeSpan (24, 0, 0);
1328 * AdjustmentRule specifies a DST period that starts and ends within a year.
1329 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1330 * Thus we fallback to the transitions.
1332 if (dst_start.AddYears (1) < dst_end)
1333 storeTransition = true;
1335 DateTime dateStart, dateEnd;
1336 if (dst_start.Month < 7)
1337 dateStart = new DateTime (dst_start.Year, 1, 1);
1339 dateStart = new DateTime (dst_start.Year, 7, 1);
1341 if (dst_end.Month >= 7)
1342 dateEnd = new DateTime (dst_end.Year, 12, 31);
1344 dateEnd = new DateTime (dst_end.Year, 6, 30);
1347 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1348 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1349 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1350 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1352 dst_observed = false;
1354 if (daylightDisplayName != ttype.Name)
1355 daylightDisplayName = ttype.Name;
1356 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1357 // Round to nearest minute, since it's not possible to create an adjustment rule
1358 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1359 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1360 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1361 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1362 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1366 dst_observed = true;
1371 if (adjustmentRules.Count == 0 && !storeTransition) {
1372 if (standardDisplayName == null) {
1373 var t = time_types [0];
1374 standardDisplayName = t.Name;
1375 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1377 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1379 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1382 if (storeTransition && transitions.Count > 0) {
1383 tz.transitions = transitions;
1384 tz.supportsDaylightSavingTime = true;
1390 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1392 var abbrevs = new Dictionary<int, string> ();
1393 int abbrev_index = 0;
1394 var sb = new StringBuilder ();
1395 for (int i = 0; i < count; i++) {
1396 char c = (char) buffer [index + i];
1400 abbrevs.Add (abbrev_index, sb.ToString ());
1401 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1402 //j == sb.Length empty substring also needs to be added #31432
1403 for (int j = 1; j <= sb.Length; j++)
1404 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1405 abbrev_index = i + 1;
1406 sb = new StringBuilder ();
1412 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1414 var types = new Dictionary<int, TimeType> (count);
1415 for (int i = 0; i < count; i++) {
1416 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1419 // The official tz database contains timezone with GMT offsets
1420 // not only in whole hours/minutes but in seconds. This happens for years
1421 // before 1901. For example
1423 // NAME GMTOFF RULES FORMAT UNTIL
1424 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1426 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1427 // we remove seconds to not crash later
1429 offset = (offset / 60) * 60;
1431 byte is_dst = buffer [index + 6 * i + 4];
1432 byte abbrev = buffer [index + 6 * i + 5];
1433 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1438 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1440 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1441 for (int i = 0; i < count; i++) {
1442 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1443 DateTime ttime = DateTimeFromUnixTime (unixtime);
1444 byte ttype = buffer [index + 4 * count + i];
1445 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1450 static DateTime DateTimeFromUnixTime (long unix_time)
1452 DateTime date_time = new DateTime (1970, 1, 1);
1453 return date_time.AddSeconds (unix_time);
1456 #region reference sources
1457 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1458 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1461 return Local.GetUtcOffset (dateTime, out dst);
1464 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1467 return GetUtcOffset (dateTime, out dst);
1470 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1472 isDaylightSavings = false;
1473 isAmbiguousLocalDst = false;
1474 TimeSpan baseOffset = zone.BaseUtcOffset;
1476 if (zone.IsAmbiguousTime (time)) {
1477 isAmbiguousLocalDst = true;
1481 return zone.GetUtcOffset (time, out isDaylightSavings);
1487 public readonly int Offset;
1488 public readonly bool IsDst;
1491 public TimeType (int offset, bool is_dst, string abbrev)
1493 this.Offset = offset;
1494 this.IsDst = is_dst;
1498 public override string ToString ()
1500 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;