[LoongArch64] Part-5:add loongarch support in some files for LoongArch64. (#21769)
[mono-project.git] / mcs / class / corlib / System / TimeZoneInfo.cs
blob9c6f0d7b6154ac6729ae372b1f83a8e58b3fcf82
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) || MOBILE_DESKTOP_HOST
153 #if WASM
154 [MethodImplAttribute(MethodImplOptions.InternalCall)]
155 extern static void mono_timezone_get_local_name (ref string name);
156 #endif
157 static TimeZoneInfo CreateLocal ()
159 #if WIN_PLATFORM
160 if (IsWindows && LocalZoneKey != null) {
161 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
162 if (name == null)
163 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
164 name = TrimSpecial (name);
165 if (name != null)
166 return TimeZoneInfo.FindSystemTimeZoneById (name);
167 } else if (IsWindows) {
168 return GetLocalTimeZoneInfoWinRTFallback ();
170 #endif
171 #if WASM
172 string localName = null;
173 mono_timezone_get_local_name (ref localName);
174 try {
175 return FindSystemTimeZoneByFileName (localName, Path.Combine (TimeZoneDirectory, localName));
176 } catch {
177 return Utc;
179 #else
180 var tz = Environment.GetEnvironmentVariable ("TZ");
181 if (tz != null) {
182 if (tz == String.Empty)
183 return Utc;
184 try {
185 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
186 } catch {
187 return Utc;
191 var tzFilePaths = new string [] {
192 "/etc/localtime",
193 Path.Combine (TimeZoneDirectory, "localtime")};
195 foreach (var tzFilePath in tzFilePaths) {
196 try {
197 string tzName = null;
198 if (!TryGetNameFromPath (tzFilePath, out tzName))
199 tzName = "Local";
200 return FindSystemTimeZoneByFileName (tzName, tzFilePath);
201 } catch (TimeZoneNotFoundException) {
202 continue;
206 return Utc;
207 #endif
210 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
212 #if LIBC
213 string filepath = Path.Combine (TimeZoneDirectory, id);
214 return FindSystemTimeZoneByFileName (id, filepath);
215 #else
216 throw new NotImplementedException ();
217 #endif
220 static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
222 #if WIN_PLATFORM
223 if (TimeZoneKey != null) {
224 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
225 using (RegistryKey subkey = TimeZoneKey.OpenSubKey (id))
227 if (subkey == null || subkey.GetValue ("TZI") == null)
228 continue;
230 systemTimeZones.Add (FindSystemTimeZoneById (id));
233 return;
234 } else if (IsWindows) {
235 systemTimeZones.AddRange (GetSystemTimeZonesWinRTFallback ());
236 return;
238 #endif
240 #if LIBC
241 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
242 foreach (string continent in continents) {
243 try {
244 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
245 try {
246 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
247 systemTimeZones.Add (FindSystemTimeZoneById (id));
248 } catch (ArgumentNullException) {
249 } catch (TimeZoneNotFoundException) {
250 } catch (InvalidTimeZoneException) {
251 } catch (Exception) {
252 throw;
255 } catch {}
257 #else
258 throw new NotImplementedException ("This method is not implemented for this platform");
259 #endif
261 #endif // !MONODROID && !MONOTOUCH && !XAMMAC && !WASM
263 string standardDisplayName;
264 public string StandardName {
265 get { return standardDisplayName; }
268 bool supportsDaylightSavingTime;
269 public bool SupportsDaylightSavingTime {
270 get { return supportsDaylightSavingTime; }
273 static TimeZoneInfo utc;
274 public static TimeZoneInfo Utc {
275 get {
276 if (utc == null)
277 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
278 return utc;
281 #if LIBC
282 #if WASM
283 const string DefaultTimeZoneDirectory = "/zoneinfo";
284 #else
285 const string DefaultTimeZoneDirectory = "/usr/share/zoneinfo";
286 #endif
287 static string timeZoneDirectory;
288 static string TimeZoneDirectory {
289 get {
290 if (timeZoneDirectory == null)
291 timeZoneDirectory = readlink (DefaultTimeZoneDirectory) ?? DefaultTimeZoneDirectory;
292 return timeZoneDirectory;
294 set {
295 ClearCachedData ();
296 timeZoneDirectory = value;
299 #endif
300 private AdjustmentRule [] adjustmentRules;
302 #if (!MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM) && !XAMMAC_4_5
303 /// <summary>
304 /// Determine whether windows of not (taken Stephane Delcroix's code)
305 /// </summary>
306 private static bool IsWindows
308 get {
309 int platform = (int) Environment.OSVersion.Platform;
310 return ((platform != 4) && (platform != 6) && (platform != 128));
314 /// <summary>
315 /// Needed to trim misc garbage in MS registry keys
316 /// </summary>
317 private static string TrimSpecial (string str)
319 if (str == null)
320 return str;
321 var Istart = 0;
322 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
323 var Iend = str.Length - 1;
324 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend]) && str[Iend] != ')') // zone name can include parentheses like "Central Standard Time (Mexico)"
325 Iend--;
327 return str.Substring (Istart, Iend-Istart+1);
330 #if !FULL_AOT_DESKTOP || WIN_PLATFORM
331 static RegistryKey timeZoneKey;
332 static RegistryKey TimeZoneKey {
333 get {
334 if (timeZoneKey != null)
335 return timeZoneKey;
336 if (!IsWindows)
337 return null;
339 try {
340 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
341 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
342 false);
343 } catch {
344 return null;
349 static RegistryKey localZoneKey;
350 static RegistryKey LocalZoneKey {
351 get {
352 if (localZoneKey != null)
353 return localZoneKey;
355 if (!IsWindows)
356 return null;
358 try {
359 return localZoneKey = Registry.LocalMachine.OpenSubKey (
360 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
361 } catch {
362 return null;
366 #endif
367 #endif // !MOBILE || !FULL_AOT_DESKTOP || WIN_PLATFORM
369 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
371 var resultTicks = date.Ticks + ticks;
372 if (resultTicks < DateTime.MinValue.Ticks) {
373 result = DateTime.SpecifyKind (DateTime.MinValue, kind);
374 return false;
377 if (resultTicks > DateTime.MaxValue.Ticks) {
378 result = DateTime.SpecifyKind (DateTime.MaxValue, kind);
379 return false;
382 result = new DateTime (resultTicks, kind);
383 return true;
386 public static void ClearCachedData ()
388 local = null;
389 utc = null;
390 systemTimeZones = null;
393 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
395 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
398 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
400 if (sourceTimeZone == null)
401 throw new ArgumentNullException ("sourceTimeZone");
403 if (destinationTimeZone == null)
404 throw new ArgumentNullException ("destinationTimeZone");
406 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
407 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
409 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
410 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
412 if (sourceTimeZone.IsInvalidTime (dateTime))
413 throw new ArgumentException ("dateTime parameter is an invalid time");
415 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
416 return dateTime;
418 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
420 if (destinationTimeZone != TimeZoneInfo.Utc) {
421 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
422 if (dateTime.Kind == DateTimeKind.Unspecified)
423 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
426 return utc;
429 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
431 if (destinationTimeZone == null)
432 throw new ArgumentNullException("destinationTimeZone");
434 var utcDateTime = dateTimeOffset.UtcDateTime;
436 bool isDst;
437 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
439 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
442 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
444 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
447 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
449 TimeZoneInfo source_tz;
450 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZoneId == TimeZoneInfo.Utc.Id) {
451 source_tz = Utc;
452 } else {
453 source_tz = FindSystemTimeZoneById (sourceTimeZoneId);
456 return ConvertTime (dateTime, source_tz, FindSystemTimeZoneById (destinationTimeZoneId));
459 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
461 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
464 private DateTime ConvertTimeFromUtc (DateTime dateTime)
466 if (dateTime.Kind == DateTimeKind.Local)
467 throw new ArgumentException ("Kind property of dateTime is Local");
469 if (this == TimeZoneInfo.Utc)
470 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
472 var utcOffset = GetUtcOffset (dateTime);
474 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
476 DateTime result;
477 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
478 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
480 return result;
483 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
485 if (destinationTimeZone == null)
486 throw new ArgumentNullException ("destinationTimeZone");
488 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
491 public static DateTime ConvertTimeToUtc (DateTime dateTime)
493 if (dateTime.Kind == DateTimeKind.Utc)
494 return dateTime;
496 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
499 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
501 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
504 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
506 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
509 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
511 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
512 if (sourceTimeZone == null)
513 throw new ArgumentNullException ("sourceTimeZone");
515 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
516 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
518 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
519 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
521 if (sourceTimeZone.IsInvalidTime (dateTime))
522 throw new ArgumentException ("dateTime parameter is an invalid time");
525 if (dateTime.Kind == DateTimeKind.Utc)
526 return dateTime;
528 bool isDst;
529 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
531 DateTime utcDateTime;
532 TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc);
533 return utcDateTime;
536 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
538 bool isDaylightSavings;
539 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
542 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
544 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
547 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
549 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
552 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
554 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
557 public override bool Equals (object obj)
559 return Equals (obj as TimeZoneInfo);
562 public bool Equals (TimeZoneInfo other)
564 if (other == null)
565 return false;
567 return other.Id == this.Id && HasSameRules (other);
570 public static TimeZoneInfo FindSystemTimeZoneById (string id)
572 //FIXME: this method should check for cached values in systemTimeZones
573 if (id == null)
574 throw new ArgumentNullException ("id");
575 #if WIN_PLATFORM
576 if (TimeZoneKey != null)
578 if (id == "Coordinated Universal Time")
579 id = "UTC"; //windows xp exception for "StandardName" property
580 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
581 if (key == null)
582 throw new TimeZoneNotFoundException ();
583 return FromRegistryKey(id, key);
584 } else if (IsWindows) {
585 return FindSystemTimeZoneByIdWinRTFallback (id);
587 #endif
588 // Local requires special logic that already exists in the Local property (bug #326)
589 if (id == "Local")
590 return Local;
592 return FindSystemTimeZoneByIdCore (id);
595 #if LIBC
596 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
598 FileStream stream = null;
599 try {
600 stream = File.OpenRead (filepath);
601 } catch (Exception ex) {
602 throw new TimeZoneNotFoundException ("Couldn't read time zone file " + filepath, ex);
604 try {
605 return BuildFromStream (id, stream);
606 } finally {
607 if (stream != null)
608 stream.Dispose();
611 #endif
613 #if WIN_PLATFORM
614 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
616 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
618 if (reg_tzi == null)
619 throw new InvalidTimeZoneException ();
621 int bias = BitConverter.ToInt32 (reg_tzi, 0);
622 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
624 string display_name = (string) key.GetValue ("Display");
625 string standard_name = (string) key.GetValue ("Std");
626 string daylight_name = (string) key.GetValue ("Dlt");
628 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
630 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
631 if (dst_key != null) {
632 int first_year = (int) dst_key.GetValue ("FirstEntry");
633 int last_year = (int) dst_key.GetValue ("LastEntry");
634 int year;
636 for (year=first_year; year<=last_year; year++) {
637 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
638 if (dst_tzi != null) {
639 int start_year = year == first_year ? 1 : year;
640 int end_year = year == last_year ? 9999 : year;
641 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
645 else
646 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
648 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules));
651 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
653 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
654 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
656 int standard_year = BitConverter.ToInt16 (buffer, 12);
657 int standard_month = BitConverter.ToInt16 (buffer, 14);
658 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
659 int standard_day = BitConverter.ToInt16 (buffer, 18);
660 int standard_hour = BitConverter.ToInt16 (buffer, 20);
661 int standard_minute = BitConverter.ToInt16 (buffer, 22);
662 int standard_second = BitConverter.ToInt16 (buffer, 24);
663 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
665 int daylight_year = BitConverter.ToInt16 (buffer, 28);
666 int daylight_month = BitConverter.ToInt16 (buffer, 30);
667 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
668 int daylight_day = BitConverter.ToInt16 (buffer, 34);
669 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
670 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
671 int daylight_second = BitConverter.ToInt16 (buffer, 40);
672 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
674 if (standard_month == 0 || daylight_month == 0)
675 return;
677 DateTime start_date;
678 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
679 TransitionTime start_transition_time;
681 start_date = new DateTime (start_year, 1, 1);
682 if (daylight_year == 0) {
683 start_transition_time = TransitionTime.CreateFloatingDateRule (
684 start_timeofday, daylight_month, daylight_day,
685 (DayOfWeek) daylight_dayofweek);
687 else {
688 start_transition_time = TransitionTime.CreateFixedDateRule (
689 start_timeofday, daylight_month, daylight_day);
692 DateTime end_date;
693 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
694 TransitionTime end_transition_time;
696 end_date = new DateTime (end_year, 12, 31);
697 if (standard_year == 0) {
698 end_transition_time = TransitionTime.CreateFloatingDateRule (
699 end_timeofday, standard_month, standard_day,
700 (DayOfWeek) standard_dayofweek);
702 else {
703 end_transition_time = TransitionTime.CreateFixedDateRule (
704 end_timeofday, standard_month, standard_day);
707 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
709 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
710 start_date, end_date, daylight_delta,
711 start_transition_time, end_transition_time));
713 #endif
715 public AdjustmentRule [] GetAdjustmentRules ()
717 if (!supportsDaylightSavingTime || adjustmentRules == null)
718 return new AdjustmentRule [0];
719 else
720 return (AdjustmentRule []) adjustmentRules.Clone ();
723 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
725 if (!IsAmbiguousTime (dateTime))
726 throw new ArgumentException ("dateTime is not an ambiguous time");
728 AdjustmentRule rule = GetApplicableRule (dateTime);
729 if (rule != null)
730 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
731 else
732 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
735 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
737 if (!IsAmbiguousTime (dateTimeOffset))
738 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
740 throw new NotImplementedException ();
743 public override int GetHashCode ()
745 int hash_code = Id.GetHashCode ();
746 foreach (AdjustmentRule rule in GetAdjustmentRules ())
747 hash_code ^= rule.GetHashCode ();
748 return hash_code;
751 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
753 if (info == null)
754 throw new ArgumentNullException ("info");
755 info.AddValue ("Id", id);
756 info.AddValue ("DisplayName", displayName);
757 info.AddValue ("StandardName", standardDisplayName);
758 info.AddValue ("DaylightName", daylightDisplayName);
759 info.AddValue ("BaseUtcOffset", baseUtcOffset);
760 info.AddValue ("AdjustmentRules", adjustmentRules);
761 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
764 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
766 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
768 if (systemTimeZones == null) {
769 var tz = new List<TimeZoneInfo> ();
770 GetSystemTimeZonesCore (tz);
771 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
774 return systemTimeZones;
777 public TimeSpan GetUtcOffset (DateTime dateTime)
779 bool isDST;
780 return GetUtcOffset (dateTime, out isDST);
783 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
785 bool isDST;
786 return GetUtcOffset (dateTimeOffset.UtcDateTime, out isDST);
789 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST, bool forOffset = false)
791 isDST = false;
793 TimeZoneInfo tz = this;
794 if (dateTime.Kind == DateTimeKind.Utc)
795 tz = TimeZoneInfo.Utc;
797 if (dateTime.Kind == DateTimeKind.Local)
798 tz = TimeZoneInfo.Local;
800 bool isTzDst;
801 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst, forOffset);
803 if (tz == this) {
804 isDST = isTzDst;
805 return tzOffset;
808 DateTime utcDateTime;
809 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
810 return BaseUtcOffset;
812 return GetUtcOffsetHelper (utcDateTime, this, out isDST, forOffset);
815 // This is an helper method used by the method above, do not use this on its own.
816 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST, bool forOffset = false)
818 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
819 throw new Exception ();
821 isDST = false;
823 if (tz == TimeZoneInfo.Utc)
824 return TimeSpan.Zero;
826 TimeSpan offset;
827 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST, forOffset))
828 return offset;
830 if (dateTime.Kind == DateTimeKind.Utc) {
831 var utcRule = tz.GetApplicableRule (dateTime);
832 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
833 isDST = true;
834 return tz.BaseUtcOffset + utcRule.DaylightDelta;
837 return tz.BaseUtcOffset;
840 DateTime stdUtcDateTime;
841 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
842 return tz.BaseUtcOffset;
844 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
846 DateTime dstUtcDateTime = DateTime.MinValue;
847 if (tzRule != null) {
848 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
849 return tz.BaseUtcOffset;
852 if (tzRule != null && tz.IsInDST (tzRule, dateTime)) {
853 // Replicate what .NET does when given a time which falls into the hour which is lost when
854 // DST starts. isDST should be false and the offset should be BaseUtcOffset without the
855 // DST delta while in that hour.
856 if (forOffset)
857 isDST = true;
858 if (tz.IsInDST (tzRule, dstUtcDateTime)) {
859 isDST = true;
860 return tz.BaseUtcOffset + tzRule.DaylightDelta;
861 } else {
862 return tz.BaseUtcOffset;
866 return tz.BaseUtcOffset;
869 public bool HasSameRules (TimeZoneInfo other)
871 if (other == null)
872 throw new ArgumentNullException ("other");
874 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
875 return false;
877 if (this.adjustmentRules == null)
878 return true;
880 if (this.BaseUtcOffset != other.BaseUtcOffset)
881 return false;
883 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
884 return false;
886 for (int i = 0; i < adjustmentRules.Length; i++) {
887 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
888 return false;
891 return true;
894 public bool IsAmbiguousTime (DateTime dateTime)
896 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
897 throw new ArgumentException ("Kind is Local and time is Invalid");
899 if (this == TimeZoneInfo.Utc)
900 return false;
902 if (dateTime.Kind == DateTimeKind.Utc)
903 dateTime = ConvertTimeFromUtc (dateTime);
905 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
906 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
908 AdjustmentRule rule = GetApplicableRule (dateTime);
909 if (rule != null) {
910 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
911 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
912 return true;
915 return false;
918 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
920 throw new NotImplementedException ();
923 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
925 // Check whether we're in the dateTime year's DST period
926 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
927 return true;
929 // We might be in the dateTime previous year's DST period
930 return dateTime.Year > 1 && IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
933 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
935 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
936 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
937 if (dateTime.Kind == DateTimeKind.Utc) {
938 DST_start -= BaseUtcOffset;
939 DST_end -= BaseUtcOffset;
941 DST_end -= rule.DaylightDelta;
942 return (dateTime >= DST_start && dateTime < DST_end);
945 public bool IsDaylightSavingTime (DateTime dateTime)
947 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
948 throw new ArgumentException ("dateTime is invalid and Kind is Local");
950 if (this == TimeZoneInfo.Utc)
951 return false;
953 if (!SupportsDaylightSavingTime)
954 return false;
956 bool isDst;
957 GetUtcOffset (dateTime, out isDst);
959 return isDst;
962 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
964 return IsDaylightSavingTime (dateTime);
967 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
969 var dateTime = dateTimeOffset.DateTime;
971 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
972 throw new ArgumentException ("dateTime is invalid and Kind is Local");
974 if (this == TimeZoneInfo.Utc)
975 return false;
977 if (!SupportsDaylightSavingTime)
978 return false;
980 bool isDst;
981 GetUtcOffset (dateTime, out isDst, true);
983 return isDst;
986 internal DaylightTime GetDaylightChanges (int year)
988 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
989 TimeSpan delta = new TimeSpan ();
991 if (transitions != null) {
992 end = DateTime.MaxValue;
993 for (var i = transitions.Count - 1; i >= 0; i--) {
994 var pair = transitions [i];
995 DateTime ttime = pair.Key;
996 TimeType ttype = pair.Value;
998 if (ttime.Year > year)
999 continue;
1000 if (ttime.Year < year)
1001 break;
1003 if (ttype.IsDst) {
1004 // DaylightTime.Delta is relative to the current BaseUtcOffset.
1005 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
1006 start = ttime;
1007 } else {
1008 end = ttime;
1012 // DaylightTime.Start is relative to the Standard time.
1013 if (!TryAddTicks (start, BaseUtcOffset.Ticks, out start))
1014 start = DateTime.MinValue;
1016 // DaylightTime.End is relative to the DST time.
1017 if (!TryAddTicks (end, BaseUtcOffset.Ticks + delta.Ticks, out end))
1018 end = DateTime.MinValue;
1019 } else {
1020 AdjustmentRule first = null, last = null;
1022 // Rule start/end dates are either very specific or very broad depending on the platform
1023 // 2015-10-04..2016-04-03 - Rule for a time zone in southern hemisphere on non-Windows platforms
1024 // 2016-03-27..2016-10-03 - Rule for a time zone in northern hemisphere on non-Windows platforms
1025 // 0001-01-01..9999-12-31 - Rule for a time zone on Windows
1027 foreach (var rule in GetAdjustmentRules ()) {
1028 if (rule.DateStart.Year > year || rule.DateEnd.Year < year)
1029 continue;
1030 if (rule.DateStart.Year <= year && (first == null || rule.DateStart.Year > first.DateStart.Year))
1031 first = rule;
1032 if (rule.DateEnd.Year >= year && (last == null || rule.DateEnd.Year < last.DateEnd.Year))
1033 last = rule;
1036 if (first == null || last == null)
1037 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1039 start = TransitionPoint (first.DaylightTransitionStart, year);
1040 end = TransitionPoint (last.DaylightTransitionEnd, year);
1041 delta = first.DaylightDelta;
1044 if (start == DateTime.MinValue || end == DateTime.MinValue)
1045 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
1047 return new DaylightTime (start, end, delta);
1050 public bool IsInvalidTime (DateTime dateTime)
1052 if (dateTime.Kind == DateTimeKind.Utc)
1053 return false;
1054 if (dateTime.Kind == DateTimeKind.Local && this != Local)
1055 return false;
1057 AdjustmentRule rule = GetApplicableRule (dateTime);
1058 if (rule != null) {
1059 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
1060 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
1061 return true;
1064 return false;
1067 void IDeserializationCallback.OnDeserialization (object sender)
1069 try {
1070 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
1071 } catch (ArgumentException ex) {
1072 throw new SerializationException ("invalid serialization data", ex);
1076 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
1078 if (id == null)
1079 throw new ArgumentNullException ("id");
1081 if (id == String.Empty)
1082 throw new ArgumentException ("id parameter is an empty string");
1084 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1085 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1087 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1088 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1090 #if STRICT
1091 if (id.Length > 32)
1092 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1093 #endif
1095 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1096 AdjustmentRule prev = null;
1097 foreach (AdjustmentRule current in adjustmentRules) {
1098 if (current == null)
1099 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1101 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1102 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1103 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;");
1105 if (prev != null && prev.DateStart > current.DateStart)
1106 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1108 if (prev != null && prev.DateEnd > current.DateStart)
1109 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1111 if (prev != null && prev.DateEnd == current.DateStart)
1112 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1114 prev = current;
1119 public override string ToString ()
1121 return DisplayName;
1124 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1126 if (info == null)
1127 throw new ArgumentNullException ("info");
1128 id = (string) info.GetValue ("Id", typeof (string));
1129 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1130 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1131 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1132 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1133 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1134 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1137 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1139 if (id == null)
1140 throw new ArgumentNullException ("id");
1142 if (id == String.Empty)
1143 throw new ArgumentException ("id parameter is an empty string");
1145 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1146 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1148 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1149 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1151 #if STRICT
1152 if (id.Length > 32)
1153 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1154 #endif
1156 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1158 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1159 AdjustmentRule prev = null;
1160 foreach (AdjustmentRule current in adjustmentRules) {
1161 if (current == null)
1162 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1164 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1165 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1166 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;");
1168 if (prev != null && prev.DateStart > current.DateStart)
1169 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1171 if (prev != null && prev.DateEnd > current.DateStart)
1172 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1174 if (prev != null && prev.DateEnd == current.DateStart)
1175 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1177 prev = current;
1179 } else {
1180 supportsDaylightSavingTime = false;
1183 this.id = id;
1184 this.baseUtcOffset = baseUtcOffset;
1185 this.displayName = displayName ?? id;
1186 this.standardDisplayName = standardDisplayName ?? id;
1187 this.daylightDisplayName = daylightDisplayName;
1188 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1189 this.adjustmentRules = adjustmentRules;
1192 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1194 //Applicable rules are in standard time
1195 DateTime date = dateTime;
1197 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1198 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1199 return null;
1200 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1201 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1202 return null;
1205 // get the date component of the datetime
1206 date = date.Date;
1208 if (adjustmentRules != null) {
1209 foreach (AdjustmentRule rule in adjustmentRules) {
1210 if (rule.DateStart > date)
1211 return null;
1212 if (rule.DateEnd < date)
1213 continue;
1214 return rule;
1217 return null;
1220 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset, out bool isDst, bool forOffset = false)
1222 offset = BaseUtcOffset;
1223 isDst = false;
1225 if (transitions == null)
1226 return false;
1228 //Transitions are in UTC
1229 DateTime date = dateTime;
1231 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1232 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1233 return false;
1236 var isUtc = false;
1237 if (dateTime.Kind != DateTimeKind.Utc) {
1238 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1239 return false;
1240 } else
1241 isUtc = true;
1244 AdjustmentRule current = GetApplicableRule (date);
1245 if (current != null) {
1246 DateTime tStart = TransitionPoint (current.DaylightTransitionStart, date.Year);
1247 DateTime tEnd = TransitionPoint (current.DaylightTransitionEnd, date.Year);
1248 TryAddTicks (tStart, -BaseUtcOffset.Ticks, out tStart, DateTimeKind.Utc);
1249 TryAddTicks (tEnd, -BaseUtcOffset.Ticks, out tEnd, DateTimeKind.Utc);
1250 if ((date >= tStart) && (date <= tEnd)) {
1251 if (forOffset)
1252 isDst = true;
1253 offset = baseUtcOffset;
1254 if (isUtc || (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc)))
1256 offset += current.DaylightDelta;
1257 isDst = true;
1260 if (date >= new DateTime (tEnd.Ticks - current.DaylightDelta.Ticks, DateTimeKind.Utc))
1262 offset = baseUtcOffset;
1263 isDst = false;
1266 return true;
1269 return false;
1272 private static DateTime TransitionPoint (TransitionTime transition, int year)
1274 if (transition.IsFixedDateRule) {
1275 var daysInMonth = DateTime.DaysInMonth (year, transition.Month);
1276 var transitionDay = transition.Day <= daysInMonth ? transition.Day : daysInMonth;
1277 return new DateTime (year, transition.Month, transitionDay) + transition.TimeOfDay.TimeOfDay;
1280 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1281 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1282 if (day > DateTime.DaysInMonth (year, transition.Month))
1283 day -= 7;
1284 if (day < 1)
1285 day += 7;
1286 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1289 static AdjustmentRule[] ValidateRules (List<AdjustmentRule> adjustmentRules)
1291 if (adjustmentRules == null || adjustmentRules.Count == 0)
1292 return null;
1294 AdjustmentRule prev = null;
1295 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1296 if (prev != null && prev.DateEnd > current.DateStart) {
1297 adjustmentRules.Remove (current);
1299 prev = current;
1301 return adjustmentRules.ToArray ();
1304 #if LIBC || MONOTOUCH
1305 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1307 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1309 byte [] buffer = new byte [BUFFER_SIZE];
1310 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1312 if (!ValidTZFile (buffer, length))
1313 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1315 try {
1316 return ParseTZBuffer (id, buffer, length);
1317 } catch (InvalidTimeZoneException) {
1318 throw;
1319 } catch (Exception e) {
1320 throw new InvalidTimeZoneException ("Time zone information file contains invalid data", e);
1324 private static bool ValidTZFile (byte [] buffer, int length)
1326 StringBuilder magic = new StringBuilder ();
1328 for (int i = 0; i < 4; i++)
1329 magic.Append ((char)buffer [i]);
1331 if (magic.ToString () != "TZif")
1332 return false;
1334 if (length >= BUFFER_SIZE)
1335 return false;
1337 return true;
1340 static int SwapInt32 (int i)
1342 return (((i >> 24) & 0xff)
1343 | ((i >> 8) & 0xff00)
1344 | ((i << 8) & 0xff0000)
1345 | (((i & 0xff) << 24)));
1348 static int ReadBigEndianInt32 (byte [] buffer, int start)
1350 int i = BitConverter.ToInt32 (buffer, start);
1351 if (!BitConverter.IsLittleEndian)
1352 return i;
1354 return SwapInt32 (i);
1357 static long ReadBigEndianInt64(byte[] buffer, int start)
1359 byte[] longBytes = new byte[8];
1360 for (int i = 0; i < 8; i++) {
1361 longBytes[i] = buffer[start + i];
1364 if (BitConverter.IsLittleEndian) {
1365 Array.Reverse(longBytes);
1368 return BitConverter.ToInt64(longBytes, 0);
1371 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1373 //Reading the header. 4 bytes for magic, 16 are reserved
1374 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1375 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1376 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1377 int timecnt = ReadBigEndianInt32 (buffer, 32);
1378 int typecnt = ReadBigEndianInt32 (buffer, 36);
1379 int charcnt = ReadBigEndianInt32 (buffer, 40);
1381 byte version = buffer[4];
1382 int index = 0;
1383 int timeValuesLength = 4;
1384 if (version == '2' || version == '3') {
1385 index += 44 + (int)((timeValuesLength * timecnt) + timecnt + (6 * typecnt) + ((timeValuesLength + 4) * leapcnt) + ttisstdcnt + ttisgmtcnt + charcnt);
1386 // move index past the V1 information to read the V2 information
1388 ttisgmtcnt = ReadBigEndianInt32 (buffer, 20 + index);
1389 ttisstdcnt = ReadBigEndianInt32 (buffer, 24 + index);
1390 leapcnt = ReadBigEndianInt32 (buffer, 28 + index);
1391 timecnt = ReadBigEndianInt32 (buffer, 32 + index);
1392 typecnt = ReadBigEndianInt32 (buffer, 36 + index);
1393 charcnt = ReadBigEndianInt32 (buffer, 40 + index);
1395 timeValuesLength = 8;
1398 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1399 throw new InvalidTimeZoneException ();
1401 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, index + 44 + timeValuesLength * timecnt + timecnt + 6 * typecnt, charcnt);
1402 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, index + 44 + timeValuesLength * timecnt + timecnt, typecnt, abbreviations);
1403 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, index + 44, timecnt, timeValuesLength, time_types);
1405 if (time_types.Count == 0)
1406 throw new InvalidTimeZoneException ();
1408 if (time_types.Count == 1 && time_types[0].IsDst)
1409 throw new InvalidTimeZoneException ();
1411 TimeSpan baseUtcOffset = new TimeSpan (0);
1412 TimeSpan dstDelta = new TimeSpan (0);
1413 string standardDisplayName = null;
1414 string daylightDisplayName = null;
1415 bool dst_observed = false;
1416 DateTime dst_start = DateTime.MinValue;
1417 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1418 bool storeTransition = false;
1420 for (int i = 0; i < transitions.Count; i++) {
1421 var pair = transitions [i];
1422 DateTime ttime = pair.Key;
1423 TimeType ttype = pair.Value;
1424 if (!ttype.IsDst) {
1425 if (standardDisplayName != ttype.Name)
1426 standardDisplayName = ttype.Name;
1427 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1428 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1429 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1430 storeTransition = true;
1431 adjustmentRules = new List<AdjustmentRule> ();
1432 dst_observed = false;
1434 if (dst_observed) {
1435 //FIXME: check additional fields for this:
1436 //most of the transitions are expressed in GMT
1437 dst_start += baseUtcOffset;
1438 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1440 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1441 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1442 dst_end -= new TimeSpan (24, 0, 0);
1445 * AdjustmentRule specifies a DST period that starts and ends within a year.
1446 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1447 * Thus we fallback to the transitions.
1449 if (dst_start.AddYears (1) < dst_end)
1450 storeTransition = true;
1452 DateTime dateStart, dateEnd;
1453 if (dst_start.Month < 7)
1454 dateStart = new DateTime (dst_start.Year, 1, 1);
1455 else
1456 dateStart = new DateTime (dst_start.Year, 7, 1);
1458 if (dst_end.Month >= 7)
1459 dateEnd = new DateTime (dst_end.Year, 12, 31);
1460 else
1461 dateEnd = new DateTime (dst_end.Year, 6, 30);
1464 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1465 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1466 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1467 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1469 dst_observed = false;
1470 } else {
1471 if (daylightDisplayName != ttype.Name)
1472 daylightDisplayName = ttype.Name;
1473 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1474 // Round to nearest minute, since it's not possible to create an adjustment rule
1475 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1476 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1477 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1478 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1479 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1482 dst_start = ttime;
1483 dst_observed = true;
1487 TimeZoneInfo tz;
1488 if (adjustmentRules.Count == 0 && !storeTransition) {
1489 if (standardDisplayName == null) {
1490 var t = time_types [0];
1491 standardDisplayName = t.Name;
1492 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1494 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1495 } else {
1496 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules));
1499 if (storeTransition && transitions.Count > 0) {
1500 tz.transitions = transitions;
1502 tz.supportsDaylightSavingTime = adjustmentRules.Count > 0;
1504 return tz;
1507 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1509 var abbrevs = new Dictionary<int, string> ();
1510 int abbrev_index = 0;
1511 var sb = new StringBuilder ();
1512 for (int i = 0; i < count; i++) {
1513 char c = (char) buffer [index + i];
1514 if (c != '\0')
1515 sb.Append (c);
1516 else {
1517 abbrevs.Add (abbrev_index, sb.ToString ());
1518 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1519 //j == sb.Length empty substring also needs to be added #31432
1520 for (int j = 1; j <= sb.Length; j++)
1521 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1522 abbrev_index = i + 1;
1523 sb = new StringBuilder ();
1526 return abbrevs;
1529 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1531 var types = new Dictionary<int, TimeType> (count);
1532 for (int i = 0; i < count; i++) {
1533 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1536 // The official tz database contains timezone with GMT offsets
1537 // not only in whole hours/minutes but in seconds. This happens for years
1538 // before 1901. For example
1540 // NAME GMTOFF RULES FORMAT UNTIL
1541 // Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
1543 // .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
1544 // we remove seconds to not crash later
1546 offset = (offset / 60) * 60;
1548 byte is_dst = buffer [index + 6 * i + 4];
1549 byte abbrev = buffer [index + 6 * i + 5];
1550 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1552 return types;
1555 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, int timeValuesLength, Dictionary<int, TimeType> time_types)
1557 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1558 for (int i = 0; i < count; i++) {
1559 long unixtime = 0;
1560 if (timeValuesLength == 8) {
1561 unixtime = ReadBigEndianInt64 (buffer, index + timeValuesLength * i);
1562 } else {
1563 unixtime = ReadBigEndianInt32 (buffer, index + timeValuesLength * i);
1566 DateTime ttime = DateTimeFromUnixTime (unixtime);
1567 byte ttype = buffer [index + timeValuesLength * count + i];
1568 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1570 return list;
1573 static DateTime DateTimeFromUnixTime (long unix_time) =>
1574 unix_time < DateTimeOffset.UnixMinSeconds ? DateTime.MinValue :
1575 unix_time > DateTimeOffset.UnixMaxSeconds ? DateTime.MaxValue :
1576 DateTimeOffset.FromUnixTimeSeconds(unix_time).UtcDateTime;
1578 #region reference sources
1579 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1580 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1582 bool dst;
1583 return Local.GetUtcOffset (dateTime, out dst);
1586 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1588 bool dst;
1589 return GetUtcOffset (dateTime, out dst);
1592 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1594 isDaylightSavings = false;
1595 isAmbiguousLocalDst = false;
1596 TimeSpan baseOffset = zone.BaseUtcOffset;
1598 if (zone.IsAmbiguousTime (time)) {
1599 isAmbiguousLocalDst = true;
1600 // return baseOffset;
1603 return zone.GetUtcOffset (time, out isDaylightSavings);
1605 #endregion
1608 class TimeType {
1609 public readonly int Offset;
1610 public readonly bool IsDst;
1611 public string Name;
1613 public TimeType (int offset, bool is_dst, string abbrev)
1615 this.Offset = offset;
1616 this.IsDst = is_dst;
1617 this.Name = abbrev;
1620 public override string ToString ()
1622 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
1624 #else
1626 #endif