TimeZoneInfo.cs 49.9 KB
Newer Older
1
 /*
2 3 4 5 6
 * System.TimeZoneInfo
 *
 * Author(s)
 * 	Stephane Delcroix <stephane@delcroix.org>
 *
M
Miguel de Icaza 已提交
7 8
 * Copyright 2011 Xamarin Inc.
 *
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

29
using System;
30
using System.Runtime.CompilerServices;
31
using System.Threading;
32 33
using System.Collections.Generic;
using System.Collections.ObjectModel;
34
using System.Runtime.Serialization;
M
Marcos Henrich 已提交
35
using System.Runtime.InteropServices;
36
using System.Text;
37
using System.Globalization;
38 39
using System.IO;

40 41
using Microsoft.Win32;

42 43
namespace System
{
44
	partial class TimeZoneInfo
45 46 47 48 49 50 51 52 53
	{
		TimeSpan baseUtcOffset;
		public TimeSpan BaseUtcOffset {
			get { return baseUtcOffset; }
		}

		string daylightDisplayName;
		public string DaylightName {
			get { 
54 55 56
				return supportsDaylightSavingTime
					? daylightDisplayName
					: string.Empty;
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
			}
		}

		string displayName;
		public string DisplayName {
			get { return displayName; }
		}

		string id;
		public string Id {
			get { return id; }
		}

		static TimeZoneInfo local;
		public static TimeZoneInfo Local {
			get { 
73 74 75 76 77 78 79 80 81 82 83 84 85 86
				var l = local;
				if (l == null) {
					l = CreateLocal ();
					if (l == null)
						throw new TimeZoneNotFoundException ();

					if (Interlocked.CompareExchange (ref local, l, null) != null)
						l = local;
				}

				return l;
			}
		}

87 88 89 90 91
		/*
			TimeZone transitions are stored when there is a change on the base offset.
		*/
		private List<KeyValuePair<DateTime, TimeType>> transitions;

92
		private static bool readlinkNotFound;
M
Marcos Henrich 已提交
93 94 95 96 97 98

		[DllImport ("libc")]
		private static extern int readlink (string path, byte[] buffer, int buflen);

		private static string readlink (string path)
		{
99
			if (readlinkNotFound)
M
Marcos Henrich 已提交
100 101 102 103 104 105 106
				return null;

			byte[] buf = new byte [512];
			int ret;

			try {
				ret = readlink (path, buf, buf.Length);
M
Marek Safar 已提交
107
			} catch (DllNotFoundException) {
108 109
				readlinkNotFound = true;
				return null;
M
Marek Safar 已提交
110
			} catch (EntryPointNotFoundException) {
111
				readlinkNotFound = true;
M
Marcos Henrich 已提交
112 113 114 115 116 117 118 119 120 121 122 123
				return null;
			}

			if (ret == -1) return null;
			char[] cbuf = new char [512];
			int chars = System.Text.Encoding.Default.GetChars (buf, 0, ret, cbuf, 0);
			return new String (cbuf, 0, chars);
		}

		private static bool TryGetNameFromPath (string path, out string name)
		{
			name = null;
124 125 126 127 128
#if UNITY
			//Avoids calling readlink on webgl, which causes abort due to dlopen
			if(!File.Exists(path))
				return false;
#endif
M
Marcos Henrich 已提交
129
			var linkPath = readlink (path);
130 131 132 133 134 135
			if (linkPath != null) {
				if (Path.IsPathRooted(linkPath))
					path = linkPath;
				else
					path = Path.Combine(Path.GetDirectoryName(path), linkPath);
			}
M
Marcos Henrich 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

			path = Path.GetFullPath (path);

			if (string.IsNullOrEmpty (TimeZoneDirectory))
				return false;

			var baseDir = TimeZoneDirectory;
			if (baseDir [baseDir.Length-1] != Path.DirectorySeparatorChar)
				baseDir += Path.DirectorySeparatorChar;

			if (!path.StartsWith (baseDir, StringComparison.InvariantCulture))
				return false;

			name = path.Substring (baseDir.Length);
			if (name == "localtime")
				name = "Local";

			return true;
		}

156
#if !MONODROID && !MONOTOUCH && !XAMMAC
157 158
		static TimeZoneInfo CreateLocal ()
		{
159
#if WIN_PLATFORM
160 161
			if (IsWindows && LocalZoneKey != null) {
				string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
M
myrup 已提交
162 163
				if (name == null)
					name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
164 165 166
				name = TrimSpecial (name);
				if (name != null)
					return TimeZoneInfo.FindSystemTimeZoneById (name);
167 168
			} else if (IsWindows) {
				return GetLocalTimeZoneInfoWinRTFallback ();
169
			}
170
#endif
171

172 173 174 175 176 177
#if UNITY
			var localTimeZoneFallback = CreateLocalUnity();
			if(localTimeZoneFallback == null)
			    localTimeZoneFallback = Utc;
#endif

178 179 180
			var tz = Environment.GetEnvironmentVariable ("TZ");
			if (tz != null) {
				if (tz == String.Empty)
181 182 183
#if UNITY
					return localTimeZoneFallback;
#else
184
					return Utc;
185
#endif
186 187 188
				try {
					return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
				} catch {
189 190 191
#if UNITY
					return localTimeZoneFallback;
#else
192
					return Utc;
193
#endif
194 195 196
				}
			}

M
Marcos Henrich 已提交
197 198 199 200 201
			var tzFilePaths = new string [] {
				"/etc/localtime",
				Path.Combine (TimeZoneDirectory, "localtime")};

			foreach (var tzFilePath in tzFilePaths) {
202
				try {
M
Marcos Henrich 已提交
203 204 205 206
					string tzName = null;
					if (!TryGetNameFromPath (tzFilePath, out tzName))
						tzName = "Local";
					return FindSystemTimeZoneByFileName (tzName, tzFilePath);
207
				} catch (TimeZoneNotFoundException) {
M
Marcos Henrich 已提交
208
					continue;
209 210
				}
			}
M
Marcos Henrich 已提交
211

212 213 214
#if UNITY
			return localTimeZoneFallback;
#else
M
Marcos Henrich 已提交
215
			return Utc;
216
#endif
217 218 219 220 221 222 223 224 225 226 227 228
		}

		static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
		{
#if LIBC
			string filepath = Path.Combine (TimeZoneDirectory, id);
			return FindSystemTimeZoneByFileName (id, filepath);
#else
			throw new NotImplementedException ();
#endif
		}

