diff options
Diffstat (limited to 'src/mscorlib/src/System/TimeZoneInfo.AdjustmentRule.cs')
-rw-r--r-- | src/mscorlib/src/System/TimeZoneInfo.AdjustmentRule.cs | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/TimeZoneInfo.AdjustmentRule.cs b/src/mscorlib/src/System/TimeZoneInfo.AdjustmentRule.cs new file mode 100644 index 0000000000..d27d2386e2 --- /dev/null +++ b/src/mscorlib/src/System/TimeZoneInfo.AdjustmentRule.cs @@ -0,0 +1,251 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Contracts; +using System.Runtime.Serialization; + +namespace System +{ + public sealed partial class TimeZoneInfo + { + [Serializable] + public sealed class AdjustmentRule : IEquatable<AdjustmentRule>, ISerializable, IDeserializationCallback + { + private readonly DateTime _dateStart; + private readonly DateTime _dateEnd; + private readonly TimeSpan _daylightDelta; + private readonly TransitionTime _daylightTransitionStart; + private readonly TransitionTime _daylightTransitionEnd; + private readonly TimeSpan _baseUtcOffsetDelta; // delta from the default Utc offset (utcOffset = defaultUtcOffset + _baseUtcOffsetDelta) + private readonly bool _noDaylightTransitions; + + public DateTime DateStart => _dateStart; + + public DateTime DateEnd => _dateEnd; + + public TimeSpan DaylightDelta => _daylightDelta; + + public TransitionTime DaylightTransitionStart => _daylightTransitionStart; + + public TransitionTime DaylightTransitionEnd => _daylightTransitionEnd; + + internal TimeSpan BaseUtcOffsetDelta => _baseUtcOffsetDelta; + + /// <summary> + /// Gets a value indicating that this AdjustmentRule fixes the time zone offset + /// from DateStart to DateEnd without any daylight transitions in between. + /// </summary> + internal bool NoDaylightTransitions => _noDaylightTransitions; + + internal bool HasDaylightSaving => + DaylightDelta != TimeSpan.Zero || + (DaylightTransitionStart != default(TransitionTime) && DaylightTransitionStart.TimeOfDay != DateTime.MinValue) || + (DaylightTransitionEnd != default(TransitionTime) && DaylightTransitionEnd.TimeOfDay != DateTime.MinValue.AddMilliseconds(1)); + + public bool Equals(AdjustmentRule other) => + other != null && + _dateStart == other._dateStart && + _dateEnd == other._dateEnd && + _daylightDelta == other._daylightDelta && + _baseUtcOffsetDelta == other._baseUtcOffsetDelta && + _daylightTransitionEnd.Equals(other._daylightTransitionEnd) && + _daylightTransitionStart.Equals(other._daylightTransitionStart); + + public override int GetHashCode() => _dateStart.GetHashCode(); + + private AdjustmentRule( + DateTime dateStart, + DateTime dateEnd, + TimeSpan daylightDelta, + TransitionTime daylightTransitionStart, + TransitionTime daylightTransitionEnd, + TimeSpan baseUtcOffsetDelta, + bool noDaylightTransitions) + { + ValidateAdjustmentRule(dateStart, dateEnd, daylightDelta, + daylightTransitionStart, daylightTransitionEnd, noDaylightTransitions); + + _dateStart = dateStart; + _dateEnd = dateEnd; + _daylightDelta = daylightDelta; + _daylightTransitionStart = daylightTransitionStart; + _daylightTransitionEnd = daylightTransitionEnd; + _baseUtcOffsetDelta = baseUtcOffsetDelta; + _noDaylightTransitions = noDaylightTransitions; + } + + public static AdjustmentRule CreateAdjustmentRule( + DateTime dateStart, + DateTime dateEnd, + TimeSpan daylightDelta, + TransitionTime daylightTransitionStart, + TransitionTime daylightTransitionEnd) + { + return new AdjustmentRule( + dateStart, + dateEnd, + daylightDelta, + daylightTransitionStart, + daylightTransitionEnd, + baseUtcOffsetDelta: TimeSpan.Zero, + noDaylightTransitions: false); + } + + internal static AdjustmentRule CreateAdjustmentRule( + DateTime dateStart, + DateTime dateEnd, + TimeSpan daylightDelta, + TransitionTime daylightTransitionStart, + TransitionTime daylightTransitionEnd, + TimeSpan baseUtcOffsetDelta, + bool noDaylightTransitions) + { + return new AdjustmentRule( + dateStart, + dateEnd, + daylightDelta, + daylightTransitionStart, + daylightTransitionEnd, + baseUtcOffsetDelta, + noDaylightTransitions); + } + + // + // When Windows sets the daylight transition start Jan 1st at 12:00 AM, it means the year starts with the daylight saving on. + // We have to special case this value and not adjust it when checking if any date is in the daylight saving period. + // + internal bool IsStartDateMarkerForBeginningOfYear() => + !NoDaylightTransitions && + DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 && + DaylightTransitionStart.TimeOfDay.Minute == 0 && DaylightTransitionStart.TimeOfDay.Second == 0 && + _dateStart.Year == _dateEnd.Year; + + // + // When Windows sets the daylight transition end Jan 1st at 12:00 AM, it means the year ends with the daylight saving on. + // We have to special case this value and not adjust it when checking if any date is in the daylight saving period. + // + internal bool IsEndDateMarkerForEndOfYear() => + !NoDaylightTransitions && + DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 && + DaylightTransitionEnd.TimeOfDay.Minute == 0 && DaylightTransitionEnd.TimeOfDay.Second == 0 && + _dateStart.Year == _dateEnd.Year; + + /// <summary> + /// Helper function that performs all of the validation checks for the actory methods and deserialization callback. + /// </summary> + private static void ValidateAdjustmentRule( + DateTime dateStart, + DateTime dateEnd, + TimeSpan daylightDelta, + TransitionTime daylightTransitionStart, + TransitionTime daylightTransitionEnd, + bool noDaylightTransitions) + { + if (dateStart.Kind != DateTimeKind.Unspecified && dateStart.Kind != DateTimeKind.Utc) + { + throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecifiedOrUtc"), nameof(dateStart)); + } + + if (dateEnd.Kind != DateTimeKind.Unspecified && dateEnd.Kind != DateTimeKind.Utc) + { + throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeKindMustBeUnspecifiedOrUtc"), nameof(dateEnd)); + } + + if (daylightTransitionStart.Equals(daylightTransitionEnd) && !noDaylightTransitions) + { + throw new ArgumentException(Environment.GetResourceString("Argument_TransitionTimesAreIdentical"), nameof(daylightTransitionEnd)); + } + + if (dateStart > dateEnd) + { + throw new ArgumentException(Environment.GetResourceString("Argument_OutOfOrderDateTimes"), nameof(dateStart)); + } + + // This cannot use UtcOffsetOutOfRange to account for the scenario where Samoa moved across the International Date Line, + // which caused their current BaseUtcOffset to be +13. But on the other side of the line it was UTC-11 (+1 for daylight). + // So when trying to describe DaylightDeltas for those times, the DaylightDelta needs + // to be -23 (what it takes to go from UTC+13 to UTC-10) + if (daylightDelta.TotalHours < -23.0 || daylightDelta.TotalHours > 14.0) + { + throw new ArgumentOutOfRangeException(nameof(daylightDelta), daylightDelta, Environment.GetResourceString("ArgumentOutOfRange_UtcOffset")); + } + + if (daylightDelta.Ticks % TimeSpan.TicksPerMinute != 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_TimeSpanHasSeconds"), nameof(daylightDelta)); + } + + if (dateStart != DateTime.MinValue && dateStart.Kind == DateTimeKind.Unspecified && dateStart.TimeOfDay != TimeSpan.Zero) + { + throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeHasTimeOfDay"), nameof(dateStart)); + } + + if (dateEnd != DateTime.MaxValue && dateEnd.Kind == DateTimeKind.Unspecified && dateEnd.TimeOfDay != TimeSpan.Zero) + { + throw new ArgumentException(Environment.GetResourceString("Argument_DateTimeHasTimeOfDay"), nameof(dateEnd)); + } + Contract.EndContractBlock(); + } + + void IDeserializationCallback.OnDeserialization(object sender) + { + // OnDeserialization is called after each instance of this class is deserialized. + // This callback method performs AdjustmentRule validation after being deserialized. + + try + { + ValidateAdjustmentRule(_dateStart, _dateEnd, _daylightDelta, + _daylightTransitionStart, _daylightTransitionEnd, _noDaylightTransitions); + } + catch (ArgumentException e) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e); + } + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + Contract.EndContractBlock(); + + info.AddValue("DateStart", _dateStart); + info.AddValue("DateEnd", _dateEnd); + info.AddValue("DaylightDelta", _daylightDelta); + info.AddValue("DaylightTransitionStart", _daylightTransitionStart); + info.AddValue("DaylightTransitionEnd", _daylightTransitionEnd); + info.AddValue("BaseUtcOffsetDelta", _baseUtcOffsetDelta); + info.AddValue("NoDaylightTransitions", _noDaylightTransitions); + } + + AdjustmentRule(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + _dateStart = (DateTime)info.GetValue("DateStart", typeof(DateTime)); + _dateEnd = (DateTime)info.GetValue("DateEnd", typeof(DateTime)); + _daylightDelta = (TimeSpan)info.GetValue("DaylightDelta", typeof(TimeSpan)); + _daylightTransitionStart = (TransitionTime)info.GetValue("DaylightTransitionStart", typeof(TransitionTime)); + _daylightTransitionEnd = (TransitionTime)info.GetValue("DaylightTransitionEnd", typeof(TransitionTime)); + + object o = info.GetValueNoThrow("BaseUtcOffsetDelta", typeof(TimeSpan)); + if (o != null) + { + _baseUtcOffsetDelta = (TimeSpan)o; + } + + o = info.GetValueNoThrow("NoDaylightTransitions", typeof(bool)); + if (o != null) + { + _noDaylightTransitions = (bool)o; + } + } + } + } +} |