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 {
}
#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;
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)
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)
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);
}
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)) {
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)
//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;
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--) {
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);
} 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;