229
		static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
230
		{
231
#if WIN_PLATFORM
232 233
			if (TimeZoneKey != null) {
				foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
234 235 236 237 238 239
					using (RegistryKey subkey = TimeZoneKey.OpenSubKey (id))
					{
						if (subkey == null || subkey.GetValue ("TZI") == null)
							continue;
					}
					systemTimeZones.Add (FindSystemTimeZoneById (id));
240 241
				}

242 243 244
				return;
			} else if (IsWindows) {
				systemTimeZones.AddRange (GetSystemTimeZonesWinRTFallback ());
245 246
				return;
			}
247
#endif
248 249

#if LIBC
250
			string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
			foreach (string continent in continents) {
				try {
					foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
						try {
							string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
							systemTimeZones.Add (FindSystemTimeZoneById (id));
						} catch (ArgumentNullException) {
						} catch (TimeZoneNotFoundException) {
						} catch (InvalidTimeZoneException) {
						} catch (Exception) {
							throw;
						}
					}
				} catch {}
			}
#else
			throw new NotImplementedException ("This method is not implemented for this platform");
268
#endif
269
		}
270
#endif // !MONODROID && !MONOTOUCH && !XAMMAC
271 272 273 274 275 276

		string standardDisplayName;
		public string StandardName {
			get { return standardDisplayName; }
		}

277
		bool supportsDaylightSavingTime;
278
		public bool SupportsDaylightSavingTime {
279
			get  { return supportsDaylightSavingTime; }
280 281 282 283 284 285 286 287 288 289 290
		}

		static TimeZoneInfo utc;
		public static TimeZoneInfo Utc {
			get {
				if (utc == null)
					utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
				return utc;
			}
		}
#if LIBC
291
		static string timeZoneDirectory;
292
		static string TimeZoneDirectory {
293 294 295 296 297 298 299 300 301 302 303 304 305
			get {
				if (timeZoneDirectory == null)
					timeZoneDirectory = "/usr/share/zoneinfo";
				return timeZoneDirectory;
			}
			set {
				ClearCachedData ();
				timeZoneDirectory = value;
			}
		}
#endif
		private AdjustmentRule [] adjustmentRules;

306
#if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
		/// <summary>
		/// Determine whether windows of not (taken Stephane Delcroix's code)
		/// </summary>
		private static bool IsWindows
		{
			get {
				int platform = (int) Environment.OSVersion.Platform;
				return ((platform != 4) && (platform != 6) && (platform != 128));
			}
		}
		
		/// <summary>
		/// Needed to trim misc garbage in MS registry keys
		/// </summary>
		private static string TrimSpecial (string str)
		{
M
myrup 已提交
323 324
			if (str == null)
				return str;
325 326 327
			var Istart = 0;
			while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
			var Iend = str.Length - 1;
328 329
			while (Iend > Istart && !char.IsLetterOrDigit(str[Iend]) && str[Iend] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
				Iend--;
330 331 332
			
			return str.Substring (Istart, Iend-Istart+1);
		}
333

334
#if !FULL_AOT_DESKTOP || WIN_PLATFORM
335
		static RegistryKey timeZoneKey;
336 337
		static RegistryKey TimeZoneKey {
			get {
338 339 340 341 342
				if (timeZoneKey != null)
					return timeZoneKey;
				if (!IsWindows)
					return null;
				
343 344 345 346 347 348 349
				try {
					return timeZoneKey = Registry.LocalMachine.OpenSubKey (
						"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
						false);
				} catch {
					return null;
				}
350 351 352
			}
		}
		
353
		static RegistryKey localZoneKey;
354 355 356 357 358 359 360 361
		static RegistryKey LocalZoneKey {
			get {
				if (localZoneKey != null)
					return localZoneKey;
				
				if (!IsWindows)
					return null;
				
362 363 364 365 366 367
				try {
					return localZoneKey = Registry.LocalMachine.OpenSubKey (
						"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
				} catch {
					return null;
				}
368 369
			}
		}
370
#endif
371
#endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
372

373
		private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
374 375
		{
			var resultTicks = date.Ticks + ticks;
376 377 378 379 380 381 382
			if (resultTicks < DateTime.MinValue.Ticks) {
				result = DateTime.SpecifyKind (DateTime.MinValue, kind);
				return false;
			}

			if (resultTicks > DateTime.MaxValue.Ticks) {
				result = DateTime.SpecifyKind (DateTime.MaxValue, kind);
383 384 385 386 387 388 389
				return false;
			}

			result = new DateTime (resultTicks, kind);
			return true;
		}

390 391 392 393 394 395 396 397 398
		public static void ClearCachedData ()
		{
			local = null;
			utc = null;
			systemTimeZones = null;
		}

		public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
		{
399
			return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
400 401 402 403
		}

		public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
		{
404 405 406 407 408 409
			if (sourceTimeZone == null)
				throw new ArgumentNullException ("sourceTimeZone");

			if (destinationTimeZone == null)
				throw new ArgumentNullException ("destinationTimeZone");
			
410
			if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
411
				throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
412 413

			if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
414
				throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
415
			
416 417 418 419 420 421
			if (sourceTimeZone.IsInvalidTime (dateTime))
				throw new ArgumentException ("dateTime parameter is an invalid time");

			if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
				return dateTime;

C
Crisdut 已提交
422
			DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
423

424 425 426 427 428 429 430
			if (destinationTimeZone != TimeZoneInfo.Utc) {
				utc = ConvertTimeFromUtc (utc, destinationTimeZone);
				if (dateTime.Kind == DateTimeKind.Unspecified)
					return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
			}
			
			return utc;
431 432
		}

433
		public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) 
434
		{
435 436
			if (destinationTimeZone == null) 
				throw new ArgumentNullException("destinationTimeZone");
437

438
			var utcDateTime = dateTimeOffset.UtcDateTime;
439 440 441 442 443

			bool isDst;
			var utcOffset =  destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);

			return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
444 445 446 447 448 449 450 451 452
		}

		public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
		{
			return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
		}

		public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
		{
453 454 455 456 457 458 459 460
			TimeZoneInfo source_tz;
			if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZoneId == TimeZoneInfo.Utc.Id) {
				source_tz = Utc;
			} else {
				source_tz = FindSystemTimeZoneById (sourceTimeZoneId);
			}

			return ConvertTime (dateTime, source_tz, FindSystemTimeZoneById (destinationTimeZoneId));
