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;
49 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
51 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
53 [SerializableAttribute]
55 sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
57 TimeSpan baseUtcOffset;
58 public TimeSpan BaseUtcOffset {
59 get { return baseUtcOffset; }
62 string daylightDisplayName;
63 public string DaylightName {
65 return supportsDaylightSavingTime
72 public string DisplayName {
73 get { return displayName; }
81 static TimeZoneInfo local;
82 public static TimeZoneInfo Local {
88 throw new TimeZoneNotFoundException ();
90 if (Interlocked.CompareExchange (ref local, l, null) != null)
99 TimeZone transitions are stored when there is a change on the base offset.
101 private List<KeyValuePair<DateTime, TimeType>> transitions;
103 static TimeZoneInfo CreateLocal ()
106 return AndroidTimeZones.Local;
108 using (Stream stream = GetMonoTouchData (null)) {
109 return BuildFromStream ("Local", stream);
113 if (IsWindows && LocalZoneKey != null) {
114 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
116 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
117 name = TrimSpecial (name);
119 return TimeZoneInfo.FindSystemTimeZoneById (name);
123 var tz = Environment.GetEnvironmentVariable ("TZ");
125 if (tz == String.Empty)
128 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
135 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
138 return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
146 string standardDisplayName;
147 public string StandardName {
148 get { return standardDisplayName; }
151 bool supportsDaylightSavingTime;
152 public bool SupportsDaylightSavingTime {
153 get { return supportsDaylightSavingTime; }
156 static TimeZoneInfo utc;
157 public static TimeZoneInfo Utc {
160 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
165 static string timeZoneDirectory;
166 static string TimeZoneDirectory {
168 if (timeZoneDirectory == null)
169 timeZoneDirectory = "/usr/share/zoneinfo";
170 return timeZoneDirectory;
174 timeZoneDirectory = value;
178 private AdjustmentRule [] adjustmentRules;
182 /// Determine whether windows of not (taken Stephane Delcroix's code)
184 private static bool IsWindows
187 int platform = (int) Environment.OSVersion.Platform;
188 return ((platform != 4) && (platform != 6) && (platform != 128));
193 /// Needed to trim misc garbage in MS registry keys
195 private static string TrimSpecial (string str)
200 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
201 var Iend = str.Length - 1;
202 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
204 return str.Substring (Istart, Iend-Istart+1);
207 static RegistryKey timeZoneKey;
208 static RegistryKey TimeZoneKey {
210 if (timeZoneKey != null)
215 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
216 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
221 static RegistryKey localZoneKey;
222 static RegistryKey LocalZoneKey {
224 if (localZoneKey != null)
230 return localZoneKey = Registry.LocalMachine.OpenSubKey (
231 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
236 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
238 var resultTicks = date.Ticks + ticks;
239 if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
240 result = default (DateTime);
244 result = new DateTime (resultTicks, kind);
248 public static void ClearCachedData ()
252 systemTimeZones = null;
255 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
257 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
260 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
262 if (sourceTimeZone == null)
263 throw new ArgumentNullException ("sourceTimeZone");
265 if (destinationTimeZone == null)
266 throw new ArgumentNullException ("destinationTimeZone");
268 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
269 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
271 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
272 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
274 if (sourceTimeZone.IsInvalidTime (dateTime))
275 throw new ArgumentException ("dateTime parameter is an invalid time");
277 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
280 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
282 if (destinationTimeZone != TimeZoneInfo.Utc) {
283 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
284 if (dateTime.Kind == DateTimeKind.Unspecified)
285 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
291 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
293 if (destinationTimeZone == null)
294 throw new ArgumentNullException("destinationTimeZone");
296 var utcDateTime = dateTimeOffset.UtcDateTime;
299 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
301 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
304 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
306 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
309 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
311 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
314 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
316 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
319 private DateTime ConvertTimeFromUtc (DateTime dateTime)
321 if (dateTime.Kind == DateTimeKind.Local)
322 throw new ArgumentException ("Kind property of dateTime is Local");
324 if (this == TimeZoneInfo.Utc)
325 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
327 var utcOffset = GetUtcOffset (dateTime);
329 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
332 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
333 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
338 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
340 if (destinationTimeZone == null)
341 throw new ArgumentNullException ("destinationTimeZone");
343 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
346 public static DateTime ConvertTimeToUtc (DateTime dateTime)
348 if (dateTime.Kind == DateTimeKind.Utc)
351 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
354 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
356 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
359 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
361 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
364 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
366 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
367 if (sourceTimeZone == null)
368 throw new ArgumentNullException ("sourceTimeZone");
370 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
371 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
373 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
374 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
376 if (sourceTimeZone.IsInvalidTime (dateTime))
377 throw new ArgumentException ("dateTime parameter is an invalid time");
380 if (dateTime.Kind == DateTimeKind.Utc)
384 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
386 DateTime utcDateTime;
387 if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
388 return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
393 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
395 bool isDaylightSavings;
396 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
399 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
401 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
404 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
406 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
409 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
411 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
414 public override bool Equals (object obj)
416 return Equals (obj as TimeZoneInfo);
419 public bool Equals (TimeZoneInfo other)
424 return other.Id == this.Id && HasSameRules (other);
427 public static TimeZoneInfo FindSystemTimeZoneById (string id)
429 //FIXME: this method should check for cached values in systemTimeZones
431 throw new ArgumentNullException ("id");
433 if (TimeZoneKey != null)
435 if (id == "Coordinated Universal Time")
436 id = "UTC"; //windows xp exception for "StandardName" property
437 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
439 throw new TimeZoneNotFoundException ();
440 return FromRegistryKey(id, key);
443 // Local requires special logic that already exists in the Local property (bug #326)
447 using (Stream stream = GetMonoTouchData (id)) {
448 return BuildFromStream (id, stream);
451 var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
452 if (timeZoneInfo == null)
453 throw new TimeZoneNotFoundException ();
456 string filepath = Path.Combine (TimeZoneDirectory, id);
457 return FindSystemTimeZoneByFileName (id, filepath);
459 throw new NotImplementedException ();
464 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
466 if (!File.Exists (filepath))
467 throw new TimeZoneNotFoundException ();
469 using (FileStream stream = File.OpenRead (filepath)) {
470 return BuildFromStream (id, stream);
474 #if LIBC || MONOTOUCH
475 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
477 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
479 byte [] buffer = new byte [BUFFER_SIZE];
480 int length = stream.Read (buffer, 0, BUFFER_SIZE);
482 if (!ValidTZFile (buffer, length))
483 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
486 return ParseTZBuffer (id, buffer, length);
487 } catch (Exception e) {
488 throw new InvalidTimeZoneException (e.Message);
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)
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 //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
649 private static List<TimeZoneInfo> systemTimeZones;
650 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
652 if (systemTimeZones == null) {
653 systemTimeZones = new List<TimeZoneInfo> ();
655 if (TimeZoneKey != null) {
656 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
658 systemTimeZones.Add (FindSystemTimeZoneById (id));
662 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
666 foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
667 var tz = AndroidTimeZones.GetTimeZone (id, id);
669 systemTimeZones.Add (tz);
672 if (systemTimeZones.Count == 0) {
673 foreach (string name in GetMonoTouchNames ()) {
674 using (Stream stream = GetMonoTouchData (name, false)) {
677 systemTimeZones.Add (BuildFromStream (name, stream));
682 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
683 foreach (string continent in continents) {
685 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
687 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
688 systemTimeZones.Add (FindSystemTimeZoneById (id));
689 } catch (ArgumentNullException) {
690 } catch (TimeZoneNotFoundException) {
691 } catch (InvalidTimeZoneException) {
692 } catch (Exception) {
699 throw new NotImplementedException ("This method is not implemented for this platform");
702 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
705 public TimeSpan GetUtcOffset (DateTime dateTime)
708 return GetUtcOffset (dateTime, out isDST);
711 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
713 throw new NotImplementedException ();
716 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
720 TimeZoneInfo tz = this;
721 if (dateTime.Kind == DateTimeKind.Utc)
722 tz = TimeZoneInfo.Utc;
724 if (dateTime.Kind == DateTimeKind.Local)
725 tz = TimeZoneInfo.Local;
728 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
735 DateTime utcDateTime;
736 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
737 return BaseUtcOffset;
739 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
742 // This is an helper method used by the method above, do not use this on its own.
743 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
745 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
746 throw new Exception ();
750 if (tz == TimeZoneInfo.Utc)
751 return TimeSpan.Zero;
754 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
757 if (dateTime.Kind == DateTimeKind.Utc) {
758 var utcRule = tz.GetApplicableRule (dateTime);
759 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
761 return tz.BaseUtcOffset + utcRule.DaylightDelta;
764 return tz.BaseUtcOffset;
767 DateTime stdUtcDateTime;
768 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
769 return tz.BaseUtcOffset;
771 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
773 DateTime dstUtcDateTime = DateTime.MinValue;
774 if (tzRule != null) {
775 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
776 return tz.BaseUtcOffset;
779 if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
781 return tz.BaseUtcOffset + tzRule.DaylightDelta;
784 return tz.BaseUtcOffset;
787 public bool HasSameRules (TimeZoneInfo other)
790 throw new ArgumentNullException ("other");
792 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
795 if (this.adjustmentRules == null)
798 if (this.BaseUtcOffset != other.BaseUtcOffset)
801 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
804 for (int i = 0; i < adjustmentRules.Length; i++) {
805 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
812 public bool IsAmbiguousTime (DateTime dateTime)
814 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
815 throw new ArgumentException ("Kind is Local and time is Invalid");
817 if (this == TimeZoneInfo.Utc)
820 if (dateTime.Kind == DateTimeKind.Utc)
821 dateTime = ConvertTimeFromUtc (dateTime);
823 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
824 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
826 AdjustmentRule rule = GetApplicableRule (dateTime);
828 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
829 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
836 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
838 throw new NotImplementedException ();
841 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
843 // Check whether we're in the dateTime year's DST period
844 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
847 // We might be in the dateTime previous year's DST period
848 return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
851 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
853 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
854 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
855 if (dateTime.Kind == DateTimeKind.Utc) {
856 DST_start -= BaseUtcOffset;
857 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
860 return (dateTime >= DST_start && dateTime < DST_end);
863 public bool IsDaylightSavingTime (DateTime dateTime)
865 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
866 throw new ArgumentException ("dateTime is invalid and Kind is Local");
868 if (this == TimeZoneInfo.Utc)
871 if (!SupportsDaylightSavingTime)
875 GetUtcOffset (dateTime, out isDst);
880 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
882 return IsDaylightSavingTime (dateTime);
885 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
887 throw new NotImplementedException ();
890 public bool IsInvalidTime (DateTime dateTime)
892 if (dateTime.Kind == DateTimeKind.Utc)
894 if (dateTime.Kind == DateTimeKind.Local && this != Local)
897 AdjustmentRule rule = GetApplicableRule (dateTime);
899 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
900 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
907 void IDeserializationCallback.OnDeserialization (object sender)
910 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
911 } catch (ArgumentException ex) {
912 throw new SerializationException ("invalid serialization data", ex);
916 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
919 throw new ArgumentNullException ("id");
921 if (id == String.Empty)
922 throw new ArgumentException ("id parameter is an empty string");
924 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
925 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
927 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
928 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
932 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
935 if (adjustmentRules != null && adjustmentRules.Length != 0) {
936 AdjustmentRule prev = null;
937 foreach (AdjustmentRule current in adjustmentRules) {
939 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
941 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
942 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
943 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;");
945 if (prev != null && prev.DateStart > current.DateStart)
946 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
948 if (prev != null && prev.DateEnd > current.DateStart)
949 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
951 if (prev != null && prev.DateEnd == current.DateStart)
952 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
959 public override string ToString ()
964 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
967 throw new ArgumentNullException ("info");
968 id = (string) info.GetValue ("Id", typeof (string));
969 displayName = (string) info.GetValue ("DisplayName", typeof (string));
970 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
971 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
972 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
973 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
974 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
977 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
980 throw new ArgumentNullException ("id");
982 if (id == String.Empty)
983 throw new ArgumentException ("id parameter is an empty string");
985 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
986 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
988 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
989 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
993 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
996 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
998 if (adjustmentRules != null && adjustmentRules.Length != 0) {
999 AdjustmentRule prev = null;
1000 foreach (AdjustmentRule current in adjustmentRules) {
1001 if (current == null)
1002 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1004 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1005 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1006 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;");
1008 if (prev != null && prev.DateStart > current.DateStart)
1009 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1011 if (prev != null && prev.DateEnd > current.DateStart)
1012 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1014 if (prev != null && prev.DateEnd == current.DateStart)
1015 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1020 supportsDaylightSavingTime = false;
1024 this.baseUtcOffset = baseUtcOffset;
1025 this.displayName = displayName ?? id;
1026 this.standardDisplayName = standardDisplayName ?? id;
1027 this.daylightDisplayName = daylightDisplayName;
1028 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1029 this.adjustmentRules = adjustmentRules;
1032 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1034 //Applicable rules are in standard time
1035 DateTime date = dateTime;
1037 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1038 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1040 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1041 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1045 // get the date component of the datetime
1048 if (adjustmentRules != null) {
1049 foreach (AdjustmentRule rule in adjustmentRules) {
1050 if (rule.DateStart > date)
1052 if (rule.DateEnd < date)
1060 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1062 offset = BaseUtcOffset;
1065 if (transitions == null)
1068 //Transitions are in UTC
1069 DateTime date = dateTime;
1071 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1072 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1076 if (dateTime.Kind != DateTimeKind.Utc) {
1077 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1081 for (var i = transitions.Count - 1; i >= 0; i--) {
1082 var pair = transitions [i];
1083 DateTime ttime = pair.Key;
1084 TimeType ttype = pair.Value;
1089 offset = new TimeSpan (0, 0, ttype.Offset);
1090 isDst = ttype.IsDst;
1098 private static DateTime TransitionPoint (TransitionTime transition, int year)
1100 if (transition.IsFixedDateRule)
1101 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1103 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1104 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1105 if (day > DateTime.DaysInMonth (year, transition.Month))
1109 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1112 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1114 AdjustmentRule prev = null;
1115 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1116 if (prev != null && prev.DateEnd > current.DateStart) {
1117 adjustmentRules.Remove (current);
1121 return adjustmentRules;
1124 #if LIBC || MONODROID
1125 private static bool ValidTZFile (byte [] buffer, int length)
1127 StringBuilder magic = new StringBuilder ();
1129 for (int i = 0; i < 4; i++)
1130 magic.Append ((char)buffer [i]);
1132 if (magic.ToString () != "TZif")
1135 if (length >= BUFFER_SIZE)
1141 static int SwapInt32 (int i)
1143 return (((i >> 24) & 0xff)
1144 | ((i >> 8) & 0xff00)
1145 | ((i << 8) & 0xff0000)
1149 static int ReadBigEndianInt32 (byte [] buffer, int start)
1151 int i = BitConverter.ToInt32 (buffer, start);
1152 if (!BitConverter.IsLittleEndian)
1155 return SwapInt32 (i);
1158 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1160 //Reading the header. 4 bytes for magic, 16 are reserved
1161 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1162 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1163 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1164 int timecnt = ReadBigEndianInt32 (buffer, 32);
1165 int typecnt = ReadBigEndianInt32 (buffer, 36);
1166 int charcnt = ReadBigEndianInt32 (buffer, 40);
1168 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1169 throw new InvalidTimeZoneException ();
1171 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1172 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1173 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1175 if (time_types.Count == 0)
1176 throw new InvalidTimeZoneException ();
1178 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1179 throw new InvalidTimeZoneException ();
1181 TimeSpan baseUtcOffset = new TimeSpan (0);
1182 TimeSpan dstDelta = new TimeSpan (0);
1183 string standardDisplayName = null;
1184 string daylightDisplayName = null;
1185 bool dst_observed = false;
1186 DateTime dst_start = DateTime.MinValue;
1187 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1188 bool storeTransition = false;
1190 for (int i = 0; i < transitions.Count; i++) {
1191 var pair = transitions [i];
1192 DateTime ttime = pair.Key;
1193 TimeType ttype = pair.Value;
1195 if (standardDisplayName != ttype.Name)
1196 standardDisplayName = ttype.Name;
1197 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1198 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1199 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1200 storeTransition = true;
1201 adjustmentRules = new List<AdjustmentRule> ();
1202 dst_observed = false;
1205 //FIXME: check additional fields for this:
1206 //most of the transitions are expressed in GMT
1207 dst_start += baseUtcOffset;
1208 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1210 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1211 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1212 dst_end -= new TimeSpan (24, 0, 0);
1215 * AdjustmentRule specifies a DST period that starts and ends within a year.
1216 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1217 * Thus we fallback to the transitions.
1219 if (dst_start.AddYears (1) < dst_end)
1220 storeTransition = true;
1222 DateTime dateStart, dateEnd;
1223 if (dst_start.Month < 7)
1224 dateStart = new DateTime (dst_start.Year, 1, 1);
1226 dateStart = new DateTime (dst_start.Year, 7, 1);
1228 if (dst_end.Month >= 7)
1229 dateEnd = new DateTime (dst_end.Year, 12, 31);
1231 dateEnd = new DateTime (dst_end.Year, 6, 30);
1234 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1235 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1236 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1237 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1239 dst_observed = false;
1241 if (daylightDisplayName != ttype.Name)
1242 daylightDisplayName = ttype.Name;
1243 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
1244 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
1247 dst_observed = true;
1252 if (adjustmentRules.Count == 0 && !storeTransition) {
1253 TimeType t = (TimeType)time_types [0];
1254 if (standardDisplayName == null) {
1255 standardDisplayName = t.Name;
1256 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1258 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1260 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1263 if (storeTransition)
1264 tz.transitions = transitions;
1269 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1271 var abbrevs = new Dictionary<int, string> ();
1272 int abbrev_index = 0;
1273 var sb = new StringBuilder ();
1274 for (int i = 0; i < count; i++) {
1275 char c = (char) buffer [index + i];
1279 abbrevs.Add (abbrev_index, sb.ToString ());
1280 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1281 for (int j = 1; j < sb.Length; j++)
1282 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1283 abbrev_index = i + 1;
1284 sb = new StringBuilder ();
1290 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1292 var types = new Dictionary<int, TimeType> (count);
1293 for (int i = 0; i < count; i++) {
1294 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1295 byte is_dst = buffer [index + 6 * i + 4];
1296 byte abbrev = buffer [index + 6 * i + 5];
1297 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1302 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1304 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1305 for (int i = 0; i < count; i++) {
1306 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1307 DateTime ttime = DateTimeFromUnixTime (unixtime);
1308 byte ttype = buffer [index + 4 * count + i];
1309 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1314 static DateTime DateTimeFromUnixTime (long unix_time)
1316 DateTime date_time = new DateTime (1970, 1, 1);
1317 return date_time.AddSeconds (unix_time);
1320 #region reference sources
1321 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1322 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1325 return Local.GetUtcOffset (dateTime, out dst);
1328 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1331 return GetUtcOffset (dateTime, out dst);
1334 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1336 isDaylightSavings = false;
1337 isAmbiguousLocalDst = false;
1338 TimeSpan baseOffset = zone.BaseUtcOffset;
1340 if (zone.IsAmbiguousTime (time)) {
1341 isAmbiguousLocalDst = true;
1345 return zone.GetUtcOffset (time, out isDaylightSavings);
1351 public readonly int Offset;
1352 public readonly bool IsDst;
1355 public TimeType (int offset, bool is_dst, string abbrev)
1357 this.Offset = offset;
1358 this.IsDst = is_dst;
1362 public override string ToString ()
1364 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;