Merge pull request #1696 from esdrubal/tzrefactor
[mono.git] / mcs / class / corlib / System / TimeZoneInfo.cs
index 77ddf5b9c77129dc51fa4e9964e9e1c9c94a6517..0c17412242c835af7107d559a158ce9be1e25444 100644 (file)
@@ -45,14 +45,7 @@ using Microsoft.Win32;
 
 namespace System
 {
-#if MOBILE
-       [TypeForwardedFrom (Consts.AssemblySystem_Core)]
-#else
-       [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
-#endif
-       [SerializableAttribute]
-       public
-       sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
+       partial class TimeZoneInfo
        {
                TimeSpan baseUtcOffset;
                public TimeSpan BaseUtcOffset {
@@ -233,6 +226,18 @@ namespace System
                }
 #endif
 
+               private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
+               {
+                       var resultTicks = date.Ticks + ticks;
+                       if (resultTicks < DateTime.MinValue.Ticks || resultTicks > DateTime.MaxValue.Ticks) {
+                               result =  default (DateTime);
+                               return false;
+                       }
+
+                       result = new DateTime (resultTicks, kind);
+                       return true;
+               }
+
                public static void ClearCachedData ()
                {
                        local = null;
@@ -316,7 +321,11 @@ namespace System
 
                        var kind = (this == TimeZoneInfo.Local)? DateTimeKind.Local : DateTimeKind.Unspecified;
 
-                       return DateTime.SpecifyKind (dateTime + utcOffset, kind);
+                       DateTime result;
+                       if (!TryAddTicks (dateTime, utcOffset.Ticks, out result, kind))
+                               return DateTime.SpecifyKind (DateTime.MaxValue, kind);
+
+                       return result;
                }
 
                public static DateTime ConvertTimeFromUtc (DateTime dateTime, TimeZoneInfo destinationTimeZone)
@@ -367,7 +376,11 @@ namespace System
                        bool isDst;
                        var utcOffset = sourceTimeZone.GetUtcOffset (dateTime, out isDst);
 
-                       return DateTime.SpecifyKind (dateTime - utcOffset, DateTimeKind.Utc);
+                       DateTime utcDateTime;
+                       if (!TryAddTicks (dateTime, -utcOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
+                               return DateTime.SpecifyKind (DateTime.MinValue, DateTimeKind.Utc);
+
+                       return utcDateTime;
                }
 
                static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out Boolean isAmbiguousLocalDst)
@@ -712,12 +725,10 @@ namespace System
                                return tzOffset;
                        }
 
-                       var utcTicks = dateTime.Ticks - tzOffset.Ticks;
-                       if (utcTicks < 0 || utcTicks > DateTime.MaxValue.Ticks)
+                       DateTime utcDateTime;
+                       if (!TryAddTicks (dateTime, -tzOffset.Ticks, out utcDateTime, DateTimeKind.Utc))
                                return BaseUtcOffset;
 
-                       var utcDateTime = new DateTime (utcTicks, DateTimeKind.Utc);
-
                        return GetUtcOffsetHelper (utcDateTime, this, out isDST);
                }
 
@@ -746,20 +757,16 @@ namespace System
                                return tz.BaseUtcOffset;
                        }
 
-                       var stdTicks = dateTime.Ticks - tz.BaseUtcOffset.Ticks;
-                       if (stdTicks < 0 || stdTicks > DateTime.MaxValue.Ticks)
+                       DateTime stdUtcDateTime;
+                       if (!TryAddTicks (dateTime, -tz.BaseUtcOffset.Ticks, out stdUtcDateTime, DateTimeKind.Utc))
                                return tz.BaseUtcOffset;
 
-                       var stdUtcDateTime = new DateTime (stdTicks, DateTimeKind.Utc);
                        var tzRule = tz.GetApplicableRule (stdUtcDateTime);
 
                        DateTime dstUtcDateTime = DateTime.MinValue;
                        if (tzRule != null) {
-                               var dstTicks = stdUtcDateTime.Ticks - tzRule.DaylightDelta.Ticks;
-                               if (dstTicks < 0 || dstTicks > DateTime.MaxValue.Ticks)
+                               if (!TryAddTicks (stdUtcDateTime, -tzRule.DaylightDelta.Ticks, out dstUtcDateTime, DateTimeKind.Utc))
                                        return tz.BaseUtcOffset;
-
-                               dstUtcDateTime = new DateTime (dstTicks, DateTimeKind.Utc);
                        }
 
                        if (tzRule != null && tz.IsInDST (tzRule, stdUtcDateTime) && tz.IsInDST (tzRule, dstUtcDateTime)) {
@@ -873,6 +880,68 @@ namespace System
                        throw new NotImplementedException ();
                }
 