461 462 463 464 465 466 467 468 469 470 471 472 473 474
		}

		public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
		{
			return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
		}

		private DateTime ConvertTimeFromUtc (DateTime dateTime)
		{
			if (dateTime.Kind == DateTimeKind.Local)
				throw new ArgumentException ("Kind property of dateTime is Local");

			if (this == TimeZoneInfo.Utc)
				return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
475

476 477 478
			var utcOffset = GetUtcOffset (dateTime);

			var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
479

480 481 482 483 484
			DateTime result;
			if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
				return DateTime.SpecifyKind (DateTime.MaxValue, kind);

			return result;
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
		}

		public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
		{
			if (destinationTimeZone == null)
				throw new ArgumentNullException ("destinationTimeZone");

			return destinationTimeZone.ConvertTimeFromUtc (dateTime);
		}

		public static DateTime ConvertTimeToUtc (DateTime dateTime)
		{
			if (dateTime.Kind == DateTimeKind.Utc)
				return dateTime;

500
			return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
501 502
		}

503 504 505 506 507
		static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
		{
			return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
 		}

508 509
		public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
		{
510 511
			return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
		}
512

513 514 515 516 517
		static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
		{
			if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
				if (sourceTimeZone == null)
					throw new ArgumentNullException ("sourceTimeZone");
518

519 520
				if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
					throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
521

522 523 524 525 526 527
				if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
					throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");

				if (sourceTimeZone.IsInvalidTime (dateTime))
					throw new ArgumentException ("dateTime parameter is an invalid time");
			}
528 529 530 531

			if (dateTime.Kind == DateTimeKind.Utc)
				return dateTime;

532 533
			bool isDst;
			var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
534

535
			DateTime utcDateTime;
536
			TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc);
537
			return utcDateTime;
538 539
		}

540 541 542 543 544 545
		static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
		{
			bool isDaylightSavings;
			return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
		}

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
		public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName) 
		{
			return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
		}

		public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
		{
			return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
		}

		public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
		{
			return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
		}

M
Marek Safar 已提交
561 562 563 564 565
		public override bool Equals (object obj)
		{
			return Equals (obj as TimeZoneInfo);
		}

566 567 568 569 570 571 572 573 574 575 576 577 578
		public bool Equals (TimeZoneInfo other)
		{
			if (other == null)
				return false;

			return other.Id == this.Id && HasSameRules (other);
		}

		public static TimeZoneInfo FindSystemTimeZoneById (string id)
		{
			//FIXME: this method should check for cached values in systemTimeZones
			if (id == null)
				throw new ArgumentNullException ("id");
579
#if WIN_PLATFORM
580 581
			if (TimeZoneKey != null)
			{
582 583
				if (id == "Coordinated Universal Time")
					id = "UTC"; //windows xp exception for "StandardName" property
584 585 586 587
				RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
				if (key == null)
					throw new TimeZoneNotFoundException ();
				return FromRegistryKey(id, key);
588 589
			} else if (IsWindows) {
				return FindSystemTimeZoneByIdWinRTFallback (id);
590
			}
591
#endif
592 593 594
			// Local requires special logic that already exists in the Local property (bug #326)
			if (id == "Local")
				return Local;
595 596

			return FindSystemTimeZoneByIdCore (id);
597 598 599 600 601
		}

#if LIBC
		private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
		{
602 603 604 605 606 607 608
			FileStream stream = null;
			try {
				stream = File.OpenRead (filepath);	
			} catch (Exception ex) {
				throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath, ex);
			}
			try {
609
				return BuildFromStream (id, stream);
610 611 612
			} finally {
				if (stream != null)
					stream.Dispose();
613
			}
