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 public bool IsInvalidTime (DateTime dateTime)
885 if (dateTime.Kind == DateTimeKind.Utc)
887 if (dateTime.Kind == DateTimeKind.Local && this != Local)
890 AdjustmentRule rule = GetApplicableRule (dateTime);
892 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
893 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
900 void IDeserializationCallback.OnDeserialization (object sender)
903 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
904 } catch (ArgumentException ex) {
905 throw new SerializationException ("invalid serialization data", ex);
909 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
912 throw new ArgumentNullException ("id");
914 if (id == String.Empty)
915 throw new ArgumentException ("id parameter is an empty string");
917 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
918 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
920 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
921 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
925 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
928 if (adjustmentRules != null && adjustmentRules.Length != 0) {
929 AdjustmentRule prev = null;
930 foreach (AdjustmentRule current in adjustmentRules) {
932 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
934 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
935 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
936 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;");
938 if (prev != null && prev.DateStart > current.DateStart)
939 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
941 if (prev != null && prev.DateEnd > current.DateStart)
942 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
944 if (prev != null && prev.DateEnd == current.DateStart)
945 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
952 public override string ToString ()
957 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
960 throw new ArgumentNullException ("info");
961 id = (string) info.GetValue ("Id", typeof (string));
962 displayName = (string) info.GetValue ("DisplayName", typeof (string));
963 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
964 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
965 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
966 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
967 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
970 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
973 throw new ArgumentNullException ("id");
975 if (id == String.Empty)
976 throw new ArgumentException ("id parameter is an empty string");
978 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
979 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
981 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
982 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
986 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
989 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
991 if (adjustmentRules != null && adjustmentRules.Length != 0) {
992 AdjustmentRule prev = null;
993 foreach (AdjustmentRule current in adjustmentRules) {
995 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
997 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
998 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
999 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;");
1001 if (prev != null && prev.DateStart > current.DateStart)
1002 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1004 if (prev != null && prev.DateEnd > current.DateStart)
1005 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1007 if (prev != null && prev.DateEnd == current.DateStart)
1008 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1013 supportsDaylightSavingTime = false;
1017 this.baseUtcOffset = baseUtcOffset;
1018 this.displayName = displayName ?? id;
1019 this.standardDisplayName = standardDisplayName ?? id;
1020 this.daylightDisplayName = daylightDisplayName;
1021 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1022 this.adjustmentRules = adjustmentRules;
1025 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1027 //Applicable rules are in standard time
1028 DateTime date = dateTime;
1030 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1031 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1033 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1034 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1038 // get the date component of the datetime
1041 if (adjustmentRules != null) {
1042 foreach (AdjustmentRule rule in adjustmentRules) {
1043 if (rule.DateStart > date)
1045 if (rule.DateEnd < date)
1053 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1055 offset = BaseUtcOffset;
1058 if (transitions == null)
1061 //Transitions are in UTC
1062 DateTime date = dateTime;
1064 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1065 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1069 if (dateTime.Kind != DateTimeKind.Utc) {
1070 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1074 for (var i = transitions.Count - 1; i >= 0; i--) {
1075 var pair = transitions [i];
1076 DateTime ttime = pair.Key;
1077 TimeType ttype = pair.Value;
1082 offset = new TimeSpan (0, 0, ttype.Offset);
1083 isDst = ttype.IsDst;
1091 private static DateTime TransitionPoint (TransitionTime transition, int year)
1093 if (transition.IsFixedDateRule)
1094 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1096 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1097 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1098 if (day > DateTime.DaysInMonth (year, transition.Month))
1102 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1105 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1107 AdjustmentRule prev = null;
1108 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1109 if (prev != null && prev.DateEnd > current.DateStart) {
1110 adjustmentRules.Remove (current);
1114 return adjustmentRules;
1117 #if LIBC || MONODROID
1118 private static bool ValidTZFile (byte [] buffer, int length)
1120 StringBuilder magic = new StringBuilder ();
1122 for (int i = 0; i < 4; i++)
1123 magic.Append ((char)buffer [i]);
1125 if (magic.ToString () != "TZif")
1128 if (length >= BUFFER_SIZE)
1134 static int SwapInt32 (int i)
1136 return (((i >> 24) & 0xff)
1137 | ((i >> 8) & 0xff00)
1138 | ((i << 8) & 0xff0000)
1142 static int ReadBigEndianInt32 (byte [] buffer, int start)
1144 int i = BitConverter.ToInt32 (buffer, start);
1145 if (!BitConverter.IsLittleEndian)
1148 return SwapInt32 (i);
1151 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1153 //Reading the header. 4 bytes for magic, 16 are reserved
1154 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1155 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1156 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1157 int timecnt = ReadBigEndianInt32 (buffer, 32);
1158 int typecnt = ReadBigEndianInt32 (buffer, 36);
1159 int charcnt = ReadBigEndianInt32 (buffer, 40);
1161 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1162 throw new InvalidTimeZoneException ();
1164 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1165 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1166 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1168 if (time_types.Count == 0)
1169 throw new InvalidTimeZoneException ();
1171 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1172 throw new InvalidTimeZoneException ();
1174 TimeSpan baseUtcOffset = new TimeSpan (0);
1175 TimeSpan dstDelta = new TimeSpan (0);
1176 string standardDisplayName = null;
1177 string daylightDisplayName = null;
1178 bool dst_observed = false;
1179 DateTime dst_start = DateTime.MinValue;
1180 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1181 bool storeTransition = false;
1183 for (int i = 0; i < transitions.Count; i++) {
1184 var pair = transitions [i];
1185 DateTime ttime = pair.Key;
1186 TimeType ttype = pair.Value;
1188 if (standardDisplayName != ttype.Name)
1189 standardDisplayName = ttype.Name;
1190 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1191 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1192 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1193 storeTransition = true;
1194 adjustmentRules = new List<AdjustmentRule> ();
1195 dst_observed = false;
1198 //FIXME: check additional fields for this:
1199 //most of the transitions are expressed in GMT
1200 dst_start += baseUtcOffset;
1201 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1203 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1204 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1205 dst_end -= new TimeSpan (24, 0, 0);
1208 * AdjustmentRule specifies a DST period that starts and ends within a year.
1209 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1210 * Thus we fallback to the transitions.
1212 if (dst_start.AddYears (1) < dst_end)
1213 storeTransition = true;
1215 DateTime dateStart, dateEnd;
1216 if (dst_start.Month < 7)
1217 dateStart = new DateTime (dst_start.Year, 1, 1);
1219 dateStart = new DateTime (dst_start.Year, 7, 1);
1221 if (dst_end.Month >= 7)
1222 dateEnd = new DateTime (dst_end.Year, 12, 31);
1224 dateEnd = new DateTime (dst_end.Year, 6, 30);
1227 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1228 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1229 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1230 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1232 dst_observed = false;
1234 if (daylightDisplayName != ttype.Name)
1235 daylightDisplayName = ttype.Name;
1236 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
1237 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
1240 dst_observed = true;
1245 if (adjustmentRules.Count == 0 && !storeTransition) {
1246 TimeType t = (TimeType)time_types [0];
1247 if (standardDisplayName == null) {
1248 standardDisplayName = t.Name;
1249 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1251 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1253 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1256 if (storeTransition)
1257 tz.transitions = transitions;
1262 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1264 var abbrevs = new Dictionary<int, string> ();
1265 int abbrev_index = 0;
1266 var sb = new StringBuilder ();
1267 for (int i = 0; i < count; i++) {
1268 char c = (char) buffer [index + i];
1272 abbrevs.Add (abbrev_index, sb.ToString ());
1273 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1274 for (int j = 1; j < sb.Length; j++)
1275 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1276 abbrev_index = i + 1;
1277 sb = new StringBuilder ();
1283 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1285 var types = new Dictionary<int, TimeType> (count);
1286 for (int i = 0; i < count; i++) {
1287 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1288 byte is_dst = buffer [index + 6 * i + 4];
1289 byte abbrev = buffer [index + 6 * i + 5];
1290 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1295 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1297 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1298 for (int i = 0; i < count; i++) {
1299 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1300 DateTime ttime = DateTimeFromUnixTime (unixtime);
1301 byte ttype = buffer [index + 4 * count + i];
1302 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1307 static DateTime DateTimeFromUnixTime (long unix_time)
1309 DateTime date_time = new DateTime (1970, 1, 1);
1310 return date_time.AddSeconds (unix_time);
1313 #region reference sources
1314 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1315 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1318 return Local.GetUtcOffset (dateTime, out dst);
1321 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1324 return GetUtcOffset (dateTime, out dst);
1327 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1329 isDaylightSavings = false;
1330 isAmbiguousLocalDst = false;
1331 TimeSpan baseOffset = zone.BaseUtcOffset;
1333 if (zone.IsAmbiguousTime (time)) {
1334 isAmbiguousLocalDst = true;
1338 return zone.GetUtcOffset (time, out isDaylightSavings);
1344 public readonly int Offset;
1345 public readonly bool IsDst;
1348 public TimeType (int offset, bool is_dst, string abbrev)
1350 this.Offset = offset;
1351 this.IsDst = is_dst;
1355 public override string ToString ()
1357 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;