+               internal DaylightTime GetDaylightChanges (int year)
+               {
+                       DateTime start = DateTime.MinValue, end = DateTime.MinValue;
+                       TimeSpan delta = new TimeSpan ();
+
+                       if (transitions != null) {
+                               end = DateTime.MaxValue;
+                               for (var i =  transitions.Count - 1; i >= 0; i--) {
+                                       var pair = transitions [i];
+                                       DateTime ttime = pair.Key;
+                                       TimeType ttype = pair.Value;
+
+                                       if (ttype.IsDst) {
+                                               // DaylightTime.Delta is relative to the current BaseUtcOffset.
+                                               var d =  new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
+                                               // Handle DST gradients
+                                               if (start != DateTime.MinValue && delta != d)
+                                                       end = start;
+
+                                               start = ttime;
+                                               delta = d;
+
+                                               if (ttime.Year <= year)
+                                                       break;
+                                       } else {
+                                               if (ttime.Year < year)
+                                                       break;
+
+                                               end = ttime;
+                                               start = DateTime.MinValue;
+                                       }
+                               }
+
+                               // DaylightTime.Start is relative to the Standard time.
+                               if (start != DateTime.MinValue)
+                                       start += BaseUtcOffset;
+
+                               // DaylightTime.End is relative to the DST time.
+                               if (end != DateTime.MaxValue)
+                                       end += BaseUtcOffset + delta;
+                       } else {
+                               AdjustmentRule rule = null;
+                               foreach (var r in GetAdjustmentRules ()) {
+                                       if (r.DateEnd.Year < year)
+                                               continue;
+                                       if (r.DateStart.Year > year)
+                                               break;
+                                       rule = r;
+                               }
+                               if (rule != null) {
+                                       start = TransitionPoint (rule.DaylightTransitionStart, year);
+                                       end = TransitionPoint (rule.DaylightTransitionEnd, year);
+                                       delta = rule.DaylightDelta;
+                               }
+                       }
+
+                       if (start == DateTime.MinValue || end == DateTime.MinValue)
+                               return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
+
+                       return new DaylightTime (start, end, delta);
+               }
+
                public bool IsInvalidTime (DateTime dateTime)
                {
                        if (dateTime.Kind == DateTimeKind.Utc)
@@ -1020,10 +1089,13 @@ namespace System
                        //Applicable rules are in standard time
                        DateTime date = dateTime;
 
-                       if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local)
-                               date = date.ToUniversalTime () + BaseUtcOffset;
-                       else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
-                               date = date + BaseUtcOffset;
+                       if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
+                               if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date))
+                                       return null;
+                       } else if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc) {
+                               if (!TryAddTicks (date, BaseUtcOffset.Ticks, out date))
+                                       return null;
+                       }
 
                        // get the date component of the datetime
                        date = date.Date;
@@ -1052,19 +1124,13 @@ namespace System
                        DateTime date = dateTime;
 
                        if (dateTime.Kind == DateTimeKind.Local && this != TimeZoneInfo.Local) {
-                               var ticks = date.ToUniversalTime ().Ticks + BaseUtcOffset.Ticks;
-                               if (ticks < DateTime.MinValue.Ticks || ticks > DateTime.MaxValue.Ticks)
+                               if (!TryAddTicks (date.ToUniversalTime (), BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
                                        return false;
-
-                               date = new DateTime (ticks, DateTimeKind.Utc);
                        }
 
                        if (dateTime.Kind != DateTimeKind.Utc) {
-                               var ticks = date.Ticks - BaseUtcOffset.Ticks;
-                               if (ticks < DateTime.MinValue.Ticks || ticks > DateTime.MaxValue.Ticks)
+                               if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
                                        return false;
-
-                               date = new DateTime (ticks, DateTimeKind.Utc);
                        }
 
                        for (var i =  transitions.Count - 1; i >= 0; i--) {
@@ -1200,6 +1266,14 @@ namespace System
                                                if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
                                                        dst_end -= new TimeSpan (24, 0, 0);
 
+                                               /*
+                                                * AdjustmentRule specifies a DST period that starts and ends within a year.
+                                                * When we have a DST period longer than a year, the generated AdjustmentRule may not be usable.
+                                                * Thus we fallback to the transitions.
+                                                */
+                                               if (dst_start.AddYears (1) < dst_end)
+                                                       storeTransition = true;
+
                                                DateTime dateStart, dateEnd;
                                                if (dst_start.Month < 7)
                                                        dateStart = new DateTime (dst_start.Year, 1, 1);
@@ -1221,8 +1295,12 @@ namespace System
                                } else {
                                        if (daylightDisplayName != ttype.Name)
                                                daylightDisplayName = ttype.Name;
-                                       if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
-                                               dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
+                                       if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
+                                               // Round to nearest minute, since it's not possible to create an adjustment rule
+                                               // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
+                                               // This happens with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
+                                               dstDelta = new TimeSpan (0, 0, ttype.Offset - ttype.Offset % 60) - baseUtcOffset;
+                                       }
 
                                        dst_start = ttime;
                                        dst_observed = true;