614 615
		}
#endif
616

617
#if WIN_PLATFORM
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
		private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
		{
			byte [] reg_tzi = (byte []) key.GetValue ("TZI");

			if (reg_tzi == null)
				throw new InvalidTimeZoneException ();

			int bias = BitConverter.ToInt32 (reg_tzi, 0);
			TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);

			string display_name = (string) key.GetValue ("Display");
			string standard_name = (string) key.GetValue ("Std");
			string daylight_name = (string) key.GetValue ("Dlt");

			List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();

			RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
			if (dst_key != null) {
				int first_year = (int) dst_key.GetValue ("FirstEntry");
				int last_year = (int) dst_key.GetValue ("LastEntry");
				int year;

				for (year=first_year; year<=last_year; year++) {
					byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
					if (dst_tzi != null) {
						int start_year = year == first_year ? 1 : year;
						int end_year = year == last_year ? 9999 : year;
						ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
					}
				}
			}
			else
				ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);

652
			return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules));
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
		}

		private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
		{
			//int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
			int daylight_bias = BitConverter.ToInt32 (buffer, 8);

			int standard_year = BitConverter.ToInt16 (buffer, 12);
			int standard_month = BitConverter.ToInt16 (buffer, 14);
			int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
			int standard_day = BitConverter.ToInt16 (buffer, 18);
			int standard_hour = BitConverter.ToInt16 (buffer, 20);
			int standard_minute = BitConverter.ToInt16 (buffer, 22);
			int standard_second = BitConverter.ToInt16 (buffer, 24);
			int standard_millisecond = BitConverter.ToInt16 (buffer, 26);

			int daylight_year = BitConverter.ToInt16 (buffer, 28);
			int daylight_month = BitConverter.ToInt16 (buffer, 30);
			int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
			int daylight_day = BitConverter.ToInt16 (buffer, 34);
			int daylight_hour = BitConverter.ToInt16 (buffer, 36);
			int daylight_minute = BitConverter.ToInt16 (buffer, 38);
			int daylight_second = BitConverter.ToInt16 (buffer, 40);
			int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);

			if (standard_month == 0 || daylight_month == 0)
				return;

			DateTime start_date;
			DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
			TransitionTime start_transition_time;

685
			start_date = new DateTime (start_year, 1, 1);
686 687 688 689 690 691 692 693 694 695 696 697 698 699
			if (daylight_year == 0) {
				start_transition_time = TransitionTime.CreateFloatingDateRule (
					start_timeofday, daylight_month, daylight_day,
					(DayOfWeek) daylight_dayofweek);
			}
			else {
				start_transition_time = TransitionTime.CreateFixedDateRule (
					start_timeofday, daylight_month, daylight_day);
			}

			DateTime end_date;
			DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
			TransitionTime end_transition_time;

700
			end_date = new DateTime (end_year, 12, 31);
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
			if (standard_year == 0) {
				end_transition_time = TransitionTime.CreateFloatingDateRule (
					end_timeofday, standard_month, standard_day,
					(DayOfWeek) standard_dayofweek);
			}
			else {
				end_transition_time = TransitionTime.CreateFixedDateRule (
					end_timeofday, standard_month, standard_day);
			}

			TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);

			adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
				start_date, end_date, daylight_delta,
				start_transition_time, end_transition_time));
		}
717
#endif
718

719 720
		public AdjustmentRule [] GetAdjustmentRules ()
		{
721
			if (!supportsDaylightSavingTime || adjustmentRules == null)
722 723 724 725 726 727 728 729 730 731 732
				return new AdjustmentRule [0];
			else
				return (AdjustmentRule []) adjustmentRules.Clone ();
		}

		public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
		{
			if (!IsAmbiguousTime (dateTime))
				throw new ArgumentException ("dateTime is not an ambiguous time");

			AdjustmentRule rule = GetApplicableRule (dateTime);
733 734 735 736
			if (rule != null)
				return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
			else
				return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
		}

		public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
		{
			if (!IsAmbiguousTime (dateTimeOffset))
				throw new ArgumentException ("dateTimeOffset is not an ambiguous time");

			throw new NotImplementedException ();
		}

		public override int GetHashCode ()
		{
			int hash_code = Id.GetHashCode ();
			foreach (AdjustmentRule rule in GetAdjustmentRules ())
				hash_code ^= rule.GetHashCode ();
			return hash_code;
		}

G
Gonzalo Paniagua Javier 已提交
755
		void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
756
		{
757 758 759 760 761 762 763 764 765
			if (info == null)
				throw new ArgumentNullException ("info");
			info.AddValue ("Id", id);
			info.AddValue ("DisplayName", displayName);
			info.AddValue ("StandardName", standardDisplayName);
			info.AddValue ("DaylightName", daylightDisplayName);
			info.AddValue ("BaseUtcOffset", baseUtcOffset);
			info.AddValue ("AdjustmentRules", adjustmentRules);
			info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
766 767
		}

768 769
		static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;

770 771 772
		public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
		{
			if (systemTimeZones == null) {
773
				var tz = new List<TimeZoneInfo> ();
774
				GetSystemTimeZonesCore (tz);
775
				Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
776
			}
777

778
			return systemTimeZones;
779 780 781 782
		}

		public TimeSpan GetUtcOffset (DateTime dateTime)
		{
783 784
			bool isDST;
			return GetUtcOffset (dateTime, out isDST);
785 786 787 788
		}

		public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
		{
789 790
			bool isDST;
			return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
791 792
		}

