6 * Stephane Delcroix <stephane@delcroix.org>
8 * Copyright 2011 Xamarin Inc.
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.CompilerServices;
32 using System.Threading;
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Runtime.Serialization;
37 using System.Globalization;
40 using Microsoft.Win32;
44 partial class TimeZoneInfo
46 TimeSpan baseUtcOffset;
47 public TimeSpan BaseUtcOffset {
48 get { return baseUtcOffset; }
51 string daylightDisplayName;
52 public string DaylightName {
54 return supportsDaylightSavingTime
61 public string DisplayName {
62 get { return displayName; }
70 static TimeZoneInfo local;
71 public static TimeZoneInfo Local {
77 throw new TimeZoneNotFoundException ();
79 if (Interlocked.CompareExchange (ref local, l, null) != null)
88 TimeZone transitions are stored when there is a change on the base offset.
90 private List<KeyValuePair<DateTime, TimeType>> transitions;
92 #if !MOBILE || MOBILE_STATIC
93 static TimeZoneInfo CreateLocal ()
96 if (IsWindows && LocalZoneKey != null) {
97 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
99 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
100 name = TrimSpecial (name);
102 return TimeZoneInfo.FindSystemTimeZoneById (name);
106 var tz = Environment.GetEnvironmentVariable ("TZ");
108 if (tz == String.Empty)
111 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
118 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
121 return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
128 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
131 string filepath = Path.Combine (TimeZoneDirectory, id);
132 return FindSystemTimeZoneByFileName (id, filepath);
134 throw new NotImplementedException ();
138 static void GetSystemTimeZones (List<TimeZoneInfo> systemTimeZones)
141 if (TimeZoneKey != null) {
142 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
144 systemTimeZones.Add (FindSystemTimeZoneById (id));
153 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
154 foreach (string continent in continents) {
156 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
158 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
159 systemTimeZones.Add (FindSystemTimeZoneById (id));
160 } catch (ArgumentNullException) {
161 } catch (TimeZoneNotFoundException) {
162 } catch (InvalidTimeZoneException) {
163 } catch (Exception) {
170 throw new NotImplementedException ("This method is not implemented for this platform");
175 string standardDisplayName;
176 public string StandardName {
177 get { return standardDisplayName; }
180 bool supportsDaylightSavingTime;
181 public bool SupportsDaylightSavingTime {
182 get { return supportsDaylightSavingTime; }
185 static TimeZoneInfo utc;
186 public static TimeZoneInfo Utc {
189 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
194 static string timeZoneDirectory;
195 static string TimeZoneDirectory {
197 if (timeZoneDirectory == null)
198 timeZoneDirectory = "/usr/share/zoneinfo";
199 return timeZoneDirectory;
203 timeZoneDirectory = value;
207 private AdjustmentRule [] adjustmentRules;
209 #if !NET_2_1 || MOBILE_STATIC
211 /// Determine whether windows of not (taken Stephane Delcroix's code)
213 private static bool IsWindows
216 int platform = (int) Environment.OSVersion.Platform;
217 return ((platform != 4) && (platform != 6) && (platform != 128));
222 /// Needed to trim misc garbage in MS registry keys
224 private static string TrimSpecial (string str)
229 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
230 var Iend = str.Length - 1;
231 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
233 return str.Substring (Istart, Iend-Istart+1);
237 static RegistryKey timeZoneKey;
238 static RegistryKey TimeZoneKey {
240 if (timeZoneKey != null)
245 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
246 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
251 static RegistryKey localZoneKey;
252 static RegistryKey LocalZoneKey {
254 if (localZoneKey != null)
260 return localZoneKey = Registry.LocalMachine.OpenSubKey (
261 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
267 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
269 var resultTicks = date.Ticks + ticks;
270 if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
271 result = default (DateTime);
275 result = new DateTime (resultTicks, kind);
279 public static void ClearCachedData ()
283 systemTimeZones = null;
286 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
288 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
291 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
293 if (sourceTimeZone == null)
294 throw new ArgumentNullException ("sourceTimeZone");
296 if (destinationTimeZone == null)
297 throw new ArgumentNullException ("destinationTimeZone");
299 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
300 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
302 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
303 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
305 if (sourceTimeZone.IsInvalidTime (dateTime))
306 throw new ArgumentException ("dateTime parameter is an invalid time");
308 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
311 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
313 if (destinationTimeZone != TimeZoneInfo.Utc) {
314 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
315 if (dateTime.Kind == DateTimeKind.Unspecified)
316 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
322 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
324 if (destinationTimeZone == null)
325 throw new ArgumentNullException("destinationTimeZone");
327 var utcDateTime = dateTimeOffset.UtcDateTime;
330 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
332 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
335 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
337 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
340 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
342 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
345 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
347 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
350 private DateTime ConvertTimeFromUtc (DateTime dateTime)
352 if (dateTime.Kind == DateTimeKind.Local)
353 throw new ArgumentException ("Kind property of dateTime is Local");
355 if (this == TimeZoneInfo.Utc)
356 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
358 var utcOffset = GetUtcOffset (dateTime);
360 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
363 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
364 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
369 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
371 if (destinationTimeZone == null)
372 throw new ArgumentNullException ("destinationTimeZone");
374 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
377 public static DateTime ConvertTimeToUtc (DateTime dateTime)
379 if (dateTime.Kind == DateTimeKind.Utc)
382 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
385 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
387 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
390 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
392 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
395 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
397 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
398 if (sourceTimeZone == null)
399 throw new ArgumentNullException ("sourceTimeZone");
401 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
402 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
404 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
405 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
407 if (sourceTimeZone.IsInvalidTime (dateTime))
408 throw new ArgumentException ("dateTime parameter is an invalid time");
411 if (dateTime.Kind == DateTimeKind.Utc)
415 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
417 DateTime utcDateTime;
418 if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
419 return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
424 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
426 bool isDaylightSavings;
427 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
430 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
432 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
435 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
437 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
440 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
442 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
445 public override bool Equals (object obj)
447 return Equals (obj as TimeZoneInfo);
450 public bool Equals (TimeZoneInfo other)
455 return other.Id == this.Id && HasSameRules (other);
458 public static TimeZoneInfo FindSystemTimeZoneById (string id)
460 //FIXME: this method should check for cached values in systemTimeZones
462 throw new ArgumentNullException ("id");
464 if (TimeZoneKey != null)
466 if (id == "Coordinated Universal Time")
467 id = "UTC"; //windows xp exception for "StandardName" property
468 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
470 throw new TimeZoneNotFoundException ();
471 return FromRegistryKey(id, key);
474 // Local requires special logic that already exists in the Local property (bug #326)
478 return FindSystemTimeZoneByIdCore (id);
482 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
484 if (!File.Exists (filepath))
485 throw new TimeZoneNotFoundException ();
487 using (FileStream stream = File.OpenRead (filepath)) {
488 return BuildFromStream (id, stream);
494 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
496 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
499 throw new InvalidTimeZoneException ();
501 int bias = BitConverter.ToInt32 (reg_tzi, 0);
502 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
504 string display_name = (string) key.GetValue ("Display");
505 string standard_name = (string) key.GetValue ("Std");
506 string daylight_name = (string) key.GetValue ("Dlt");
508 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
510 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
511 if (dst_key != null) {
512 int first_year = (int) dst_key.GetValue ("FirstEntry");
513 int last_year = (int) dst_key.GetValue ("LastEntry");
516 for (year=first_year; year<=last_year; year++) {
517 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
518 if (dst_tzi != null) {
519 int start_year = year == first_year ? 1 : year;
520 int end_year = year == last_year ? 9999 : year;
521 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
526 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
528 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
531 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
533 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
534 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
536 int standard_year = BitConverter.ToInt16 (buffer, 12);
537 int standard_month = BitConverter.ToInt16 (buffer, 14);
538 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
539 int standard_day = BitConverter.ToInt16 (buffer, 18);
540 int standard_hour = BitConverter.ToInt16 (buffer, 20);
541 int standard_minute = BitConverter.ToInt16 (buffer, 22);
542 int standard_second = BitConverter.ToInt16 (buffer, 24);
543 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
545 int daylight_year = BitConverter.ToInt16 (buffer, 28);
546 int daylight_month = BitConverter.ToInt16 (buffer, 30);
547 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
548 int daylight_day = BitConverter.ToInt16 (buffer, 34);
549 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
550 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
551 int daylight_second = BitConverter.ToInt16 (buffer, 40);
552 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
554 if (standard_month == 0 || daylight_month == 0)
558 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
559 TransitionTime start_transition_time;
561 if (daylight_year == 0) {
562 start_date = new DateTime (start_year, 1, 1);
563 start_transition_time = TransitionTime.CreateFloatingDateRule (
564 start_timeofday, daylight_month, daylight_day,
565 (DayOfWeek) daylight_dayofweek);
568 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
569 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
570 start_transition_time = TransitionTime.CreateFixedDateRule (
571 start_timeofday, daylight_month, daylight_day);
575 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
576 TransitionTime end_transition_time;
578 if (standard_year == 0) {
579 end_date = new DateTime (end_year, 12, 31);
580 end_transition_time = TransitionTime.CreateFloatingDateRule (
581 end_timeofday, standard_month, standard_day,
582 (DayOfWeek) standard_dayofweek);
585 end_date = new DateTime (standard_year, standard_month, standard_day,
586 standard_hour, standard_minute, standard_second, standard_millisecond);
587 end_transition_time = TransitionTime.CreateFixedDateRule (
588 end_timeofday, standard_month, standard_day);
591 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
593 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
594 start_date, end_date, daylight_delta,
595 start_transition_time, end_transition_time));
599 public AdjustmentRule [] GetAdjustmentRules ()
601 if (!supportsDaylightSavingTime || adjustmentRules == null)
602 return new AdjustmentRule [0];
604 return (AdjustmentRule []) adjustmentRules.Clone ();
607 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
609 if (!IsAmbiguousTime (dateTime))
610 throw new ArgumentException ("dateTime is not an ambiguous time");
612 AdjustmentRule rule = GetApplicableRule (dateTime);
614 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
616 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
619 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
621 if (!IsAmbiguousTime (dateTimeOffset))
622 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
624 throw new NotImplementedException ();
627 public override int GetHashCode ()
629 int hash_code = Id.GetHashCode ();
630 foreach (AdjustmentRule rule in GetAdjustmentRules ())
631 hash_code ^= rule.GetHashCode ();
635 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
638 throw new ArgumentNullException ("info");
639 info.AddValue ("Id", id);
640 info.AddValue ("DisplayName", displayName);
641 info.AddValue ("StandardName", standardDisplayName);
642 info.AddValue ("DaylightName", daylightDisplayName);
643 info.AddValue ("BaseUtcOffset", baseUtcOffset);
644 info.AddValue ("AdjustmentRules", adjustmentRules);
645 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
648 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
650 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
652 if (systemTimeZones == null) {
653 var tz = new List<TimeZoneInfo> ();
654 GetSystemTimeZones (tz);
655 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
658 return systemTimeZones;
661 public TimeSpan GetUtcOffset (DateTime dateTime)
664 return GetUtcOffset (dateTime, out isDST);
667 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
669 throw new NotImplementedException ();
672 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
676 TimeZoneInfo tz = this;
677 if (dateTime.Kind == DateTimeKind.Utc)
678 tz = TimeZoneInfo.Utc;
680 if (dateTime.Kind == DateTimeKind.Local)
681 tz = TimeZoneInfo.Local;
684 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
691 DateTime utcDateTime;
692 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
693 return BaseUtcOffset;
695 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
698 // This is an helper method used by the method above, do not use this on its own.
699 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
701 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
702 throw new Exception ();
706 if (tz == TimeZoneInfo.Utc)
707 return TimeSpan.Zero;
710 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
713 if (dateTime.Kind == DateTimeKind.Utc) {
714 var utcRule = tz.GetApplicableRule (dateTime);
715 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
717 return tz.BaseUtcOffset + utcRule.DaylightDelta;
720 return tz.BaseUtcOffset;
723 DateTime stdUtcDateTime;
724 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
725 return tz.BaseUtcOffset;
727 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
729 DateTime dstUtcDateTime = DateTime.MinValue;
730 if (tzRule != null) {
731 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
732 return tz.BaseUtcOffset;
735 if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
737 return tz.BaseUtcOffset + tzRule.DaylightDelta;
740 return tz.BaseUtcOffset;
743 public bool HasSameRules (TimeZoneInfo other)
746 throw new ArgumentNullException ("other");
748 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
751 if (this.adjustmentRules == null)
754 if (this.BaseUtcOffset != other.BaseUtcOffset)
757 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
760 for (int i = 0; i < adjustmentRules.Length; i++) {
761 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
768 public bool IsAmbiguousTime (DateTime dateTime)
770 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
771 throw new ArgumentException ("Kind is Local and time is Invalid");
773 if (this == TimeZoneInfo.Utc)
776 if (dateTime.Kind == DateTimeKind.Utc)
777 dateTime = ConvertTimeFromUtc (dateTime);
779 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
780 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
782 AdjustmentRule rule = GetApplicableRule (dateTime);
784 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
785 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
792 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
794 throw new NotImplementedException ();
797 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
799 // Check whether we're in the dateTime year's DST period
800 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
803 // We might be in the dateTime previous year's DST period
804 return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
807 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
809 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
810 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
811 if (dateTime.Kind == DateTimeKind.Utc) {
812 DST_start -= BaseUtcOffset;
813 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
816 return (dateTime >= DST_start && dateTime < DST_end);
819 public bool IsDaylightSavingTime (DateTime dateTime)
821 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
822 throw new ArgumentException ("dateTime is invalid and Kind is Local");
824 if (this == TimeZoneInfo.Utc)
827 if (!SupportsDaylightSavingTime)
831 GetUtcOffset (dateTime, out isDst);
836 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
838 return IsDaylightSavingTime (dateTime);
841 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
843 throw new NotImplementedException ();
846 internal DaylightTime GetDaylightChanges (int year)
848 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
849 TimeSpan delta = new TimeSpan ();
851 if (transitions != null) {
852 end = DateTime.MaxValue;
853 for (var i = transitions.Count - 1; i >= 0; i--) {
854 var pair = transitions [i];
855 DateTime ttime = pair.Key;
856 TimeType ttype = pair.Value;
858 if (ttime.Year > year)
860 if (ttime.Year < year)
864 // DaylightTime.Delta is relative to the current BaseUtcOffset.
865 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
872 // DaylightTime.Start is relative to the Standard time.
873 if (start != DateTime.MinValue)
874 start += BaseUtcOffset;
876 // DaylightTime.End is relative to the DST time.
877 if (end != DateTime.MinValue)
878 end += BaseUtcOffset + delta;
880 AdjustmentRule first = null, last = null;
882 foreach (var rule in GetAdjustmentRules ()) {
883 if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
885 if (rule.DateStart.Year == year)
887 if (rule.DateEnd.Year == year)
891 if (first == null || last == null)
892 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
894 start = TransitionPoint (first.DaylightTransitionStart, year);
895 end = TransitionPoint (last.DaylightTransitionEnd, year);
896 delta = first.DaylightDelta;
899 if (start == DateTime.MinValue || end == DateTime.MinValue)
900 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
902 return new DaylightTime (start, end, delta);
905 public bool IsInvalidTime (DateTime dateTime)
907 if (dateTime.Kind == DateTimeKind.Utc)
909 if (dateTime.Kind == DateTimeKind.Local && this != Local)
912 AdjustmentRule rule = GetApplicableRule (dateTime);
914 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
915 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
922 void IDeserializationCallback.OnDeserialization (object sender)
925 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
926 } catch (ArgumentException ex) {
927 throw new SerializationException ("invalid serialization data", ex);
931 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
934 throw new ArgumentNullException ("id");
936 if (id == String.Empty)
937 throw new ArgumentException ("id parameter is an empty string");
939 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
940 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
942 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
943 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
947 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
950 if (adjustmentRules != null && adjustmentRules.Length != 0) {
951 AdjustmentRule prev = null;
952 foreach (AdjustmentRule current in adjustmentRules) {
954 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
956 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
957 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
958 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;");
960 if (prev != null && prev.DateStart > current.DateStart)
961 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
963 if (prev != null && prev.DateEnd > current.DateStart)
964 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
966 if (prev != null && prev.DateEnd == current.DateStart)
967 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
974 public override string ToString ()
979 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
982 throw new ArgumentNullException ("info");
983 id = (string) info.GetValue ("Id", typeof (string));
984 displayName = (string) info.GetValue ("DisplayName", typeof (string));
985 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
986 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
987 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
988 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
989 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
992 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
995 throw new ArgumentNullException ("id");
997 if (id == String.Empty)
998 throw new ArgumentException ("id parameter is an empty string");
1000 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1001 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1003 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1004 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1008 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1011 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1013 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1014 AdjustmentRule prev = null;
1015 foreach (AdjustmentRule current in adjustmentRules) {
1016 if (current == null)
1017 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1019 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1020 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1021 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;");
1023 if (prev != null && prev.DateStart > current.DateStart)
1024 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1026 if (prev != null && prev.DateEnd > current.DateStart)
1027 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1029 if (prev != null && prev.DateEnd == current.DateStart)
1030 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1035 supportsDaylightSavingTime = false;
1039 this.baseUtcOffset = baseUtcOffset;
1040 this.displayName = displayName ?? id;
1041 this.standardDisplayName = standardDisplayName ?? id;
1042 this.daylightDisplayName = daylightDisplayName;
1043 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1044 this.adjustmentRules = adjustmentRules;
1047 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1049 //Applicable rules are in standard time
1050 DateTime date = dateTime;
1052 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1053 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1055 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1056 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1060 // get the date component of the datetime
1063 if (adjustmentRules != null) {
1064 foreach (AdjustmentRule rule in adjustmentRules) {
1065 if (rule.DateStart > date)
1067 if (rule.DateEnd < date)
1075 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1077 offset = BaseUtcOffset;
1080 if (transitions == null)
1083 //Transitions are in UTC
1084 DateTime date = dateTime;
1086 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1087 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1091 if (dateTime.Kind != DateTimeKind.Utc) {
1092 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1096 for (var i = transitions.Count - 1; i >= 0; i--) {
1097 var pair = transitions [i];
1098 DateTime ttime = pair.Key;
1099 TimeType ttype = pair.Value;
1104 offset = new TimeSpan (0, 0, ttype.Offset);
1105 isDst = ttype.IsDst;
1113 private static DateTime TransitionPoint (TransitionTime transition, int year)
1115 if (transition.IsFixedDateRule)
1116 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1118 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1119 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1120 if (day > DateTime.DaysInMonth (year, transition.Month))
1124 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1127 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1129 AdjustmentRule prev = null;
1130 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1131 if (prev != null && prev.DateEnd > current.DateStart) {
1132 adjustmentRules.Remove (current);
1136 return adjustmentRules;
1139 #if LIBC || MONOTOUCH
1140 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1142 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1144 byte [] buffer = new byte [BUFFER_SIZE];
1145 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1147 if (!ValidTZFile (buffer, length))
1148 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1151 return ParseTZBuffer (id, buffer, length);
1152 } catch (Exception e) {
1153 throw new InvalidTimeZoneException (e.Message);
1157 private static bool ValidTZFile (byte [] buffer, int length)
1159 StringBuilder magic = new StringBuilder ();
1161 for (int i = 0; i < 4; i++)
1162 magic.Append ((char)buffer [i]);
1164 if (magic.ToString () != "TZif")
1167 if (length >= BUFFER_SIZE)
1173 static int SwapInt32 (int i)
1175 return (((i >> 24) & 0xff)
1176 | ((i >> 8) & 0xff00)
1177 | ((i << 8) & 0xff0000)
1181 static int ReadBigEndianInt32 (byte [] buffer, int start)
1183 int i = BitConverter.ToInt32 (buffer, start);
1184 if (!BitConverter.IsLittleEndian)
1187 return SwapInt32 (i);
1190 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1192 //Reading the header. 4 bytes for magic, 16 are reserved
1193 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1194 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1195 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1196 int timecnt = ReadBigEndianInt32 (buffer, 32);
1197 int typecnt = ReadBigEndianInt32 (buffer, 36);
1198 int charcnt = ReadBigEndianInt32 (buffer, 40);
1200 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1201 throw new InvalidTimeZoneException ();
1203 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1204 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1205 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1207 if (time_types.Count == 0)
1208 throw new InvalidTimeZoneException ();
1210 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1211 throw new InvalidTimeZoneException ();
1213 TimeSpan baseUtcOffset = new TimeSpan (0);
1214 TimeSpan dstDelta = new TimeSpan (0);
1215 string standardDisplayName = null;
1216 string daylightDisplayName = null;
1217 bool dst_observed = false;
1218 DateTime dst_start = DateTime.MinValue;
1219 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1220 bool storeTransition = false;
1222 for (int i = 0; i < transitions.Count; i++) {
1223 var pair = transitions [i];
1224 DateTime ttime = pair.Key;
1225 TimeType ttype = pair.Value;
1227 if (standardDisplayName != ttype.Name)
1228 standardDisplayName = ttype.Name;
1229 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1230 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1231 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1232 storeTransition = true;
1233 adjustmentRules = new List<AdjustmentRule> ();
1234 dst_observed = false;
1237 //FIXME: check additional fields for this:
1238 //most of the transitions are expressed in GMT
1239 dst_start += baseUtcOffset;
1240 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1242 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1243 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1244 dst_end -= new TimeSpan (24, 0, 0);
1247 * AdjustmentRule specifies a DST period that starts and ends within a year.
1248 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1249 * Thus we fallback to the transitions.
1251 if (dst_start.AddYears (1) < dst_end)
1252 storeTransition = true;
1254 DateTime dateStart, dateEnd;
1255 if (dst_start.Month < 7)
1256 dateStart = new DateTime (dst_start.Year, 1, 1);
1258 dateStart = new DateTime (dst_start.Year, 7, 1);
1260 if (dst_end.Month >= 7)
1261 dateEnd = new DateTime (dst_end.Year, 12, 31);
1263 dateEnd = new DateTime (dst_end.Year, 6, 30);
1266 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1267 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1268 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1269 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1271 dst_observed = false;
1273 if (daylightDisplayName != ttype.Name)
1274 daylightDisplayName = ttype.Name;
1275 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1276 // Round to nearest minute, since it's not possible to create an adjustment rule
1277 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1278 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1279 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1280 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1281 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1285 dst_observed = true;
1290 if (adjustmentRules.Count == 0 && !storeTransition) {
1291 TimeType t = (TimeType)time_types [0];
1292 if (standardDisplayName == null) {
1293 standardDisplayName = t.Name;
1294 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1296 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1298 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1301 if (storeTransition && transitions.Count > 0) {
1302 tz.transitions = transitions;
1303 tz.supportsDaylightSavingTime = true;
1309 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1311 var abbrevs = new Dictionary<int, string> ();
1312 int abbrev_index = 0;
1313 var sb = new StringBuilder ();
1314 for (int i = 0; i < count; i++) {
1315 char c = (char) buffer [index + i];
1319 abbrevs.Add (abbrev_index, sb.ToString ());
1320 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1321 for (int j = 1; j < sb.Length; j++)
1322 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1323 abbrev_index = i + 1;
1324 sb = new StringBuilder ();
1330 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1332 var types = new Dictionary<int, TimeType> (count);
1333 for (int i = 0; i < count; i++) {
1334 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1335 byte is_dst = buffer [index + 6 * i + 4];
1336 byte abbrev = buffer [index + 6 * i + 5];
1337 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1342 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1344 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1345 for (int i = 0; i < count; i++) {
1346 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1347 DateTime ttime = DateTimeFromUnixTime (unixtime);
1348 byte ttype = buffer [index + 4 * count + i];
1349 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1354 static DateTime DateTimeFromUnixTime (long unix_time)
1356 DateTime date_time = new DateTime (1970, 1, 1);
1357 return date_time.AddSeconds (unix_time);
1360 #region reference sources
1361 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1362 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1365 return Local.GetUtcOffset (dateTime, out dst);
1368 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1371 return GetUtcOffset (dateTime, out dst);
1374 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1376 isDaylightSavings = false;
1377 isAmbiguousLocalDst = false;
1378 TimeSpan baseOffset = zone.BaseUtcOffset;
1380 if (zone.IsAmbiguousTime (time)) {
1381 isAmbiguousLocalDst = true;
1385 return zone.GetUtcOffset (time, out isDaylightSavings);
1391 public readonly int Offset;
1392 public readonly bool IsDst;
1395 public TimeType (int offset, bool is_dst, string abbrev)
1397 this.Offset = offset;
1398 this.IsDst = is_dst;
1402 public override string ToString ()
1404 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;