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;
44 using Microsoft.Win32;
48 partial class TimeZoneInfo
50 TimeSpan baseUtcOffset;
51 public TimeSpan BaseUtcOffset {
52 get { return baseUtcOffset; }
55 string daylightDisplayName;
56 public string DaylightName {
58 return supportsDaylightSavingTime
65 public string DisplayName {
66 get { return displayName; }
74 static TimeZoneInfo local;
75 public static TimeZoneInfo Local {
81 throw new TimeZoneNotFoundException ();
83 if (Interlocked.CompareExchange (ref local, l, null) != null)
92 TimeZone transitions are stored when there is a change on the base offset.
94 private List<KeyValuePair<DateTime, TimeType>> transitions;
96 static TimeZoneInfo CreateLocal ()
99 return AndroidTimeZones.Local;
101 using (Stream stream = GetMonoTouchData (null)) {
102 return BuildFromStream ("Local", stream);
106 if (IsWindows && LocalZoneKey != null) {
107 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
109 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
110 name = TrimSpecial (name);
112 return TimeZoneInfo.FindSystemTimeZoneById (name);
116 var tz = Environment.GetEnvironmentVariable ("TZ");
118 if (tz == String.Empty)
121 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
128 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
131 return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
139 string standardDisplayName;
140 public string StandardName {
141 get { return standardDisplayName; }
144 bool supportsDaylightSavingTime;
145 public bool SupportsDaylightSavingTime {
146 get { return supportsDaylightSavingTime; }
149 static TimeZoneInfo utc;
150 public static TimeZoneInfo Utc {
153 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
158 static string timeZoneDirectory;
159 static string TimeZoneDirectory {
161 if (timeZoneDirectory == null)
162 timeZoneDirectory = "/usr/share/zoneinfo";
163 return timeZoneDirectory;
167 timeZoneDirectory = value;
171 private AdjustmentRule [] adjustmentRules;
175 /// Determine whether windows of not (taken Stephane Delcroix's code)
177 private static bool IsWindows
180 int platform = (int) Environment.OSVersion.Platform;
181 return ((platform != 4) && (platform != 6) && (platform != 128));
186 /// Needed to trim misc garbage in MS registry keys
188 private static string TrimSpecial (string str)
193 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
194 var Iend = str.Length - 1;
195 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
197 return str.Substring (Istart, Iend-Istart+1);
200 static RegistryKey timeZoneKey;
201 static RegistryKey TimeZoneKey {
203 if (timeZoneKey != null)
208 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
209 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
214 static RegistryKey localZoneKey;
215 static RegistryKey LocalZoneKey {
217 if (localZoneKey != null)
223 return localZoneKey = Registry.LocalMachine.OpenSubKey (
224 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
229 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
231 var resultTicks = date.Ticks + ticks;
232 if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
233 result = default (DateTime);
237 result = new DateTime (resultTicks, kind);
241 public static void ClearCachedData ()
245 systemTimeZones = null;
248 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
250 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
253 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
255 if (sourceTimeZone == null)
256 throw new ArgumentNullException ("sourceTimeZone");
258 if (destinationTimeZone == null)
259 throw new ArgumentNullException ("destinationTimeZone");
261 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
262 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
264 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
265 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
267 if (sourceTimeZone.IsInvalidTime (dateTime))
268 throw new ArgumentException ("dateTime parameter is an invalid time");
270 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
273 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
275 if (destinationTimeZone != TimeZoneInfo.Utc) {
276 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
277 if (dateTime.Kind == DateTimeKind.Unspecified)
278 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
284 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
286 if (destinationTimeZone == null)
287 throw new ArgumentNullException("destinationTimeZone");
289 var utcDateTime = dateTimeOffset.UtcDateTime;
292 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
294 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
297 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
299 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
302 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
304 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
307 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
309 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
312 private DateTime ConvertTimeFromUtc (DateTime dateTime)
314 if (dateTime.Kind == DateTimeKind.Local)
315 throw new ArgumentException ("Kind property of dateTime is Local");
317 if (this == TimeZoneInfo.Utc)
318 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
320 var utcOffset = GetUtcOffset (dateTime);
322 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
325 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
326 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
331 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
333 if (destinationTimeZone == null)
334 throw new ArgumentNullException ("destinationTimeZone");
336 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
339 public static DateTime ConvertTimeToUtc (DateTime dateTime)
341 if (dateTime.Kind == DateTimeKind.Utc)
344 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
347 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
349 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
352 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
354 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
357 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
359 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
360 if (sourceTimeZone == null)
361 throw new ArgumentNullException ("sourceTimeZone");
363 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
364 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
366 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
367 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
369 if (sourceTimeZone.IsInvalidTime (dateTime))
370 throw new ArgumentException ("dateTime parameter is an invalid time");
373 if (dateTime.Kind == DateTimeKind.Utc)
377 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
379 DateTime utcDateTime;
380 if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
381 return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
386 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
388 bool isDaylightSavings;
389 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
392 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
394 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
397 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
399 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
402 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
404 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
407 public override bool Equals (object obj)
409 return Equals (obj as TimeZoneInfo);
412 public bool Equals (TimeZoneInfo other)
417 return other.Id == this.Id && HasSameRules (other);
420 public static TimeZoneInfo FindSystemTimeZoneById (string id)
422 //FIXME: this method should check for cached values in systemTimeZones
424 throw new ArgumentNullException ("id");
426 if (TimeZoneKey != null)
428 if (id == "Coordinated Universal Time")
429 id = "UTC"; //windows xp exception for "StandardName" property
430 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
432 throw new TimeZoneNotFoundException ();
433 return FromRegistryKey(id, key);
436 // Local requires special logic that already exists in the Local property (bug #326)
440 using (Stream stream = GetMonoTouchData (id)) {
441 return BuildFromStream (id, stream);
444 var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
445 if (timeZoneInfo == null)
446 throw new TimeZoneNotFoundException ();
449 string filepath = Path.Combine (TimeZoneDirectory, id);
450 return FindSystemTimeZoneByFileName (id, filepath);
452 throw new NotImplementedException ();
457 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
459 if (!File.Exists (filepath))
460 throw new TimeZoneNotFoundException ();
462 using (FileStream stream = File.OpenRead (filepath)) {
463 return BuildFromStream (id, stream);
467 #if LIBC || MONOTOUCH
468 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
470 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
472 byte [] buffer = new byte [BUFFER_SIZE];
473 int length = stream.Read (buffer, 0, BUFFER_SIZE);
475 if (!ValidTZFile (buffer, length))
476 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
479 return ParseTZBuffer (id, buffer, length);
480 } catch (Exception e) {
481 throw new InvalidTimeZoneException (e.Message);
487 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
489 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
492 throw new InvalidTimeZoneException ();
494 int bias = BitConverter.ToInt32 (reg_tzi, 0);
495 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
497 string display_name = (string) key.GetValue ("Display");
498 string standard_name = (string) key.GetValue ("Std");
499 string daylight_name = (string) key.GetValue ("Dlt");
501 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
503 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
504 if (dst_key != null) {
505 int first_year = (int) dst_key.GetValue ("FirstEntry");
506 int last_year = (int) dst_key.GetValue ("LastEntry");
509 for (year=first_year; year<=last_year; year++) {
510 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
511 if (dst_tzi != null) {
512 int start_year = year == first_year ? 1 : year;
513 int end_year = year == last_year ? 9999 : year;
514 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
519 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
521 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
524 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
526 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
527 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
529 int standard_year = BitConverter.ToInt16 (buffer, 12);
530 int standard_month = BitConverter.ToInt16 (buffer, 14);
531 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
532 int standard_day = BitConverter.ToInt16 (buffer, 18);
533 int standard_hour = BitConverter.ToInt16 (buffer, 20);
534 int standard_minute = BitConverter.ToInt16 (buffer, 22);
535 int standard_second = BitConverter.ToInt16 (buffer, 24);
536 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
538 int daylight_year = BitConverter.ToInt16 (buffer, 28);
539 int daylight_month = BitConverter.ToInt16 (buffer, 30);
540 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
541 int daylight_day = BitConverter.ToInt16 (buffer, 34);
542 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
543 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
544 int daylight_second = BitConverter.ToInt16 (buffer, 40);
545 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
547 if (standard_month == 0 || daylight_month == 0)
551 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
552 TransitionTime start_transition_time;
554 if (daylight_year == 0) {
555 start_date = new DateTime (start_year, 1, 1);
556 start_transition_time = TransitionTime.CreateFloatingDateRule (
557 start_timeofday, daylight_month, daylight_day,
558 (DayOfWeek) daylight_dayofweek);
561 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
562 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
563 start_transition_time = TransitionTime.CreateFixedDateRule (
564 start_timeofday, daylight_month, daylight_day);
568 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
569 TransitionTime end_transition_time;
571 if (standard_year == 0) {
572 end_date = new DateTime (end_year, 12, 31);
573 end_transition_time = TransitionTime.CreateFloatingDateRule (
574 end_timeofday, standard_month, standard_day,
575 (DayOfWeek) standard_dayofweek);
578 end_date = new DateTime (standard_year, standard_month, standard_day,
579 standard_hour, standard_minute, standard_second, standard_millisecond);
580 end_transition_time = TransitionTime.CreateFixedDateRule (
581 end_timeofday, standard_month, standard_day);
584 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
586 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
587 start_date, end_date, daylight_delta,
588 start_transition_time, end_transition_time));
592 public AdjustmentRule [] GetAdjustmentRules ()
594 if (!supportsDaylightSavingTime)
595 return new AdjustmentRule [0];
597 return (AdjustmentRule []) adjustmentRules.Clone ();
600 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
602 if (!IsAmbiguousTime (dateTime))
603 throw new ArgumentException ("dateTime is not an ambiguous time");
605 AdjustmentRule rule = GetApplicableRule (dateTime);
607 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
609 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
612 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
614 if (!IsAmbiguousTime (dateTimeOffset))
615 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
617 throw new NotImplementedException ();
620 public override int GetHashCode ()
622 int hash_code = Id.GetHashCode ();
623 foreach (AdjustmentRule rule in GetAdjustmentRules ())
624 hash_code ^= rule.GetHashCode ();
628 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
631 throw new ArgumentNullException ("info");
632 info.AddValue ("Id", id);
633 info.AddValue ("DisplayName", displayName);
634 info.AddValue ("StandardName", standardDisplayName);
635 info.AddValue ("DaylightName", daylightDisplayName);
636 info.AddValue ("BaseUtcOffset", baseUtcOffset);
637 info.AddValue ("AdjustmentRules", adjustmentRules);
638 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
641 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
642 private static List<TimeZoneInfo> systemTimeZones;
643 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
645 if (systemTimeZones == null) {
646 systemTimeZones = new List<TimeZoneInfo> ();
648 if (TimeZoneKey != null) {
649 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
651 systemTimeZones.Add (FindSystemTimeZoneById (id));
655 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
659 foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
660 var tz = AndroidTimeZones.GetTimeZone (id, id);
662 systemTimeZones.Add (tz);
665 if (systemTimeZones.Count == 0) {
666 foreach (string name in GetMonoTouchNames ()) {
667 using (Stream stream = GetMonoTouchData (name, false)) {
670 systemTimeZones.Add (BuildFromStream (name, stream));
675 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
676 foreach (string continent in continents) {
678 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
680 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
681 systemTimeZones.Add (FindSystemTimeZoneById (id));
682 } catch (ArgumentNullException) {
683 } catch (TimeZoneNotFoundException) {
684 } catch (InvalidTimeZoneException) {
685 } catch (Exception) {
692 throw new NotImplementedException ("This method is not implemented for this platform");
695 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
698 public TimeSpan GetUtcOffset (DateTime dateTime)
701 return GetUtcOffset (dateTime, out isDST);
704 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
706 throw new NotImplementedException ();
709 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
713 TimeZoneInfo tz = this;
714 if (dateTime.Kind == DateTimeKind.Utc)
715 tz = TimeZoneInfo.Utc;
717 if (dateTime.Kind == DateTimeKind.Local)
718 tz = TimeZoneInfo.Local;
721 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
728 DateTime utcDateTime;
729 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
730 return BaseUtcOffset;
732 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
735 // This is an helper method used by the method above, do not use this on its own.
736 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
738 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
739 throw new Exception ();
743 if (tz == TimeZoneInfo.Utc)
744 return TimeSpan.Zero;
747 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
750 if (dateTime.Kind == DateTimeKind.Utc) {
751 var utcRule = tz.GetApplicableRule (dateTime);
752 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
754 return tz.BaseUtcOffset + utcRule.DaylightDelta;
757 return tz.BaseUtcOffset;
760 DateTime stdUtcDateTime;
761 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
762 return tz.BaseUtcOffset;
764 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
766 DateTime dstUtcDateTime = DateTime.MinValue;
767 if (tzRule != null) {
768 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
769 return tz.BaseUtcOffset;
772 if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
774 return tz.BaseUtcOffset + tzRule.DaylightDelta;
777 return tz.BaseUtcOffset;
780 public bool HasSameRules (TimeZoneInfo other)
783 throw new ArgumentNullException ("other");
785 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
788 if (this.adjustmentRules == null)
791 if (this.BaseUtcOffset != other.BaseUtcOffset)
794 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
797 for (int i = 0; i < adjustmentRules.Length; i++) {
798 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
805 public bool IsAmbiguousTime (DateTime dateTime)
807 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
808 throw new ArgumentException ("Kind is Local and time is Invalid");
810 if (this == TimeZoneInfo.Utc)
813 if (dateTime.Kind == DateTimeKind.Utc)
814 dateTime = ConvertTimeFromUtc (dateTime);
816 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
817 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
819 AdjustmentRule rule = GetApplicableRule (dateTime);
821 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
822 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
829 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
831 throw new NotImplementedException ();
834 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
836 // Check whether we're in the dateTime year's DST period
837 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
840 // We might be in the dateTime previous year's DST period
841 return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
844 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
846 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
847 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
848 if (dateTime.Kind == DateTimeKind.Utc) {
849 DST_start -= BaseUtcOffset;
850 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
853 return (dateTime >= DST_start && dateTime < DST_end);
856 public bool IsDaylightSavingTime (DateTime dateTime)
858 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
859 throw new ArgumentException ("dateTime is invalid and Kind is Local");
861 if (this == TimeZoneInfo.Utc)
864 if (!SupportsDaylightSavingTime)
868 GetUtcOffset (dateTime, out isDst);
873 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
875 return IsDaylightSavingTime (dateTime);
878 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
880 throw new NotImplementedException ();
883 internal DaylightTime GetDaylightChanges (int year)
885 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
886 TimeSpan delta = new TimeSpan ();
888 if (transitions != null) {
889 end = DateTime.MaxValue;
890 for (var i = transitions.Count - 1; i >= 0; i--) {
891 var pair = transitions [i];
892 DateTime ttime = pair.Key;
893 TimeType ttype = pair.Value;
896 // DaylightTime.Delta is relative to the current BaseUtcOffset.
897 var d = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
898 // Handle DST gradients
899 if (start != DateTime.MinValue && delta != d)
905 if (ttime.Year <= year)
908 if (ttime.Year < year)
912 start = DateTime.MinValue;
916 // DaylightTime.Start is relative to the Standard time.
917 if (start != DateTime.MinValue)
918 start += BaseUtcOffset;
920 // DaylightTime.End is relative to the DST time.
921 if (end != DateTime.MaxValue)
922 end += BaseUtcOffset + delta;
924 AdjustmentRule rule = null;
925 foreach (var r in GetAdjustmentRules ()) {
926 if (r.DateEnd.Year < year)
928 if (r.DateStart.Year > year)
933 start = TransitionPoint (rule.DaylightTransitionStart, year);
934 end = TransitionPoint (rule.DaylightTransitionEnd, year);
935 delta = rule.DaylightDelta;
939 if (start == DateTime.MinValue || end == DateTime.MinValue)
940 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
942 return new DaylightTime (start, end, delta);
945 public bool IsInvalidTime (DateTime dateTime)
947 if (dateTime.Kind == DateTimeKind.Utc)
949 if (dateTime.Kind == DateTimeKind.Local && this != Local)
952 AdjustmentRule rule = GetApplicableRule (dateTime);
954 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
955 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
962 void IDeserializationCallback.OnDeserialization (object sender)
965 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
966 } catch (ArgumentException ex) {
967 throw new SerializationException ("invalid serialization data", ex);
971 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
974 throw new ArgumentNullException ("id");
976 if (id == String.Empty)
977 throw new ArgumentException ("id parameter is an empty string");
979 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
980 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
982 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
983 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
987 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
990 if (adjustmentRules != null && adjustmentRules.Length != 0) {
991 AdjustmentRule prev = null;
992 foreach (AdjustmentRule current in adjustmentRules) {
994 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
996 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
997 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
998 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;");
1000 if (prev != null && prev.DateStart > current.DateStart)
1001 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1003 if (prev != null && prev.DateEnd > current.DateStart)
1004 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1006 if (prev != null && prev.DateEnd == current.DateStart)
1007 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1014 public override string ToString ()
1019 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
1022 throw new ArgumentNullException ("info");
1023 id = (string) info.GetValue ("Id", typeof (string));
1024 displayName = (string) info.GetValue ("DisplayName", typeof (string));
1025 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
1026 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
1027 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
1028 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
1029 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
1032 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
1035 throw new ArgumentNullException ("id");
1037 if (id == String.Empty)
1038 throw new ArgumentException ("id parameter is an empty string");
1040 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
1041 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
1043 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
1044 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1048 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1051 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1053 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1054 AdjustmentRule prev = null;
1055 foreach (AdjustmentRule current in adjustmentRules) {
1056 if (current == null)
1057 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1059 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1060 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1061 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;");
1063 if (prev != null && prev.DateStart > current.DateStart)
1064 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1066 if (prev != null && prev.DateEnd > current.DateStart)
1067 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1069 if (prev != null && prev.DateEnd == current.DateStart)
1070 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1075 supportsDaylightSavingTime = false;
1079 this.baseUtcOffset = baseUtcOffset;
1080 this.displayName = displayName ?? id;
1081 this.standardDisplayName = standardDisplayName ?? id;
1082 this.daylightDisplayName = daylightDisplayName;
1083 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1084 this.adjustmentRules = adjustmentRules;
1087 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1089 //Applicable rules are in standard time
1090 DateTime date = dateTime;
1092 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1093 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1095 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1096 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1100 // get the date component of the datetime
1103 if (adjustmentRules != null) {
1104 foreach (AdjustmentRule rule in adjustmentRules) {
1105 if (rule.DateStart > date)
1107 if (rule.DateEnd < date)
1115 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1117 offset = BaseUtcOffset;
1120 if (transitions == null)
1123 //Transitions are in UTC
1124 DateTime date = dateTime;
1126 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1127 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1131 if (dateTime.Kind != DateTimeKind.Utc) {
1132 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1136 for (var i = transitions.Count - 1; i >= 0; i--) {
1137 var pair = transitions [i];
1138 DateTime ttime = pair.Key;
1139 TimeType ttype = pair.Value;
1144 offset = new TimeSpan (0, 0, ttype.Offset);
1145 isDst = ttype.IsDst;
1153 private static DateTime TransitionPoint (TransitionTime transition, int year)
1155 if (transition.IsFixedDateRule)
1156 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1158 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1159 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1160 if (day > DateTime.DaysInMonth (year, transition.Month))
1164 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1167 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1169 AdjustmentRule prev = null;
1170 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1171 if (prev != null && prev.DateEnd > current.DateStart) {
1172 adjustmentRules.Remove (current);
1176 return adjustmentRules;
1179 #if LIBC || MONODROID
1180 private static bool ValidTZFile (byte [] buffer, int length)
1182 StringBuilder magic = new StringBuilder ();
1184 for (int i = 0; i < 4; i++)
1185 magic.Append ((char)buffer [i]);
1187 if (magic.ToString () != "TZif")
1190 if (length >= BUFFER_SIZE)
1196 static int SwapInt32 (int i)
1198 return (((i >> 24) & 0xff)
1199 | ((i >> 8) & 0xff00)
1200 | ((i << 8) & 0xff0000)
1204 static int ReadBigEndianInt32 (byte [] buffer, int start)
1206 int i = BitConverter.ToInt32 (buffer, start);
1207 if (!BitConverter.IsLittleEndian)
1210 return SwapInt32 (i);
1213 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1215 //Reading the header. 4 bytes for magic, 16 are reserved
1216 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1217 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1218 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1219 int timecnt = ReadBigEndianInt32 (buffer, 32);
1220 int typecnt = ReadBigEndianInt32 (buffer, 36);
1221 int charcnt = ReadBigEndianInt32 (buffer, 40);
1223 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1224 throw new InvalidTimeZoneException ();
1226 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1227 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1228 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1230 if (time_types.Count == 0)
1231 throw new InvalidTimeZoneException ();
1233 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1234 throw new InvalidTimeZoneException ();
1236 TimeSpan baseUtcOffset = new TimeSpan (0);
1237 TimeSpan dstDelta = new TimeSpan (0);
1238 string standardDisplayName = null;
1239 string daylightDisplayName = null;
1240 bool dst_observed = false;
1241 DateTime dst_start = DateTime.MinValue;
1242 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1243 bool storeTransition = false;
1245 for (int i = 0; i < transitions.Count; i++) {
1246 var pair = transitions [i];
1247 DateTime ttime = pair.Key;
1248 TimeType ttype = pair.Value;
1250 if (standardDisplayName != ttype.Name)
1251 standardDisplayName = ttype.Name;
1252 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1253 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1254 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1255 storeTransition = true;
1256 adjustmentRules = new List<AdjustmentRule> ();
1257 dst_observed = false;
1260 //FIXME: check additional fields for this:
1261 //most of the transitions are expressed in GMT
1262 dst_start += baseUtcOffset;
1263 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1265 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1266 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1267 dst_end -= new TimeSpan (24, 0, 0);
1270 * AdjustmentRule specifies a DST period that starts and ends within a year.
1271 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1272 * Thus we fallback to the transitions.
1274 if (dst_start.AddYears (1) < dst_end)
1275 storeTransition = true;
1277 DateTime dateStart, dateEnd;
1278 if (dst_start.Month < 7)
1279 dateStart = new DateTime (dst_start.Year, 1, 1);
1281 dateStart = new DateTime (dst_start.Year, 7, 1);
1283 if (dst_end.Month >= 7)
1284 dateEnd = new DateTime (dst_end.Year, 12, 31);
1286 dateEnd = new DateTime (dst_end.Year, 6, 30);
1289 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1290 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1291 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1292 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1294 dst_observed = false;
1296 if (daylightDisplayName != ttype.Name)
1297 daylightDisplayName = ttype.Name;
1298 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1299 // Round to nearest minute, since it's not possible to create an adjustment rule
1300 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1301 // This happens with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1302 dstDelta = new TimeSpan (0, 0, ttype.Offset - ttype.Offset % 60) - baseUtcOffset;
1306 dst_observed = true;
1311 if (adjustmentRules.Count == 0 && !storeTransition) {
1312 TimeType t = (TimeType)time_types [0];
1313 if (standardDisplayName == null) {
1314 standardDisplayName = t.Name;
1315 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1317 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1319 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1322 if (storeTransition)
1323 tz.transitions = transitions;
1328 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1330 var abbrevs = new Dictionary<int, string> ();
1331 int abbrev_index = 0;
1332 var sb = new StringBuilder ();
1333 for (int i = 0; i < count; i++) {
1334 char c = (char) buffer [index + i];
1338 abbrevs.Add (abbrev_index, sb.ToString ());
1339 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1340 for (int j = 1; j < sb.Length; j++)
1341 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1342 abbrev_index = i + 1;
1343 sb = new StringBuilder ();
1349 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1351 var types = new Dictionary<int, TimeType> (count);
1352 for (int i = 0; i < count; i++) {
1353 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1354 byte is_dst = buffer [index + 6 * i + 4];
1355 byte abbrev = buffer [index + 6 * i + 5];
1356 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1361 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1363 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1364 for (int i = 0; i < count; i++) {
1365 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1366 DateTime ttime = DateTimeFromUnixTime (unixtime);
1367 byte ttype = buffer [index + 4 * count + i];
1368 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1373 static DateTime DateTimeFromUnixTime (long unix_time)
1375 DateTime date_time = new DateTime (1970, 1, 1);
1376 return date_time.AddSeconds (unix_time);
1379 #region reference sources
1380 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1381 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1384 return Local.GetUtcOffset (dateTime, out dst);
1387 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1390 return GetUtcOffset (dateTime, out dst);
1393 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1395 isDaylightSavings = false;
1396 isAmbiguousLocalDst = false;
1397 TimeSpan baseOffset = zone.BaseUtcOffset;
1399 if (zone.IsAmbiguousTime (time)) {
1400 isAmbiguousLocalDst = true;
1404 return zone.GetUtcOffset (time, out isDaylightSavings);
1410 public readonly int Offset;
1411 public readonly bool IsDst;
1414 public TimeType (int offset, bool is_dst, string abbrev)
1416 this.Offset = offset;
1417 this.IsDst = is_dst;
1421 public override string ToString ()
1423 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;