793 794 795 796
		private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
		{
			isDST = false;

797 798 799 800 801 802 803
			TimeZoneInfo tz = this;
			if (dateTime.Kind == DateTimeKind.Utc)
				tz = TimeZoneInfo.Utc;

			if (dateTime.Kind == DateTimeKind.Local)
				tz = TimeZoneInfo.Local;

804
			bool isTzDst;
805
			var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
806 807 808 809 810 811

			if (tz == this) {
				isDST = isTzDst;
				return tzOffset;
			}

812 813
			DateTime utcDateTime;
			if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
814 815
				return BaseUtcOffset;

816
			return GetUtcOffsetHelper (utcDateTime, this, out isDST);
817
		}
818

819 820
		// This is an helper method used by the method above, do not use this on its own.
		private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
821 822 823
		{
			if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
				throw new Exception ();
824

825
			isDST = false;
826

827 828 829
			if (tz == TimeZoneInfo.Utc)
				return TimeSpan.Zero;

830 831 832 833
			TimeSpan offset;
			if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
				return offset;

834 835 836 837 838 839 840 841
			if (dateTime.Kind == DateTimeKind.Utc) {
				var utcRule = tz.GetApplicableRule (dateTime);
				if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
					isDST = true;
					return tz.BaseUtcOffset + utcRule.DaylightDelta;
				}

				return tz.BaseUtcOffset;
842 843
			}

844 845
			DateTime stdUtcDateTime;
			if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
846
				return tz.BaseUtcOffset;
847

848
			var tzRule = tz.GetApplicableRule (stdUtcDateTime);
849

850 851
			DateTime dstUtcDateTime = DateTime.MinValue;
			if (tzRule != null) {
852
				if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
853 854 855
					return tz.BaseUtcOffset;
			}

856
			if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
857 858 859
				// Replicate what .NET does when given a time which falls into the hour which is lost when
				// DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
				// DST delta while in that hour.
860
				isDST = true;
861 862 863 864 865
				if (tz.IsInDST (tzRule, dstUtcDateTime)) {
					return tz.BaseUtcOffset + tzRule.DaylightDelta;
				} else {
					return tz.BaseUtcOffset;
				}
866 867 868
			}

			return tz.BaseUtcOffset;
869 870 871 872 873 874 875
		}

		public bool HasSameRules (TimeZoneInfo other)
		{
			if (other == null)
				throw new ArgumentNullException ("other");

876 877 878 879 880 881
			if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
				return false;

			if (this.adjustmentRules == null)
      				return true;

882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
			if (this.BaseUtcOffset != other.BaseUtcOffset)
				return false;

			if (this.adjustmentRules.Length != other.adjustmentRules.Length)
				return false;

			for (int i = 0; i < adjustmentRules.Length; i++) {
				if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
					return false;
			}
			
			return true;
		}

		public bool IsAmbiguousTime (DateTime dateTime)
		{
			if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
				throw new ArgumentException ("Kind is Local and time is Invalid");

			if (this == TimeZoneInfo.Utc)
				return false;
			
			if (dateTime.Kind == DateTimeKind.Utc)
				dateTime = ConvertTimeFromUtc (dateTime);

			if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
				dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);

			AdjustmentRule rule = GetApplicableRule (dateTime);
911 912
			if (rule != null) {
				DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
913
				if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
914 915
					return true;
			}
916 917 918 919 920 921 922 923 924
				
			return false;
		}

		public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
		{
			throw new NotImplementedException ();
		}

925 926 927 928 929 930 931
		private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
		{
			// Check whether we're in the dateTime year's DST period
			if (IsInDSTForYear (rule, dateTime, dateTime.Year))
				return true;

			// We might be in the dateTime previous year's DST period
932
			return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
933 934
		}

935 936 937 938 939 940 941 942 943 944 945
		bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
		{
			DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
			DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
			if (dateTime.Kind == DateTimeKind.Utc) {
				DST_start -= BaseUtcOffset;
				DST_end -= (BaseUtcOffset + rule.DaylightDelta);
			}
			return (dateTime >= DST_start && dateTime < DST_end);
		}
		
946 947 948 949 950 951 952
		public bool IsDaylightSavingTime (DateTime dateTime)
		{
			if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
				throw new ArgumentException ("dateTime is invalid and Kind is Local");

			if (this == TimeZoneInfo.Utc)
				return false;
953
			
954 955 956
			if (!SupportsDaylightSavingTime)
				return false;

957 958
			bool isDst;
			GetUtcOffset (dateTime, out isDst);
959

960
			return isDst;
961 962
		}

963
		internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
964
		{
965
			return IsDaylightSavingTime (dateTime);
966 967
		}

968
		public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
969
		{
970
			return IsDaylightSavingTime (dateTimeOffset.DateTime);
971 972
		}

