[corlib] Improve file:// url handling in AppDomainSetup. (#16161)
[mono-project.git] / mcs / class / corlib / System / TimeZoneInfo.cs
blob28366d586892243a82e9527fe3b1f6bcf3eee7c0
2 /*
3 * System.TimeZoneInfo
5 * Author(s)
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.
30 using System;
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;
37 using System.Text;
38 using System.Globalization;
39 using System.IO;
41 using Microsoft.Win32;
43 namespace System
45 partial class TimeZoneInfo
47 TimeSpan baseUtcOffset;
48 public TimeSpan BaseUtcOffset {
49 get { return baseUtcOffset; }
52 string daylightDisplayName;
53 public string DaylightName {
54 get {
55 return supportsDaylightSavingTime
56 ? daylightDisplayName
57 : string.Empty;
61 string displayName;
62 public string DisplayName {
63 get { return displayName; }
66 string id;
67 public string Id {
68 get { return id; }
71 static TimeZoneInfo local;
72 public static TimeZoneInfo Local {
73 get {
74 var l = local;
75 if (l == null) {
76 l = CreateLocal ();
77 if (l == null)
78 throw new TimeZoneNotFoundException ();
80 if (Interlocked.CompareExchange (ref local, l, null) != null)
81 l = local;
84 return l;
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;
95 [DllImport ("libc")]
96 private static extern int readlink (string path, byte[] buffer, int buflen);
98 private static string readlink (string path)
100 if (readlinkNotFound)
101 return null;
103 byte[] buf = new byte [512];
104 int ret;
106 try {
107 ret = readlink (path, buf, buf.Length);
108 } catch (DllNotFoundException) {
109 readlinkNotFound = true;
110 return null;
111 } catch (EntryPointNotFoundException) {
112 readlinkNotFound = true;
113 return null;
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)
124 name = null;
125 var linkPath = readlink (path);
126 if (linkPath != null) {
127 if (Path.IsPathRooted(linkPath))
128 path = linkPath;
129 else
130 path = Path.Combine(Path.GetDirectoryName(path), linkPath);
133 path = Path.GetFullPath (path);
135 if (string.IsNullOrEmpty (TimeZoneDirectory))
136 return false;
138 var baseDir = TimeZoneDirectory;
139 if (baseDir [baseDir.Length-1] != Path.DirectorySeparatorChar)
140 baseDir += Path.DirectorySeparatorChar;
142 if (!path.StartsWith (baseDir, StringComparison.InvariantCulture))
143 return false;
145 name = path.Substring (baseDir.Length);
146 if (name == "localtime")
147 name = "Local";
149 return true;
152 #if (!MONODROID && !MONOTOUCH && !XAMMAC && !WASM) || MOBILE_DESKTOP_HOST
153 static TimeZoneInfo CreateLocal ()
155 #if WIN_PLATFORM
156 if (IsWindows && LocalZoneKey != null) {
157 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
158 if (name == null)
159 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
160 name = TrimSpecial (name);
161 if (name != null)
162 return TimeZoneInfo.FindSystemTimeZoneById (name);
163 } else if (IsWindows) {
164 return GetLocalTimeZoneInfoWinRTFallback ();
166 #endif
168 var tz = Environment.GetEnvironmentVariable ("TZ");
169 if (tz != null) {
170 if (tz == String.Empty)
171 return Utc;
172 try {
173 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
174 } catch {
175 return Utc;
179 var tzFilePaths = new string [] {
180 "/etc/localtime",
181 Path.Combine (TimeZoneDirectory, "localtime")};
183 foreach (var tzFilePath in tzFilePaths) {
184 try {
185 string tzName = null;
186 if (!TryGetNameFromPath (tzFilePath, out tzName))
187 tzName = "Local";
188 return FindSystemTimeZoneByFileName (tzName, tzFilePath);
189 } catch (TimeZoneNotFoundException) {
190 continue;
194 return Utc;
197 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
199 #if LIBC
200 string filepath = Path.Combine (TimeZoneDirectory, id);
201 return FindSystemTimeZoneByFileName (id, filepath);
202 #else
203 throw new NotImplementedException ();
204 #endif
207 static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
209 #if WIN_PLATFORM
210 if (TimeZoneKey != null) {
211 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
212 using (RegistryKey subkey = TimeZoneKey.OpenSubKey (id))
214 if (subkey == null || subkey.GetValue ("TZI") == null)
215 continue;
217 systemTimeZones.Add (FindSystemTimeZoneById (id));
220 return;
221 } else if (IsWindows) {
222 systemTimeZones.AddRange (GetSystemTimeZonesWinRTFallback ());
223 return;
225 #endif
227 #if LIBC
228 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
229 foreach (string continent in continents) {
230 try {
231 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
232 try {
233 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
234 systemTimeZones.Add (FindSystemTimeZoneById (id));
235 } catch (ArgumentNullException) {
236 } catch (TimeZoneNotFoundException) {
237 } catch (InvalidTimeZoneException) {
238 } catch (Exception) {
239 throw;
242 } catch {}
244 #else
245 throw new NotImplementedException ("This method is not implemented for this platform");
246 #endif
248 #endif // !MONODROID && !MONOTOUCH && !XAMMAC && !WASM
250 string standardDisplayName;
251 public string StandardName {
252 get { return standardDisplayName; }
255 bool supportsDaylightSavingTime;
256 public bool SupportsDaylightSavingTime {
257 get { return supportsDaylightSavingTime; }
260 static TimeZoneInfo utc;
261 public static TimeZoneInfo Utc {
262 get {
263 if (utc == null)
264 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
265 return utc;
268 #if LIBC
269 const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo";
270 static string timeZoneDirectory;
271 static string TimeZoneDirectory {
272 get {
273 if (timeZoneDirectory == null)
274 timeZoneDirectory = readlink (DefaultTimeZoneDirectory) ?? DefaultTimeZoneDirectory;
275 return timeZoneDirectory;
277 set {
278 ClearCachedData ();
279 timeZoneDirectory = value;
282 #endif
283 private AdjustmentRule [] adjustmentRules;
285 #if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
286 /// <summary>
287 /// Determine whether windows of not (taken Stephane Delcroix's code)
288 /// </summary>
289 private static bool IsWindows
291 get {
292 int platform = (int) Environment.OSVersion.Platform;
293 return ((platform != 4) && (platform != 6) && (platform != 128));
297 /// <summary>
298 /// Needed to trim misc garbage in MS registry keys
299 /// </summary>
300 private static string TrimSpecial (string str)
302 if (str == null)
303 return str;
304 var Istart = 0;
305 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
306 var Iend = str.Length - 1;
307 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend]) && str[Iend] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
308 Iend--;
310 return str.Substring (Istart, Iend-Istart+1);
313 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
314 static RegistryKey timeZoneKey;
315 static RegistryKey TimeZoneKey {
316 get {
317 if (timeZoneKey != null)
318 return timeZoneKey;
319 if (!IsWindows)
320 return null;
322 try {
323 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
324 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
325 false);
326 } catch {
327 return null;
332 static RegistryKey localZoneKey;
333 static RegistryKey LocalZoneKey {
334 get {
335 if (localZoneKey != null)
336 return localZoneKey;
338 if (!IsWindows)
339 return null;
341 try {
342 return localZoneKey = Registry.LocalMachine.OpenSubKey (
343 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
344 } catch {
345 return null;
349 #endif
350 #endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
352 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
354 var resultTicks = date.Ticks + ticks;
355 if (resultTicks < DateTime.MinValue.Ticks) {
356 result = DateTime.SpecifyKind (DateTime.MinValue, kind);
357 return false;
360 if (resultTicks > DateTime.MaxValue.Ticks) {
361 result = DateTime.SpecifyKind (DateTime.MaxValue, kind);
362 return false;
365 result = new DateTime (resultTicks, kind);
366 return true;
369 public static void ClearCachedData ()
371 local = null;
372 utc = null;
373 systemTimeZones = null;
376 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
378 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
381 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
383 if (sourceTimeZone == null)
384 throw new ArgumentNullException ("sourceTimeZone");
386 if (destinationTimeZone == null)
387 throw new ArgumentNullException ("destinationTimeZone");
389 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
390 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
392 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
393 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
395 if (sourceTimeZone.IsInvalidTime (dateTime))
396 throw new ArgumentException ("dateTime parameter is an invalid time");
398 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
399 return dateTime;
401 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
403 if (destinationTimeZone != TimeZoneInfo.Utc) {
404 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
405 if (dateTime.Kind == DateTimeKind.Unspecified)
406 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
409 return utc;
412 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
414 if (destinationTimeZone == null)
415 throw new ArgumentNullException("destinationTimeZone");
417 var utcDateTime = dateTimeOffset.UtcDateTime;
419 bool isDst;
420 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
422 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
425 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
427 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
430 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
432 TimeZoneInfo source_tz;
433 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZoneId == TimeZoneInfo.Utc.Id) {
434 source_tz = Utc;
435 } else {
436 source_tz = FindSystemTimeZoneById (sourceTimeZoneId);
439 return ConvertTime (dateTime, source_tz, FindSystemTimeZoneById (destinationTimeZoneId));
442 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
444 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
447 private DateTime ConvertTimeFromUtc (DateTime dateTime)
449 if (dateTime.Kind == DateTimeKind.Local)
450 throw new ArgumentException ("Kind property of dateTime is Local");
452 if (this == TimeZoneInfo.Utc)
453 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
455 var utcOffset = GetUtcOffset (dateTime);
457 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
459 DateTime result;
460 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
461 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
463 return result;
466 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
468 if (destinationTimeZone == null)
469 throw new ArgumentNullException ("destinationTimeZone");
471 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
474 public static DateTime ConvertTimeToUtc (DateTime dateTime)
476 if (dateTime.Kind == DateTimeKind.Utc)
477 return dateTime;
479 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
482 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
484 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
487 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
489 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
492 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
494 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
495 if (sourceTimeZone == null)
496 throw new ArgumentNullException ("sourceTimeZone");
498 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
499 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
501 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
502 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
504 if (sourceTimeZone.IsInvalidTime (dateTime))
505 throw new ArgumentException ("dateTime parameter is an invalid time");
508 if (dateTime.Kind == DateTimeKind.Utc)
509 return dateTime;
511 bool isDst;
512 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
514 DateTime utcDateTime;
515 TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc);
516 return utcDateTime;
519 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
521 bool isDaylightSavings;
522 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
525 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
527 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
530 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
532 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
535 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
537 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
540 public override bool Equals (object obj)
542 return Equals (obj as TimeZoneInfo);
545 public bool Equals (TimeZoneInfo other)
547 if (other == null)
548 return false;
550 return other.Id == this.Id && HasSameRules (other);
553 public static TimeZoneInfo FindSystemTimeZoneById (string id)
555 //FIXME: this method should check for cached values in systemTimeZones
556 if (id == null)
557 throw new ArgumentNullException ("id");
558 #if WIN_PLATFORM
559 if (TimeZoneKey != null)
561 if (id == "Coordinated Universal Time")
562 id = "UTC"; //windows xp exception for "StandardName" property
563 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
564 if (key == null)
565 throw new TimeZoneNotFoundException ();
566 return FromRegistryKey(id, key);
567 } else if (IsWindows) {
568 return FindSystemTimeZoneByIdWinRTFallback (id);
570 #endif
571 // Local requires special logic that already exists in the Local property (bug #326)
572 if (id == "Local")
573 return Local;
575 return FindSystemTimeZoneByIdCore (id);
578 #if LIBC
579 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
581 FileStream stream = null;
582 try {
583 stream = File.OpenRead (filepath);
584 } catch (Exception ex) {
585 throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath, ex);
587 try {
588 return BuildFromStream (id, stream);
589 } finally {
590 if (stream != null)
591 stream.Dispose();
594 #endif
596 #if WIN_PLATFORM
597 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
599 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
601 if (reg_tzi == null)
602 throw new InvalidTimeZoneException ();
604 int bias = BitConverter.ToInt32 (reg_tzi, 0);
605 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
607 string display_name = (string) key.GetValue ("Display");
608 string standard_name = (string) key.GetValue ("Std");
609 string daylight_name = (string) key.GetValue ("Dlt");
611 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
613 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
614 if (dst_key != null) {
615 int first_year = (int) dst_key.GetValue ("FirstEntry");
616 int last_year = (int) dst_key.GetValue ("LastEntry");
617 int year;
619 for (year=first_year; year<=last_year; year++) {
620 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
621 if (dst_tzi != null) {
622 int start_year = year == first_year ? 1 : year;
623 int end_year = year == last_year ? 9999 : year;
624 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
628 else
629 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
631 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules));
634 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
636 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
637 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
639 int standard_year = BitConverter.ToInt16 (buffer, 12);
640 int standard_month = BitConverter.ToInt16 (buffer, 14);
641 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
642 int standard_day = BitConverter.ToInt16 (buffer, 18);
643 int standard_hour = BitConverter.ToInt16 (buffer, 20);
644 int standard_minute = BitConverter.ToInt16 (buffer, 22);
645 int standard_second = BitConverter.ToInt16 (buffer, 24);
646 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
648 int daylight_year = BitConverter.ToInt16 (buffer, 28);
649 int daylight_month = BitConverter.ToInt16 (buffer, 30);
650 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
651 int daylight_day = BitConverter.ToInt16 (buffer, 34);
652 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
653 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
654 int daylight_second = BitConverter.ToInt16 (buffer, 40);
655 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
657 if (standard_month == 0 || daylight_month == 0)
658 return;
660 DateTime start_date;
661 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
662 TransitionTime start_transition_time;
664 start_date = new DateTime (start_year, 1, 1);
665 if (daylight_year == 0) {
666 start_transition_time = TransitionTime.CreateFloatingDateRule (
667 start_timeofday, daylight_month, daylight_day,
668 (DayOfWeek) daylight_dayofweek);
670 else {
671 start_transition_time = TransitionTime.CreateFixedDateRule (
672 start_timeofday, daylight_month, daylight_day);
675 DateTime end_date;
676 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
677 TransitionTime end_transition_time;
679 end_date = new DateTime (end_year, 12, 31);
680 if (standard_year == 0) {
681 end_transition_time = TransitionTime.CreateFloatingDateRule (
682 end_timeofday, standard_month, standard_day,
683 (DayOfWeek) standard_dayofweek);
685 else {
686 end_transition_time = TransitionTime.CreateFixedDateRule (
687 end_timeofday, standard_month, standard_day);
690 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
692 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
693 start_date, end_date, daylight_delta,
694 start_transition_time, end_transition_time));
696 #endif
698 public AdjustmentRule [] GetAdjustmentRules ()
700 if (!supportsDaylightSavingTime || adjustmentRules == null)
701 return new AdjustmentRule [0];
702 else
703 return (AdjustmentRule []) adjustmentRules.Clone ();
706 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
708 if (!IsAmbiguousTime (dateTime))
709 throw new ArgumentException ("dateTime is not an ambiguous time");
711 AdjustmentRule rule = GetApplicableRule (dateTime);
712 if (rule != null)
713 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
714 else
715 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
718 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
720 if (!IsAmbiguousTime (dateTimeOffset))
721 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
723 throw new NotImplementedException ();
726 public override int GetHashCode ()
728 int hash_code = Id.GetHashCode ();
729 foreach (AdjustmentRule rule in GetAdjustmentRules ())
730 hash_code ^= rule.GetHashCode ();
731 return hash_code;
734 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
736 if (info == null)
737 throw new ArgumentNullException ("info");
738 info.AddValue ("Id", id);
739 info.AddValue ("DisplayName", displayName);
740 info.AddValue ("StandardName", standardDisplayName);
741 info.AddValue ("DaylightName", daylightDisplayName);
742 info.AddValue ("BaseUtcOffset", baseUtcOffset);
743 info.AddValue ("AdjustmentRules", adjustmentRules);
744 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
747 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
749 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
751 if (systemTimeZones == null) {
752 var tz = new List<TimeZoneInfo> ();
753 GetSystemTimeZonesCore (tz);
754 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
757 return systemTimeZones;
760 public TimeSpan GetUtcOffset (DateTime dateTime)
762 bool isDST;
763 return GetUtcOffset (dateTime, out isDST);
766 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
768 bool isDST;
769 return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
772 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST, bool forOffset = false)
774 isDST = false;
776 TimeZoneInfo tz = this;
777 if (dateTime.Kind == DateTimeKind.Utc)
778 tz = TimeZoneInfo.Utc;
780 if (dateTime.Kind == DateTimeKind.Local)
781 tz = TimeZoneInfo.Local;
783 bool isTzDst;
784 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst, forOffset);
786 if (tz == this) {
787 isDST = isTzDst;
788 return tzOffset;
791 DateTime utcDateTime;
792 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
793 return BaseUtcOffset;
795 return GetUtcOffsetHelper (utcDateTime, this, out isDST, forOffset);
798 // This is an helper method used by the method above, do not use this on its own.
799 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST, bool forOffset = false)
801 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
802 throw new Exception ();
804 isDST = false;
806 if (tz == TimeZoneInfo.Utc)
807 return TimeSpan.Zero;
809 TimeSpan offset;
810 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST, forOffset))
811 return offset;
813 if (dateTime.Kind == DateTimeKind.Utc) {
814 var utcRule = tz.GetApplicableRule (dateTime);
815 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
816 isDST = true;
817 return tz.BaseUtcOffset + utcRule.DaylightDelta;
820 return tz.BaseUtcOffset;
823 DateTime stdUtcDateTime;
824 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
825 return tz.BaseUtcOffset;
827 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
829 DateTime dstUtcDateTime = DateTime.MinValue;
830 if (tzRule != null) {
831 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
832 return tz.BaseUtcOffset;
835 if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
836 // Replicate what .NET does when given a time which falls into the hour which is lost when
837 // DST starts. isDST should be false and the offset should be BaseUtcOffset without the
838 // DST delta while in that hour.
839 if (forOffset)
840 isDST = true;
841 if (tz.IsInDST (tzRule, dstUtcDateTime)) {
842 isDST = true;
843 return tz.BaseUtcOffset + tzRule.DaylightDelta;
844 } else {
845 return tz.BaseUtcOffset;
849 return tz.BaseUtcOffset;
852 public bool HasSameRules (TimeZoneInfo other)
854 if (other == null)
855 throw new ArgumentNullException ("other");
857 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
858 return false;
860 if (this.adjustmentRules == null)
861 return true;
863 if (this.BaseUtcOffset != other.BaseUtcOffset)
864 return false;
866 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
867 return false;
869 for (int i = 0; i < adjustmentRules.Length; i++) {
870 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
871 return false;
874 return true;
877 public bool IsAmbiguousTime (DateTime dateTime)
879 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
880 throw new ArgumentException ("Kind is Local and time is Invalid");
882 if (this == TimeZoneInfo.Utc)
883 return false;
885 if (dateTime.Kind == DateTimeKind.Utc)
886 dateTime = ConvertTimeFromUtc (dateTime);
888 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
889 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
891 AdjustmentRule rule = GetApplicableRule (dateTime);
892 if (rule != null) {
893 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
894 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
895 return true;
898 return false;
901 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
903 throw new NotImplementedException ();
906 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
908 // Check whether we're in the dateTime year's DST period
909 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
910 return true;
912 // We might be in the dateTime previous year's DST period
913 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
916 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
918 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
919 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
920 if (dateTime.Kind == DateTimeKind.Utc) {
921 DST_start -= BaseUtcOffset;
922 DST_end -= BaseUtcOffset;
924 DST_end -= rule.DaylightDelta;
925 return (dateTime >= DST_start && dateTime < DST_end);
928 public bool IsDaylightSavingTime (DateTime dateTime)
930 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
931 throw new ArgumentException ("dateTime is invalid and Kind is Local");
933 if (this == TimeZoneInfo.Utc)
934 return false;
936 if (!SupportsDaylightSavingTime)
937 return false;
939 bool isDst;
940 GetUtcOffset (dateTime, out isDst);
942 return isDst;
945 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
947 return IsDaylightSavingTime (dateTime);
950 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
952 var dateTime = dateTimeOffset.DateTime;
954 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
955 throw new ArgumentException ("dateTime is invalid and Kind is Local");
957 if (this == TimeZoneInfo.Utc)
958 return false;
960 if (!SupportsDaylightSavingTime)
961 return false;
963 bool isDst;
964 GetUtcOffset (dateTime, out isDst, true);
966 return isDst;
969 internal DaylightTime GetDaylightChanges (int year)
971 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
972 TimeSpan delta = new TimeSpan ();
974 if (transitions != null) {
975 end = DateTime.MaxValue;
976 for (var i = transitions.Count - 1; i >= 0; i--) {
977 var pair = transitions [i];
978 DateTime ttime = pair.Key;
979 TimeType ttype = pair.Value;
981 if (ttime.Year > year)
982 continue;
983 if (ttime.Year < year)
984 break;
986 if (ttype.IsDst) {
987 // DaylightTime.Delta is relative to the current BaseUtcOffset.
988 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
989 start = ttime;
990 } else {
991 end = ttime;
995 // DaylightTime.Start is relative to the Standard time.
996 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
997 start = DateTime.MinValue;
999 // DaylightTime.End is relative to the DST time.
1000 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
1001 end = DateTime.MinValue;
1002 } else {
1003 AdjustmentRule first = null, last = null;
1005 // Rule start/end dates are either very specific or very broad depending on the platform
1006 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
1007 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
1008 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
1010 foreach (var rule in GetAdjustmentRules ()) {
1011 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
1012 continue;
1013 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
1014 first = rule;
1015 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
1016 last = rule;
1019 if (first == null || last == null)
1020 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1022 start = TransitionPoint (first.DaylightTransitionStart, year);
1023 end = TransitionPoint (last.DaylightTransitionEnd, year);
1024 delta = first.DaylightDelta;
1027 if (start == DateTime.MinValue || end == DateTime.MinValue)
1028 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1030 return new DaylightTime (start, end, delta);
1033 public bool IsInvalidTime (DateTime dateTime)
1035 if (dateTime.Kind == DateTimeKind.Utc)
1036 return false;
1037 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1038 return false;
1040 AdjustmentRule rule = GetApplicableRule (dateTime);
1041 if (rule != null) {
1042 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1043 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1044 return true;
1047 return false;
1050 void IDeserializationCallback.OnDeserialization (object sender)
1052 try {
1053 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1054 } catch (ArgumentException ex) {
1055 throw new SerializationException ("invalid serialization data", ex);
1059 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1061 if (id == null)
1062 throw new ArgumentNullException ("id");
1064 if (id == String.Empty)
1065 throw new ArgumentException ("id parameter is an empty string");
1067 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1068 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1070 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1071 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1073 #if STRICT
1074 if (id.Length > 32)
1075 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1076 #endif
1078 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1079 AdjustmentRule prev = null;
1080 foreach (AdjustmentRule current in adjustmentRules) {
1081 if (current == null)
1082 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1084 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1085 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1086 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;");
1088 if (prev != null && prev.DateStart > current.DateStart)
1089 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1091 if (prev != null && prev.DateEnd > current.DateStart)
1092 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1094 if (prev != null && prev.DateEnd == current.DateStart)
1095 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1097 prev = current;
1102 public override string ToString ()
1104 return DisplayName;
1107 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1109 if (info == null)
1110 throw new ArgumentNullException ("info");
1111 id = (string) info.GetValue ("Id", typeof (string));
1112 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1113 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1114 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1115 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1116 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1117 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1120 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1122 if (id == null)
1123 throw new ArgumentNullException ("id");
1125 if (id == String.Empty)
1126 throw new ArgumentException ("id parameter is an empty string");
1128 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1129 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1131 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1132 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1134 #if STRICT
1135 if (id.Length > 32)
1136 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1137 #endif
1139 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1141 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1142 AdjustmentRule prev = null;
1143 foreach (AdjustmentRule current in adjustmentRules) {
1144 if (current == null)
1145 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1147 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1148 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1149 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;");
1151 if (prev != null && prev.DateStart > current.DateStart)
1152 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1154 if (prev != null && prev.DateEnd > current.DateStart)
1155 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1157 if (prev != null && prev.DateEnd == current.DateStart)
1158 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1160 prev = current;
1162 } else {
1163 supportsDaylightSavingTime = false;
1166 this.id = id;
1167 this.baseUtcOffset = baseUtcOffset;
1168 this.displayName = displayName ?? id;
1169 this.standardDisplayName = standardDisplayName ?? id;
1170 this.daylightDisplayName = daylightDisplayName;
1171 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1172 this.adjustmentRules = adjustmentRules;
1175 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1177 //Applicable rules are in standard time
1178 DateTime date = dateTime;
1180 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1181 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1182 return null;
1183 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1184 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1185 return null;
1188 // get the date component of the datetime
1189 date = date.Date;
1191 if (adjustmentRules != null) {
1192 foreach (AdjustmentRule rule in adjustmentRules) {
1193 if (rule.DateStart > date)
1194 return null;
1195 if (rule.DateEnd < date)
1196 continue;
1197 return rule;
1200 return null;
1203 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset, out bool isDst, bool forOffset = false)
1205 offset = BaseUtcOffset;
1206 isDst = false;
1208 if (transitions == null)
1209 return false;
1211 //Transitions are in UTC
1212 DateTime date = dateTime;
1214 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1215 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1216 return false;
1219 if (dateTime.Kind != DateTimeKind.Utc) {
1220 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1221 return false;
1224 AdjustmentRule current = GetApplicableRule (date);
1225 if (current != null) {
1226 DateTime tStart = TransitionPoint (current.DaylightTransitionStart, date.Year);
1227 DateTime tEnd = TransitionPoint (current.DaylightTransitionEnd, date.Year);
1228 TryAddTicks (tStart, -BaseUtcOffset.Ticks, out tStart, DateTimeKind.Utc);
1229 TryAddTicks (tEnd, -BaseUtcOffset.Ticks, out tEnd, DateTimeKind.Utc);
1230 if ((date >= tStart) && (date <= tEnd)) {
1231 if (forOffset)
1232 isDst = true;
1233 offset = baseUtcOffset;
1234 if (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc))
1236 offset += current.DaylightDelta;
1237 isDst = true;
1240 if (date >= new DateTime (tEnd.Ticks - current.DaylightDelta.Ticks, DateTimeKind.Utc))
1242 offset = baseUtcOffset;
1243 isDst = false;
1246 return true;
1249 return false;
1252 private static DateTime TransitionPoint (TransitionTime transition, int year)
1254 if (transition.IsFixedDateRule)
1255 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1257 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1258 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1259 if (day > DateTime.DaysInMonth (year, transition.Month))
1260 day -= 7;
1261 if (day < 1)
1262 day += 7;
1263 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1266 static AdjustmentRule[] ValidateRules (List<AdjustmentRule> adjustmentRules)
1268 if (adjustmentRules == null || adjustmentRules.Count == 0)
1269 return null;
1271 AdjustmentRule prev = null;
1272 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1273 if (prev != null && prev.DateEnd > current.DateStart) {
1274 adjustmentRules.Remove (current);
1276 prev = current;
1278 return adjustmentRules.ToArray ();
1281 #if LIBC || MONOTOUCH
1282 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1284 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1286 byte [] buffer = new byte [BUFFER_SIZE];
1287 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1289 if (!ValidTZFile (buffer, length))
1290 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1292 try {
1293 return ParseTZBuffer (id, buffer, length);
1294 } catch (InvalidTimeZoneException) {
1295 throw;
1296 } catch (Exception e) {
1297 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1301 private static bool ValidTZFile (byte [] buffer, int length)
1303 StringBuilder magic = new StringBuilder ();
1305 for (int i = 0; i < 4; i++)
1306 magic.Append ((char)buffer [i]);
1308 if (magic.ToString () != "TZif")
1309 return false;
1311 if (length >= BUFFER_SIZE)
1312 return false;
1314 return true;
1317 static int SwapInt32 (int i)
1319 return (((i >> 24) & 0xff)
1320 | ((i >> 8) & 0xff00)
1321 | ((i << 8) & 0xff0000)
1322 | (((i & 0xff) << 24)));
1325 static int ReadBigEndianInt32 (byte [] buffer, int start)
1327 int i = BitConverter.ToInt32 (buffer, start);
1328 if (!BitConverter.IsLittleEndian)
1329 return i;
1331 return SwapInt32 (i);
1334 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1336 //Reading the header. 4 bytes for magic, 16 are reserved
1337 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1338 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1339 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1340 int timecnt = ReadBigEndianInt32 (buffer, 32);
1341 int typecnt = ReadBigEndianInt32 (buffer, 36);
1342 int charcnt = ReadBigEndianInt32 (buffer, 40);
1344 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1345 throw new InvalidTimeZoneException ();
1347 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1348 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1349 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1351 if (time_types.Count == 0)
1352 throw new InvalidTimeZoneException ();
1354 if (time_types.Count == 1 && time_types[0].IsDst)
1355 throw new InvalidTimeZoneException ();
1357 TimeSpan baseUtcOffset = new TimeSpan (0);
1358 TimeSpan dstDelta = new TimeSpan (0);
1359 string standardDisplayName = null;
1360 string daylightDisplayName = null;
1361 bool dst_observed = false;
1362 DateTime dst_start = DateTime.MinValue;
1363 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1364 bool storeTransition = false;
1366 for (int i = 0; i < transitions.Count; i++) {
1367 var pair = transitions [i];
1368 DateTime ttime = pair.Key;
1369 TimeType ttype = pair.Value;
1370 if (!ttype.IsDst) {
1371 if (standardDisplayName != ttype.Name)
1372 standardDisplayName = ttype.Name;
1373 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1374 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1375 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1376 storeTransition = true;
1377 adjustmentRules = new List<AdjustmentRule> ();
1378 dst_observed = false;
1380 if (dst_observed) {
1381 //FIXME: check additional fields for this:
1382 //most of the transitions are expressed in GMT
1383 dst_start += baseUtcOffset;
1384 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1386 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1387 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1388 dst_end -= new TimeSpan (24, 0, 0);
1391 * AdjustmentRule specifies a DST period that starts and ends within a year.
1392 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1393 * Thus we fallback to the transitions.
1395 if (dst_start.AddYears (1) < dst_end)
1396 storeTransition = true;
1398 DateTime dateStart, dateEnd;
1399 if (dst_start.Month < 7)
1400 dateStart = new DateTime (dst_start.Year, 1, 1);
1401 else
1402 dateStart = new DateTime (dst_start.Year, 7, 1);
1404 if (dst_end.Month >= 7)
1405 dateEnd = new DateTime (dst_end.Year, 12, 31);
1406 else
1407 dateEnd = new DateTime (dst_end.Year, 6, 30);
1410 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1411 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1412 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1413 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1415 dst_observed = false;
1416 } else {
1417 if (daylightDisplayName != ttype.Name)
1418 daylightDisplayName = ttype.Name;
1419 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1420 // Round to nearest minute, since it's not possible to create an adjustment rule
1421 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1422 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1423 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1424 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1425 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1428 dst_start = ttime;
1429 dst_observed = true;
1433 TimeZoneInfo tz;
1434 if (adjustmentRules.Count == 0 && !storeTransition) {
1435 if (standardDisplayName == null) {
1436 var t = time_types [0];
1437 standardDisplayName = t.Name;
1438 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1440 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1441 } else {
1442 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules));
1445 if (storeTransition && transitions.Count > 0) {
1446 tz.transitions = transitions;
1448 tz.supportsDaylightSavingTime = adjustmentRules.Count > 0;
1450 return tz;
1453 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1455 var abbrevs = new Dictionary<int, string> ();
1456 int abbrev_index = 0;
1457 var sb = new StringBuilder ();
1458 for (int i = 0; i < count; i++) {
1459 char c = (char) buffer [index + i];
1460 if (c != '\0')
1461 sb.Append (c);
1462 else {
1463 abbrevs.Add (abbrev_index, sb.ToString ());
1464 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1465 //j == sb.Length empty substring also needs to be added #31432
1466 for (int j = 1; j <= sb.Length; j++)
1467 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1468 abbrev_index = i + 1;
1469 sb = new StringBuilder ();
1472 return abbrevs;
1475 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1477 var types = new Dictionary<int, TimeType> (count);
1478 for (int i = 0; i < count; i++) {
1479 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1482 // The official tz database contains timezone with GMT offsets
1483 // not only in whole hours/minutes but in seconds. This happens for years
1484 // before 1901. For example
1486 // NAME GMTOFF RULES FORMAT UNTIL
1487 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1489 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1490 // we remove seconds to not crash later
1492 offset = (offset / 60) * 60;
1494 byte is_dst = buffer [index + 6 * i + 4];
1495 byte abbrev = buffer [index + 6 * i + 5];
1496 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1498 return types;
1501 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1503 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1504 for (int i = 0; i < count; i++) {
1505 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1506 DateTime ttime = DateTimeFromUnixTime (unixtime);
1507 byte ttype = buffer [index + 4 * count + i];
1508 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1510 return list;
1513 static DateTime DateTimeFromUnixTime (long unix_time)
1515 DateTime date_time = new DateTime (1970, 1, 1);
1516 return date_time.AddSeconds (unix_time);
1519 #region reference sources
1520 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1521 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1523 bool dst;
1524 return Local.GetUtcOffset (dateTime, out dst);
1527 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1529 bool dst;
1530 return GetUtcOffset (dateTime, out dst);
1533 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1535 isDaylightSavings = false;
1536 isAmbiguousLocalDst = false;
1537 TimeSpan baseOffset = zone.BaseUtcOffset;
1539 if (zone.IsAmbiguousTime (time)) {
1540 isAmbiguousLocalDst = true;
1541 // return baseOffset;
1544 return zone.GetUtcOffset (time, out isDaylightSavings);
1546 #endregion
1549 class TimeType {
1550 public readonly int Offset;
1551 public readonly bool IsDst;
1552 public string Name;
1554 public TimeType (int offset, bool is_dst, string abbrev)
1556 this.Offset = offset;
1557 this.IsDst = is_dst;
1558 this.Name = abbrev;
1561 public override string ToString ()
1563 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
1565 #else
1567 #endif