6 * Stephane Delcroix <stephane@delcroix.org>
8 * Copyright 2011 Xamarin Inc.
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.CompilerServices;
32 using System.Threading;
33 using System.Collections.Generic;
34 using System.Collections.ObjectModel;
35 using System.Runtime.Serialization;
37 using System.Globalization;
40 using Microsoft.Win32;
44 partial class TimeZoneInfo
46 TimeSpan baseUtcOffset;
47 public TimeSpan BaseUtcOffset {
48 get { return baseUtcOffset; }
51 string daylightDisplayName;
52 public string DaylightName {
54 return supportsDaylightSavingTime
61 public string DisplayName {
62 get { return displayName; }
70 static TimeZoneInfo local;
71 public static TimeZoneInfo Local {
77 throw new TimeZoneNotFoundException ();
79 if (Interlocked.CompareExchange (ref local, l, null) != null)
88 TimeZone transitions are stored when there is a change on the base offset.
90 private List<KeyValuePair<DateTime, TimeType>> transitions;
93 static TimeZoneInfo CreateLocal ()
95 if (IsWindows && LocalZoneKey != null) {
96 string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
98 name = (string)LocalZoneKey.GetValue ("StandardName"); // windows xp
99 name = TrimSpecial (name);
101 return TimeZoneInfo.FindSystemTimeZoneById (name);
104 var tz = Environment.GetEnvironmentVariable ("TZ");
106 if (tz == String.Empty)
109 return FindSystemTimeZoneByFileName (tz, Path.Combine (TimeZoneDirectory, tz));
116 return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
119 return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
126 static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
129 string filepath = Path.Combine (TimeZoneDirectory, id);
130 return FindSystemTimeZoneByFileName (id, filepath);
132 throw new NotImplementedException ();
136 static void GetSystemTimeZones (List<TimeZoneInfo> systemTimeZones)
138 if (TimeZoneKey != null) {
139 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
141 systemTimeZones.Add (FindSystemTimeZoneById (id));
149 string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
150 foreach (string continent in continents) {
152 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
154 string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
155 systemTimeZones.Add (FindSystemTimeZoneById (id));
156 } catch (ArgumentNullException) {
157 } catch (TimeZoneNotFoundException) {
158 } catch (InvalidTimeZoneException) {
159 } catch (Exception) {
166 throw new NotImplementedException ("This method is not implemented for this platform");
171 string standardDisplayName;
172 public string StandardName {
173 get { return standardDisplayName; }
176 bool supportsDaylightSavingTime;
177 public bool SupportsDaylightSavingTime {
178 get { return supportsDaylightSavingTime; }
181 static TimeZoneInfo utc;
182 public static TimeZoneInfo Utc {
185 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
190 static string timeZoneDirectory;
191 static string TimeZoneDirectory {
193 if (timeZoneDirectory == null)
194 timeZoneDirectory = "/usr/share/zoneinfo";
195 return timeZoneDirectory;
199 timeZoneDirectory = value;
203 private AdjustmentRule [] adjustmentRules;
207 /// Determine whether windows of not (taken Stephane Delcroix's code)
209 private static bool IsWindows
212 int platform = (int) Environment.OSVersion.Platform;
213 return ((platform != 4) && (platform != 6) && (platform != 128));
218 /// Needed to trim misc garbage in MS registry keys
220 private static string TrimSpecial (string str)
225 while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
226 var Iend = str.Length - 1;
227 while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
229 return str.Substring (Istart, Iend-Istart+1);
232 static RegistryKey timeZoneKey;
233 static RegistryKey TimeZoneKey {
235 if (timeZoneKey != null)
240 return timeZoneKey = Registry.LocalMachine.OpenSubKey (
241 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
246 static RegistryKey localZoneKey;
247 static RegistryKey LocalZoneKey {
249 if (localZoneKey != null)
255 return localZoneKey = Registry.LocalMachine.OpenSubKey (
256 "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
261 private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
263 var resultTicks = date.Ticks + ticks;
264 if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
265 result = default (DateTime);
269 result = new DateTime (resultTicks, kind);
273 public static void ClearCachedData ()
277 systemTimeZones = null;
280 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo destinationTimeZone)
282 return ConvertTime (dateTime, dateTime.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local, destinationTimeZone);
285 public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
287 if (sourceTimeZone == null)
288 throw new ArgumentNullException ("sourceTimeZone");
290 if (destinationTimeZone == null)
291 throw new ArgumentNullException ("destinationTimeZone");
293 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
294 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
296 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
297 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
299 if (sourceTimeZone.IsInvalidTime (dateTime))
300 throw new ArgumentException ("dateTime parameter is an invalid time");
302 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone == TimeZoneInfo.Local && destinationTimeZone == TimeZoneInfo.Local)
305 DateTime utc = ConvertTimeToUtc (dateTime, sourceTimeZone);
307 if (destinationTimeZone != TimeZoneInfo.Utc) {
308 utc = ConvertTimeFromUtc (utc, destinationTimeZone);
309 if (dateTime.Kind == DateTimeKind.Unspecified)
310 return DateTime.SpecifyKind (utc, DateTimeKind.Unspecified);
316 public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
318 if (destinationTimeZone == null)
319 throw new ArgumentNullException("destinationTimeZone");
321 var utcDateTime = dateTimeOffset.UtcDateTime;
324 var utcOffset = destinationTimeZone.GetUtcOffset(utcDateTime, out isDst);
326 return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + utcOffset, utcOffset);
329 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
331 return ConvertTime (dateTime, FindSystemTimeZoneById (destinationTimeZoneId));
334 public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId)
336 return ConvertTime (dateTime, FindSystemTimeZoneById (sourceTimeZoneId), FindSystemTimeZoneById (destinationTimeZoneId));
339 public static DateTimeOffset ConvertTimeBySystemTimeZoneId (DateTimeOffset dateTimeOffset, string destinationTimeZoneId)
341 return ConvertTime (dateTimeOffset, FindSystemTimeZoneById (destinationTimeZoneId));
344 private DateTime ConvertTimeFromUtc (DateTime dateTime)
346 if (dateTime.Kind == DateTimeKind.Local)
347 throw new ArgumentException ("Kind property of dateTime is Local");
349 if (this == TimeZoneInfo.Utc)
350 return DateTime.SpecifyKind (dateTime, DateTimeKind.Utc);
352 var utcOffset = GetUtcOffset (dateTime);
354 var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
357 if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
358 return DateTime.SpecifyKind (DateTime.MaxValue, kind);
363 public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
365 if (destinationTimeZone == null)
366 throw new ArgumentNullException ("destinationTimeZone");
368 return destinationTimeZone.ConvertTimeFromUtc (dateTime);
371 public static DateTime ConvertTimeToUtc (DateTime dateTime)
373 if (dateTime.Kind == DateTimeKind.Utc)
376 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local);
379 static internal DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
381 return ConvertTimeToUtc (dateTime, TimeZoneInfo.Local, flags);
384 public static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone)
386 return ConvertTimeToUtc (dateTime, sourceTimeZone, TimeZoneInfoOptions.None);
389 static DateTime ConvertTimeToUtc (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfoOptions flags)
391 if ((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) {
392 if (sourceTimeZone == null)
393 throw new ArgumentNullException ("sourceTimeZone");
395 if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
396 throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
398 if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
399 throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
401 if (sourceTimeZone.IsInvalidTime (dateTime))
402 throw new ArgumentException ("dateTime parameter is an invalid time");
405 if (dateTime.Kind == DateTimeKind.Utc)
409 var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
411 DateTime utcDateTime;
412 if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
413 return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
418 static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
420 bool isDaylightSavings;
421 return GetUtcOffsetFromUtc(time, TimeZoneInfo.Local, out isDaylightSavings, out isAmbiguousLocalDst);
424 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName)
426 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, null, null, true);
429 public static TimeZoneInfo CreateCustomTimeZone (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules)
431 return CreateCustomTimeZone (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, false);
434 public static TimeZoneInfo CreateCustomTimeZone ( string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
436 return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
439 public override bool Equals (object obj)
441 return Equals (obj as TimeZoneInfo);
444 public bool Equals (TimeZoneInfo other)
449 return other.Id == this.Id && HasSameRules (other);
452 public static TimeZoneInfo FindSystemTimeZoneById (string id)
454 //FIXME: this method should check for cached values in systemTimeZones
456 throw new ArgumentNullException ("id");
458 if (TimeZoneKey != null)
460 if (id == "Coordinated Universal Time")
461 id = "UTC"; //windows xp exception for "StandardName" property
462 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
464 throw new TimeZoneNotFoundException ();
465 return FromRegistryKey(id, key);
468 // Local requires special logic that already exists in the Local property (bug #326)
472 return FindSystemTimeZoneByIdCore (id);
476 private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
478 if (!File.Exists (filepath))
479 throw new TimeZoneNotFoundException ();
481 using (FileStream stream = File.OpenRead (filepath)) {
482 return BuildFromStream (id, stream);
488 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
490 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
493 throw new InvalidTimeZoneException ();
495 int bias = BitConverter.ToInt32 (reg_tzi, 0);
496 TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
498 string display_name = (string) key.GetValue ("Display");
499 string standard_name = (string) key.GetValue ("Std");
500 string daylight_name = (string) key.GetValue ("Dlt");
502 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
504 RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
505 if (dst_key != null) {
506 int first_year = (int) dst_key.GetValue ("FirstEntry");
507 int last_year = (int) dst_key.GetValue ("LastEntry");
510 for (year=first_year; year<=last_year; year++) {
511 byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
512 if (dst_tzi != null) {
513 int start_year = year == first_year ? 1 : year;
514 int end_year = year == last_year ? 9999 : year;
515 ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
520 ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
522 return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
525 private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
527 //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
528 int daylight_bias = BitConverter.ToInt32 (buffer, 8);
530 int standard_year = BitConverter.ToInt16 (buffer, 12);
531 int standard_month = BitConverter.ToInt16 (buffer, 14);
532 int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
533 int standard_day = BitConverter.ToInt16 (buffer, 18);
534 int standard_hour = BitConverter.ToInt16 (buffer, 20);
535 int standard_minute = BitConverter.ToInt16 (buffer, 22);
536 int standard_second = BitConverter.ToInt16 (buffer, 24);
537 int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
539 int daylight_year = BitConverter.ToInt16 (buffer, 28);
540 int daylight_month = BitConverter.ToInt16 (buffer, 30);
541 int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
542 int daylight_day = BitConverter.ToInt16 (buffer, 34);
543 int daylight_hour = BitConverter.ToInt16 (buffer, 36);
544 int daylight_minute = BitConverter.ToInt16 (buffer, 38);
545 int daylight_second = BitConverter.ToInt16 (buffer, 40);
546 int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
548 if (standard_month == 0 || daylight_month == 0)
552 DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
553 TransitionTime start_transition_time;
555 if (daylight_year == 0) {
556 start_date = new DateTime (start_year, 1, 1);
557 start_transition_time = TransitionTime.CreateFloatingDateRule (
558 start_timeofday, daylight_month, daylight_day,
559 (DayOfWeek) daylight_dayofweek);
562 start_date = new DateTime (daylight_year, daylight_month, daylight_day,
563 daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
564 start_transition_time = TransitionTime.CreateFixedDateRule (
565 start_timeofday, daylight_month, daylight_day);
569 DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
570 TransitionTime end_transition_time;
572 if (standard_year == 0) {
573 end_date = new DateTime (end_year, 12, 31);
574 end_transition_time = TransitionTime.CreateFloatingDateRule (
575 end_timeofday, standard_month, standard_day,
576 (DayOfWeek) standard_dayofweek);
579 end_date = new DateTime (standard_year, standard_month, standard_day,
580 standard_hour, standard_minute, standard_second, standard_millisecond);
581 end_transition_time = TransitionTime.CreateFixedDateRule (
582 end_timeofday, standard_month, standard_day);
585 TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
587 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
588 start_date, end_date, daylight_delta,
589 start_transition_time, end_transition_time));
593 public AdjustmentRule [] GetAdjustmentRules ()
595 if (!supportsDaylightSavingTime || adjustmentRules == null)
596 return new AdjustmentRule [0];
598 return (AdjustmentRule []) adjustmentRules.Clone ();
601 public TimeSpan [] GetAmbiguousTimeOffsets (DateTime dateTime)
603 if (!IsAmbiguousTime (dateTime))
604 throw new ArgumentException ("dateTime is not an ambiguous time");
606 AdjustmentRule rule = GetApplicableRule (dateTime);
608 return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
610 return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
613 public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
615 if (!IsAmbiguousTime (dateTimeOffset))
616 throw new ArgumentException ("dateTimeOffset is not an ambiguous time");
618 throw new NotImplementedException ();
621 public override int GetHashCode ()
623 int hash_code = Id.GetHashCode ();
624 foreach (AdjustmentRule rule in GetAdjustmentRules ())
625 hash_code ^= rule.GetHashCode ();
629 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
632 throw new ArgumentNullException ("info");
633 info.AddValue ("Id", id);
634 info.AddValue ("DisplayName", displayName);
635 info.AddValue ("StandardName", standardDisplayName);
636 info.AddValue ("DaylightName", daylightDisplayName);
637 info.AddValue ("BaseUtcOffset", baseUtcOffset);
638 info.AddValue ("AdjustmentRules", adjustmentRules);
639 info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
642 static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
644 public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
646 if (systemTimeZones == null) {
647 var tz = new List<TimeZoneInfo> ();
648 GetSystemTimeZones (tz);
649 Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
652 return systemTimeZones;
655 public TimeSpan GetUtcOffset (DateTime dateTime)
658 return GetUtcOffset (dateTime, out isDST);
661 public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
663 throw new NotImplementedException ();
666 private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
670 TimeZoneInfo tz = this;
671 if (dateTime.Kind == DateTimeKind.Utc)
672 tz = TimeZoneInfo.Utc;
674 if (dateTime.Kind == DateTimeKind.Local)
675 tz = TimeZoneInfo.Local;
678 var tzOffset = GetUtcOffsetHelper (dateTime, tz, out isTzDst);
685 DateTime utcDateTime;
686 if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
687 return BaseUtcOffset;
689 return GetUtcOffsetHelper (utcDateTime, this, out isDST);
692 // This is an helper method used by the method above, do not use this on its own.
693 private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz, out bool isDST)
695 if (dateTime.Kind == DateTimeKind.Local && tz != TimeZoneInfo.Local)
696 throw new Exception ();
700 if (tz == TimeZoneInfo.Utc)
701 return TimeSpan.Zero;
704 if (tz.TryGetTransitionOffset(dateTime, out offset, out isDST))
707 if (dateTime.Kind == DateTimeKind.Utc) {
708 var utcRule = tz.GetApplicableRule (dateTime);
709 if (utcRule != null && tz.IsInDST (utcRule, dateTime)) {
711 return tz.BaseUtcOffset + utcRule.DaylightDelta;
714 return tz.BaseUtcOffset;
717 DateTime stdUtcDateTime;
718 if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
719 return tz.BaseUtcOffset;
721 var tzRule = tz.GetApplicableRule (stdUtcDateTime);
723 DateTime dstUtcDateTime = DateTime.MinValue;
724 if (tzRule != null) {
725 if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
726 return tz.BaseUtcOffset;
729 if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
731 return tz.BaseUtcOffset + tzRule.DaylightDelta;
734 return tz.BaseUtcOffset;
737 public bool HasSameRules (TimeZoneInfo other)
740 throw new ArgumentNullException ("other");
742 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
745 if (this.adjustmentRules == null)
748 if (this.BaseUtcOffset != other.BaseUtcOffset)
751 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
754 for (int i = 0; i < adjustmentRules.Length; i++) {
755 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
762 public bool IsAmbiguousTime (DateTime dateTime)
764 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
765 throw new ArgumentException ("Kind is Local and time is Invalid");
767 if (this == TimeZoneInfo.Utc)
770 if (dateTime.Kind == DateTimeKind.Utc)
771 dateTime = ConvertTimeFromUtc (dateTime);
773 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
774 dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
776 AdjustmentRule rule = GetApplicableRule (dateTime);
778 DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
779 if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
786 public bool IsAmbiguousTime (DateTimeOffset dateTimeOffset)
788 throw new NotImplementedException ();
791 private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
793 // Check whether we're in the dateTime year's DST period
794 if (IsInDSTForYear (rule, dateTime, dateTime.Year))
797 // We might be in the dateTime previous year's DST period
798 return IsInDSTForYear (rule, dateTime, dateTime.Year - 1);
801 bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
803 DateTime DST_start = TransitionPoint (rule.DaylightTransitionStart, year);
804 DateTime DST_end = TransitionPoint (rule.DaylightTransitionEnd, year + ((rule.DaylightTransitionStart.Month < rule.DaylightTransitionEnd.Month) ? 0 : 1));
805 if (dateTime.Kind == DateTimeKind.Utc) {
806 DST_start -= BaseUtcOffset;
807 DST_end -= (BaseUtcOffset + rule.DaylightDelta);
810 return (dateTime >= DST_start && dateTime < DST_end);
813 public bool IsDaylightSavingTime (DateTime dateTime)
815 if (dateTime.Kind == DateTimeKind.Local && IsInvalidTime (dateTime))
816 throw new ArgumentException ("dateTime is invalid and Kind is Local");
818 if (this == TimeZoneInfo.Utc)
821 if (!SupportsDaylightSavingTime)
825 GetUtcOffset (dateTime, out isDst);
830 internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags)
832 return IsDaylightSavingTime (dateTime);
835 public bool IsDaylightSavingTime (DateTimeOffset dateTimeOffset)
837 throw new NotImplementedException ();
840 internal DaylightTime GetDaylightChanges (int year)
842 DateTime start = DateTime.MinValue, end = DateTime.MinValue;
843 TimeSpan delta = new TimeSpan ();
845 if (transitions != null) {
846 end = DateTime.MaxValue;
847 for (var i = transitions.Count - 1; i >= 0; i--) {
848 var pair = transitions [i];
849 DateTime ttime = pair.Key;
850 TimeType ttype = pair.Value;
852 if (ttime.Year > year)
854 if (ttime.Year < year)
858 // DaylightTime.Delta is relative to the current BaseUtcOffset.
859 delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
866 // DaylightTime.Start is relative to the Standard time.
867 if (start != DateTime.MinValue)
868 start += BaseUtcOffset;
870 // DaylightTime.End is relative to the DST time.
871 if (end != DateTime.MinValue)
872 end += BaseUtcOffset + delta;
874 AdjustmentRule first = null, last = null;
876 foreach (var rule in GetAdjustmentRules ()) {
877 if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
879 if (rule.DateStart.Year == year)
881 if (rule.DateEnd.Year == year)
885 if (first == null || last == null)
886 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
888 start = TransitionPoint (first.DaylightTransitionStart, year);
889 end = TransitionPoint (last.DaylightTransitionEnd, year);
890 delta = first.DaylightDelta;
893 if (start == DateTime.MinValue || end == DateTime.MinValue)
894 return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
896 return new DaylightTime (start, end, delta);
899 public bool IsInvalidTime (DateTime dateTime)
901 if (dateTime.Kind == DateTimeKind.Utc)
903 if (dateTime.Kind == DateTimeKind.Local && this != Local)
906 AdjustmentRule rule = GetApplicableRule (dateTime);
908 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
909 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
916 void IDeserializationCallback.OnDeserialization (object sender)
919 TimeZoneInfo.Validate (id, baseUtcOffset, adjustmentRules);
920 } catch (ArgumentException ex) {
921 throw new SerializationException ("invalid serialization data", ex);
925 private static void Validate (string id, TimeSpan baseUtcOffset, AdjustmentRule [] adjustmentRules)
928 throw new ArgumentNullException ("id");
930 if (id == String.Empty)
931 throw new ArgumentException ("id parameter is an empty string");
933 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
934 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
936 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
937 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
941 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
944 if (adjustmentRules != null && adjustmentRules.Length != 0) {
945 AdjustmentRule prev = null;
946 foreach (AdjustmentRule current in adjustmentRules) {
948 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
950 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
951 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
952 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;");
954 if (prev != null && prev.DateStart > current.DateStart)
955 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
957 if (prev != null && prev.DateEnd > current.DateStart)
958 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
960 if (prev != null && prev.DateEnd == current.DateStart)
961 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
968 public override string ToString ()
973 private TimeZoneInfo (SerializationInfo info, StreamingContext context)
976 throw new ArgumentNullException ("info");
977 id = (string) info.GetValue ("Id", typeof (string));
978 displayName = (string) info.GetValue ("DisplayName", typeof (string));
979 standardDisplayName = (string) info.GetValue ("StandardName", typeof (string));
980 daylightDisplayName = (string) info.GetValue ("DaylightName", typeof (string));
981 baseUtcOffset = (TimeSpan) info.GetValue ("BaseUtcOffset", typeof (TimeSpan));
982 adjustmentRules = (TimeZoneInfo.AdjustmentRule []) info.GetValue ("AdjustmentRules", typeof (TimeZoneInfo.AdjustmentRule []));
983 supportsDaylightSavingTime = (bool) info.GetValue ("SupportsDaylightSavingTime", typeof (bool));
986 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
989 throw new ArgumentNullException ("id");
991 if (id == String.Empty)
992 throw new ArgumentException ("id parameter is an empty string");
994 if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0)
995 throw new ArgumentException ("baseUtcOffset parameter does not represent a whole number of minutes");
997 if (baseUtcOffset > new TimeSpan (14, 0, 0) || baseUtcOffset < new TimeSpan (-14, 0, 0))
998 throw new ArgumentOutOfRangeException ("baseUtcOffset parameter is greater than 14 hours or less than -14 hours");
1002 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
1005 bool supportsDaylightSavingTime = !disableDaylightSavingTime;
1007 if (adjustmentRules != null && adjustmentRules.Length != 0) {
1008 AdjustmentRule prev = null;
1009 foreach (AdjustmentRule current in adjustmentRules) {
1010 if (current == null)
1011 throw new InvalidTimeZoneException ("one or more elements in adjustmentRules are null");
1013 if ((baseUtcOffset + current.DaylightDelta < new TimeSpan (-14, 0, 0)) ||
1014 (baseUtcOffset + current.DaylightDelta > new TimeSpan (14, 0, 0)))
1015 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;");
1017 if (prev != null && prev.DateStart > current.DateStart)
1018 throw new InvalidTimeZoneException ("adjustment rules specified in adjustmentRules parameter are not in chronological order");
1020 if (prev != null && prev.DateEnd > current.DateStart)
1021 throw new InvalidTimeZoneException ("some adjustment rules in the adjustmentRules parameter overlap");
1023 if (prev != null && prev.DateEnd == current.DateStart)
1024 throw new InvalidTimeZoneException ("a date can have multiple adjustment rules applied to it");
1029 supportsDaylightSavingTime = false;
1033 this.baseUtcOffset = baseUtcOffset;
1034 this.displayName = displayName ?? id;
1035 this.standardDisplayName = standardDisplayName ?? id;
1036 this.daylightDisplayName = daylightDisplayName;
1037 this.supportsDaylightSavingTime = supportsDaylightSavingTime;
1038 this.adjustmentRules = adjustmentRules;
1041 private AdjustmentRule GetApplicableRule (DateTime dateTime)
1043 //Applicable rules are in standard time
1044 DateTime date = dateTime;
1046 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1047 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
1049 } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
1050 if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
1054 // get the date component of the datetime
1057 if (adjustmentRules != null) {
1058 foreach (AdjustmentRule rule in adjustmentRules) {
1059 if (rule.DateStart > date)
1061 if (rule.DateEnd < date)
1069 private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out bool isDst)
1071 offset = BaseUtcOffset;
1074 if (transitions == null)
1077 //Transitions are in UTC
1078 DateTime date = dateTime;
1080 if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
1081 if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1085 if (dateTime.Kind != DateTimeKind.Utc) {
1086 if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
1090 for (var i = transitions.Count - 1; i >= 0; i--) {
1091 var pair = transitions [i];
1092 DateTime ttime = pair.Key;
1093 TimeType ttype = pair.Value;
1098 offset = new TimeSpan (0, 0, ttype.Offset);
1099 isDst = ttype.IsDst;
1107 private static DateTime TransitionPoint (TransitionTime transition, int year)
1109 if (transition.IsFixedDateRule)
1110 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
1112 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
1113 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first + 7) % 7;
1114 if (day > DateTime.DaysInMonth (year, transition.Month))
1118 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
1121 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
1123 AdjustmentRule prev = null;
1124 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
1125 if (prev != null && prev.DateEnd > current.DateStart) {
1126 adjustmentRules.Remove (current);
1130 return adjustmentRules;
1133 #if LIBC || MONOTOUCH
1134 const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
1136 private static TimeZoneInfo BuildFromStream (string id, Stream stream)
1138 byte [] buffer = new byte [BUFFER_SIZE];
1139 int length = stream.Read (buffer, 0, BUFFER_SIZE);
1141 if (!ValidTZFile (buffer, length))
1142 throw new InvalidTimeZoneException ("TZ file too big for the buffer");
1145 return ParseTZBuffer (id, buffer, length);
1146 } catch (Exception e) {
1147 throw new InvalidTimeZoneException (e.Message);
1151 private static bool ValidTZFile (byte [] buffer, int length)
1153 StringBuilder magic = new StringBuilder ();
1155 for (int i = 0; i < 4; i++)
1156 magic.Append ((char)buffer [i]);
1158 if (magic.ToString () != "TZif")
1161 if (length >= BUFFER_SIZE)
1167 static int SwapInt32 (int i)
1169 return (((i >> 24) & 0xff)
1170 | ((i >> 8) & 0xff00)
1171 | ((i << 8) & 0xff0000)
1175 static int ReadBigEndianInt32 (byte [] buffer, int start)
1177 int i = BitConverter.ToInt32 (buffer, start);
1178 if (!BitConverter.IsLittleEndian)
1181 return SwapInt32 (i);
1184 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
1186 //Reading the header. 4 bytes for magic, 16 are reserved
1187 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
1188 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
1189 int leapcnt = ReadBigEndianInt32 (buffer, 28);
1190 int timecnt = ReadBigEndianInt32 (buffer, 32);
1191 int typecnt = ReadBigEndianInt32 (buffer, 36);
1192 int charcnt = ReadBigEndianInt32 (buffer, 40);
1194 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
1195 throw new InvalidTimeZoneException ();
1197 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
1198 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
1199 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
1201 if (time_types.Count == 0)
1202 throw new InvalidTimeZoneException ();
1204 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
1205 throw new InvalidTimeZoneException ();
1207 TimeSpan baseUtcOffset = new TimeSpan (0);
1208 TimeSpan dstDelta = new TimeSpan (0);
1209 string standardDisplayName = null;
1210 string daylightDisplayName = null;
1211 bool dst_observed = false;
1212 DateTime dst_start = DateTime.MinValue;
1213 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
1214 bool storeTransition = false;
1216 for (int i = 0; i < transitions.Count; i++) {
1217 var pair = transitions [i];
1218 DateTime ttime = pair.Key;
1219 TimeType ttype = pair.Value;
1221 if (standardDisplayName != ttype.Name)
1222 standardDisplayName = ttype.Name;
1223 if (baseUtcOffset.TotalSeconds != ttype.Offset) {
1224 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
1225 if (adjustmentRules.Count > 0) // We ignore AdjustmentRules but store transitions.
1226 storeTransition = true;
1227 adjustmentRules = new List<AdjustmentRule> ();
1228 dst_observed = false;
1231 //FIXME: check additional fields for this:
1232 //most of the transitions are expressed in GMT
1233 dst_start += baseUtcOffset;
1234 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
1236 //some weird timezone (America/Phoenix) have end dates on Jan 1st
1237 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
1238 dst_end -= new TimeSpan (24, 0, 0);
1241 * AdjustmentRule specifies a DST period that starts and ends within a year.
1242 * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
1243 * Thus we fallback to the transitions.
1245 if (dst_start.AddYears (1) < dst_end)
1246 storeTransition = true;
1248 DateTime dateStart, dateEnd;
1249 if (dst_start.Month < 7)
1250 dateStart = new DateTime (dst_start.Year, 1, 1);
1252 dateStart = new DateTime (dst_start.Year, 7, 1);
1254 if (dst_end.Month >= 7)
1255 dateEnd = new DateTime (dst_end.Year, 12, 31);
1257 dateEnd = new DateTime (dst_end.Year, 6, 30);
1260 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
1261 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
1262 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
1263 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
1265 dst_observed = false;
1267 if (daylightDisplayName != ttype.Name)
1268 daylightDisplayName = ttype.Name;
1269 if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
1270 // Round to nearest minute, since it's not possible to create an adjustment rule
1271 // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
1272 // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
1273 dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
1274 if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
1275 dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
1279 dst_observed = true;
1284 if (adjustmentRules.Count == 0 && !storeTransition) {
1285 TimeType t = (TimeType)time_types [0];
1286 if (standardDisplayName == null) {
1287 standardDisplayName = t.Name;
1288 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
1290 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
1292 tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
1295 if (storeTransition && transitions.Count > 0) {
1296 tz.transitions = transitions;
1297 tz.supportsDaylightSavingTime = true;
1303 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
1305 var abbrevs = new Dictionary<int, string> ();
1306 int abbrev_index = 0;
1307 var sb = new StringBuilder ();
1308 for (int i = 0; i < count; i++) {
1309 char c = (char) buffer [index + i];
1313 abbrevs.Add (abbrev_index, sb.ToString ());
1314 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
1315 for (int j = 1; j < sb.Length; j++)
1316 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
1317 abbrev_index = i + 1;
1318 sb = new StringBuilder ();
1324 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
1326 var types = new Dictionary<int, TimeType> (count);
1327 for (int i = 0; i < count; i++) {
1328 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
1329 byte is_dst = buffer [index + 6 * i + 4];
1330 byte abbrev = buffer [index + 6 * i + 5];
1331 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
1336 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
1338 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
1339 for (int i = 0; i < count; i++) {
1340 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
1341 DateTime ttime = DateTimeFromUnixTime (unixtime);
1342 byte ttype = buffer [index + 4 * count + i];
1343 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
1348 static DateTime DateTimeFromUnixTime (long unix_time)
1350 DateTime date_time = new DateTime (1970, 1, 1);
1351 return date_time.AddSeconds (unix_time);
1354 #region reference sources
1355 // Shortcut for TimeZoneInfo.Local.GetUtcOffset
1356 internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1359 return Local.GetUtcOffset (dateTime, out dst);
1362 internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
1365 return GetUtcOffset (dateTime, out dst);
1368 static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone, out Boolean isDaylightSavings, out Boolean isAmbiguousLocalDst)
1370 isDaylightSavings = false;
1371 isAmbiguousLocalDst = false;
1372 TimeSpan baseOffset = zone.BaseUtcOffset;
1374 if (zone.IsAmbiguousTime (time)) {
1375 isAmbiguousLocalDst = true;
1379 return zone.GetUtcOffset (time, out isDaylightSavings);
1385 public readonly int Offset;
1386 public readonly bool IsDst;
1389 public TimeType (int offset, bool is_dst, string abbrev)
1391 this.Offset = offset;
1392 this.IsDst = is_dst;
1396 public override string ToString ()
1398 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;