973 974 975 976 977 978 979 980 981 982 983 984
		internal DaylightTime GetDaylightChanges (int year)
		{
			DateTime start = DateTime.MinValue, end = DateTime.MinValue;
			TimeSpan delta = new TimeSpan ();

			if (transitions != null) {
				end = DateTime.MaxValue;
				for (var i =  transitions.Count - 1; i >= 0; i--) {
					var pair = transitions [i];
					DateTime ttime = pair.Key;
					TimeType ttype = pair.Value;

985 986 987 988 989
					if (ttime.Year > year)
						continue;
					if (ttime.Year < year)
						break;

990 991
					if (ttype.IsDst) {
						// DaylightTime.Delta is relative to the current BaseUtcOffset.
992
						delta =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
993 994 995 996 997 998 999
						start = ttime;
					} else {
						end = ttime;
					}
				}

				// DaylightTime.Start is relative to the Standard time.
1000 1001
				if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
					start = DateTime.MinValue;
1002 1003

				// DaylightTime.End is relative to the DST time.
1004 1005
				if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
					end = DateTime.MinValue;
1006
			} else {
1007 1008
				AdjustmentRule first = null, last = null;

1009 1010 1011 1012 1013
				// Rule start/end dates are either very specific or very broad depending on the platform
				//   2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
				//   2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
				//   0001-01-01..9999-12-31 - Rule for a time zone on Windows

1014
				foreach (var rule in GetAdjustmentRules ()) {
1015
					if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
1016
						continue;
1017
					if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
1018
						first = rule;
1019
					if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
1020
						last = rule;
1021
				}
1022 1023 1024 1025 1026 1027 1028

				if (first == null || last == null)
					return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());

				start = TransitionPoint (first.DaylightTransitionStart, year);
				end = TransitionPoint (last.DaylightTransitionEnd, year);
				delta = first.DaylightDelta;
1029 1030 1031 1032 1033 1034 1035 1036
			}

			if (start == DateTime.MinValue || end == DateTime.MinValue)
				return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());

			return new DaylightTime (start, end, delta);
		}

1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
		public bool IsInvalidTime (DateTime dateTime)
		{
			if (dateTime.Kind == DateTimeKind.Utc)
				return false;
			if (dateTime.Kind == DateTimeKind.Local && this != Local)
				return false;

			AdjustmentRule rule = GetApplicableRule (dateTime);
			if (rule != null) {
				DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
				if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
					return true;
			}

			return false;
		}

G
Gonzalo Paniagua Javier 已提交
1054
		void IDeserializationCallback.OnDeserialization (object sender)
1055
		{
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
			try {
					TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
				} catch (ArgumentException ex) {
					throw new SerializationException ("invalid serialization data", ex);
				}
 		}

		private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
		{
			if (id == null)
				throw new ArgumentNullException ("id");

			if (id == String.Empty)
				throw new ArgumentException ("id parameter is an empty string");

			if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
				throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");

			if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
				throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");

#if STRICT
			if (id.Length > 32)
				throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
#endif

			if (adjustmentRules != null && adjustmentRules.Length != 0) {
				AdjustmentRule prev = null;
				foreach (AdjustmentRule current in adjustmentRules) {
					if (current == null)
						throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");

					if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
							(baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
						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;");

					if (prev != null && prev.DateStart > current.DateStart)
						throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
					
					if (prev != null && prev.DateEnd > current.DateStart)
						throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");

					if (prev != null && prev.DateEnd == current.DateStart)
						throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");

					prev = current;
				}
			}
1104 1105 1106 1107 1108 1109 1110
		}
		
		public override string ToString ()
		{
			return DisplayName;
		}

1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123
		private TimeZoneInfo (SerializationInfo info, StreamingContext context)
		{
			if (info == null)
				throw new ArgumentNullException ("info");
			id = (string) info.GetValue ("Id", typeof (string));
			displayName = (string) info.GetValue ("DisplayName", typeof (string));
			standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
			daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
			baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
			adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
			supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
		}

1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
		private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
		{
			if (id == null)
				throw new ArgumentNullException ("id");

			if (id == String.Empty)
				throw new ArgumentException ("id parameter is an empty string");

			if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
				throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");

			if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
				throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");

#if STRICT
			if (id.Length > 32)
				throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
#endif

1143 1144
			bool supportsDaylightSavingTime = !disableDaylightSavingTime;

1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
			if (adjustmentRules != null && adjustmentRules.Length != 0) {
				AdjustmentRule prev = null;
				foreach (AdjustmentRule current in adjustmentRules) {
					if (current == null)
						throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");

					if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
							(baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
						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;");

					if (prev != null && prev.DateStart > current.DateStart)
						throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
					
					if (prev != null && prev.DateEnd > current.DateStart)
						throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");

					if (prev != null && prev.DateEnd == current.DateStart)
						throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");

					prev = current;
				}
1166 1167
			} else {
				supportsDaylightSavingTime = false;
1168 1169 1170 1171 1172 1173 1174
			}
			
			this.id = id;
			this.baseUtcOffset = baseUtcOffset;
			this.displayName = displayName ?? id;
			this.standardDisplayName = standardDisplayName ?? id;
			this.daylightDisplayName = daylightDisplayName;
1175
			this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1176 1177 1178 1179 1180
			this.adjustmentRules = adjustmentRules;
		}

		private AdjustmentRule GetApplicableRule (DateTime dateTime)
		{
1181
			//Applicable rules are in standard time
1182 1183
			DateTime date = dateTime;

1184 1185 1186 1187 1188 1189 1190
			if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
				if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
					return null;
			} else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
				if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
					return null;
			}
1191

1192 1193 1194
			// get the date component of the datetime
			date = date.Date;

1195 1196
			if (adjustmentRules != null) {
				foreach (AdjustmentRule rule in adjustmentRules) {
1197
					if (rule.DateStart > date)
1198
						return null;
1199
					if (rule.DateEnd < date)
1200 1201 1202
						continue;
					return rule;
				}
1203 1204 1205 1206
			}
			return null;
		}

1207 1208 1209 1210 1211 1212 1213 1214
		private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
		{
			offset = BaseUtcOffset;
			isDst = false;

			if (transitions == null)
				return false;

1215
			//Transitions are in UTC
1216 1217
			DateTime date = dateTime;

1218
			if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1219
				if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1220 1221
					return false;
			}
1222

1223
			if (dateTime.Kind != DateTimeKind.Utc) {
1224
				if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1225 1226
					return false;
			}
1227

1228 1229 1230 1231 1232 1233 1234 1235
			AdjustmentRule current = GetApplicableRule(date);
			if (current != null) {
				DateTime tStart = TransitionPoint(current.DaylightTransitionStart, date.Year);
				DateTime tEnd = TransitionPoint(current.DaylightTransitionEnd, date.Year);
				if ((date >= tStart) && (date <= tEnd)) {
					offset = baseUtcOffset + current.DaylightDelta; 
					isDst = true;
					return true;
1236
				}
1237 1238 1239 1240
			}
			return false;
		}

1241 1242 1243 1244 1245 1246
		private static DateTime TransitionPoint (TransitionTime transition, int year)
		{
			if (transition.IsFixedDateRule)
				return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;

			DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1247
			int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1248 1249
			if (day >  DateTime.DaysInMonth (year, transition.Month))
				day -= 7;
1250 1251
			if (day < 1)
				day += 7;
1252 1253 1254
			return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
		}

1255
		static AdjustmentRule[] ValidateRules (List<AdjustmentRule> adjustmentRules)
1256
		{
1257 1258 1259
			if (adjustmentRules == null || adjustmentRules.Count == 0)
				return null;

1260 1261 1262 1263 1264 1265 1266
			AdjustmentRule prev = null;
			foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
				if (prev != null && prev.DateEnd > current.DateStart) {
					adjustmentRules.Remove (current);
				}
				prev = current;
			}
1267
			return adjustmentRules.ToArray ();
1268 1269
		}

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
#if LIBC || MONOTOUCH
		const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
		
		private static TimeZoneInfo BuildFromStream (string id, Stream stream)
		{
			byte [] buffer = new byte [BUFFER_SIZE];
			int length = stream.Read (buffer, 0, BUFFER_SIZE);
			
			if (!ValidTZFile (buffer, length))
				throw new InvalidTimeZoneException ("TZ file too big for the buffer");

			try {
				return ParseTZBuffer (id, buffer, length);
1283 1284
			} catch (InvalidTimeZoneException) {
				throw;
1285
			} catch (Exception e) {
1286
				throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1287 1288 1289
			}
		}

1290 1291
		private static bool ValidTZFile (byte [] buffer, int length)
		{
1292
			StringBuilder magic = new StringBuilder ();
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305

			for (int i = 0; i < 4; i++)
				magic.Append ((char)buffer [i]);
			
			if (magic.ToString () != "TZif")
				return false;

			if (length >= BUFFER_SIZE)
				return false;

			return true;
		}

1306 1307 1308 1309 1310
		static int SwapInt32 (int i)
		{
			return (((i >> 24) & 0xff)
				| ((i >> 8) & 0xff00)
				| ((i << 8) & 0xff0000)
1311
				| (((i & 0xff) << 24)));
1312 1313 1314
		}

		static int ReadBigEndianInt32 (byte [] buffer, int start)
1315
		{
1316 1317 1318
			int i = BitConverter.ToInt32 (buffer, start);
			if (!BitConverter.IsLittleEndian)
				return i;
1319

1320 1321 1322 1323 1324
			return SwapInt32 (i);
		}

		private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
		{
1325
			//Reading the header. 4 bytes for magic, 16 are reserved
1326 1327 1328 1329 1330 1331
			int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
			int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
			int leapcnt = ReadBigEndianInt32 (buffer, 28);
			int timecnt = ReadBigEndianInt32 (buffer, 32);
			int typecnt = ReadBigEndianInt32 (buffer, 36);
			int charcnt = ReadBigEndianInt32 (buffer, 40);
1332

1333
			if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1334 1335
				throw new InvalidTimeZoneException ();

1336 1337
			Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
			Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1338
			List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1339 1340 1341 1342

			if (time_types.Count == 0)
				throw new InvalidTimeZoneException ();

1343
			if (time_types.Count == 1 && time_types[0].IsDst)
1344 1345 1346 1347 1348 1349 1350 1351 1352
				throw new InvalidTimeZoneException ();

			TimeSpan baseUtcOffset = new TimeSpan (0);
			TimeSpan dstDelta = new TimeSpan (0);
			string standardDisplayName = null;
			string daylightDisplayName = null;
			bool dst_observed = false;
			DateTime dst_start = DateTime.MinValue;
			List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1353
			bool storeTransition = false;
1354 1355

			for (int i = 0; i < transitions.Count; i++) {
1356 1357 1358
				var pair = transitions [i];
				DateTime ttime = pair.Key;
				TimeType ttype = pair.Value;
1359
				if (!ttype.IsDst) {
1360
					if (standardDisplayName != ttype.Name)
1361
						standardDisplayName = ttype.Name;
1362
					if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1363
						baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1364 1365
						if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
							storeTransition = true;
1366 1367 1368 1369 1370 1371 1372 1373 1374
						adjustmentRules = new List<AdjustmentRule> ();
						dst_observed = false;
					}
					if (dst_observed) {
						//FIXME: check additional fields for this:
						//most of the transitions are expressed in GMT 
						dst_start += baseUtcOffset;
						DateTime dst_end = ttime + baseUtcOffset + dstDelta;

1375 1376 1377 1378
						//some weird timezone (America/Phoenix) have end dates on Jan 1st
						if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
							dst_end -= new TimeSpan (24, 0, 0);

1379 1380 1381 1382 1383 1384 1385 1386
						/*
						 * AdjustmentRule specifies a DST period that starts and ends within a year.
						 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
						 * Thus we fallback to the transitions.
						 */
						if (dst_start.AddYears (1) < dst_end)
							storeTransition = true;

1387 1388 1389 1390 1391 1392 1393
						DateTime dateStart, dateEnd;
						if (dst_start.Month < 7)
							dateStart = new DateTime (dst_start.Year, 1, 1);
						else
							dateStart = new DateTime (dst_start.Year, 7, 1);

						if (dst_end.Month >= 7)
1394
							dateEnd = new DateTime (dst_end.Year, 12, 31);
1395 1396 1397
						else
							dateEnd = new DateTime (dst_end.Year, 6, 30);

1398
						
1399 1400
						TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
						TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1401 1402
						if  (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
							adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1403 1404 1405
					}
					dst_observed = false;
				} else {
1406
					if (daylightDisplayName != ttype.Name)
1407
						daylightDisplayName = ttype.Name;
1408 1409 1410
					if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
						// Round to nearest minute, since it's not possible to create an adjustment rule
						// with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1411 1412 1413 1414
						// This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
						dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
						if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
							dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1415
					}
1416

1417 1418 1419 1420 1421
					dst_start = ttime;
					dst_observed = true;
				}
			}

1422 1423
			TimeZoneInfo tz;
			if (adjustmentRules.Count == 0 && !storeTransition) {
1424
				if (standardDisplayName == null) {
1425
					var t = time_types [0];
1426 1427 1428
					standardDisplayName = t.Name;
					baseUtcOffset = new TimeSpan (0, 0, t.Offset);
				}
1429
				tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1430
			} else {
1431
				tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules));
1432
			}
1433

1434
			if (storeTransition && transitions.Count > 0) {
1435
				tz.transitions = transitions;
1436
			}
1437
			tz.supportsDaylightSavingTime = adjustmentRules.Count > 0;
1438 1439

			return tz;
1440 1441
		}

1442
		static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1443
		{
1444
			var abbrevs = new Dictionary<int, string> ();
1445
			int abbrev_index = 0;
1446
			var sb = new StringBuilder ();
1447 1448 1449 1450 1451 1452
			for (int i = 0; i < count; i++) {
				char c = (char) buffer [index + i];
				if (c != '\0')
					sb.Append (c);
				else {
					abbrevs.Add (abbrev_index, sb.ToString ());
1453
					//Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1454 1455
					//j == sb.Length empty substring also needs to be added #31432
					for (int j = 1; j <= sb.Length; j++)
1456
						abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1457
					abbrev_index = i + 1;
1458
					sb = new StringBuilder ();
1459 1460 1461 1462 1463
				}
			}
			return abbrevs;
		}

1464
		static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1465
		{
1466
			var types = new Dictionary<int, TimeType> (count);
1467
			for (int i = 0; i < count; i++) {
1468
				int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482

				//
				// The official tz database contains timezone with GMT offsets
				// not only in whole hours/minutes but in seconds. This happens for years
				// before 1901. For example
				//
				// NAME		        GMTOFF   RULES	FORMAT	UNTIL
				// Europe/Madrid	-0:14:44 -	LMT	1901 Jan  1  0:00s
				//
				// .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
				// we remove seconds to not crash later
				//
				offset = (offset / 60) * 60;

1483 1484
				byte is_dst = buffer [index + 6 * i + 4];
				byte abbrev = buffer [index + 6 * i + 5];
1485
				types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1486 1487 1488 1489
			}
			return types;
		}

1490
		static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1491
		{
1492
			var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1493
			for (int i = 0; i < count; i++) {
1494
				int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1495 1496
				DateTime ttime = DateTimeFromUnixTime (unixtime);
				byte ttype = buffer [index + 4 * count + i];
1497
				list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1498
			}
1499
			return list;
1500 1501
		}

1502
		static DateTime DateTimeFromUnixTime (long unix_time)
1503 1504 1505 1506
		{
			DateTime date_time = new DateTime (1970, 1, 1);
			return date_time.AddSeconds (unix_time);
		}
1507 1508

#region reference sources
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529
		// Shortcut for TimeZoneInfo.Local.GetUtcOffset
		internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
		{
			bool dst;
			return Local.GetUtcOffset (dateTime, out dst);
		}

		internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
		{
			bool dst;
			return GetUtcOffset (dateTime, out dst);
		}

		static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
		{
			isDaylightSavings = false;
			isAmbiguousLocalDst = false;
			TimeSpan baseOffset = zone.BaseUtcOffset;

			if (zone.IsAmbiguousTime (time)) {
				isAmbiguousLocalDst = true;
1530
//				return baseOffset;
1531 1532 1533 1534
			}

			return zone.GetUtcOffset (time, out isDaylightSavings);
		}
1535
#endregion
1536 1537
	}

1538
	class TimeType {
1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555
		public readonly int Offset;
		public readonly bool IsDst;
		public string Name;

		public TimeType (int offset, bool is_dst, string abbrev)
		{
			this.Offset = offset;
			this.IsDst = is_dst;
			this.Name = abbrev;
		}

		public override string ToString ()
		{
			return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
		}
#else
	}
1556 1557 1558
#endif
	}
}