1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Runtime
.Serialization
;
9 public sealed partial class TimeZoneInfo
12 public sealed class AdjustmentRule
:
13 #nullable disable // see comment on String
14 IEquatable
<AdjustmentRule
>,
16 ISerializable
, IDeserializationCallback
18 private static readonly TimeSpan DaylightDeltaAdjustment
= TimeSpan
.FromHours(24.0);
19 private static readonly TimeSpan MaxDaylightDelta
= TimeSpan
.FromHours(12.0);
20 private readonly DateTime _dateStart
;
21 private readonly DateTime _dateEnd
;
22 private readonly TimeSpan _daylightDelta
;
23 private readonly TransitionTime _daylightTransitionStart
;
24 private readonly TransitionTime _daylightTransitionEnd
;
25 private readonly TimeSpan _baseUtcOffsetDelta
; // delta from the default Utc offset (utcOffset = defaultUtcOffset + _baseUtcOffsetDelta)
26 private readonly bool _noDaylightTransitions
;
28 public DateTime DateStart
=> _dateStart
;
30 public DateTime DateEnd
=> _dateEnd
;
32 public TimeSpan DaylightDelta
=> _daylightDelta
;
34 public TransitionTime DaylightTransitionStart
=> _daylightTransitionStart
;
36 public TransitionTime DaylightTransitionEnd
=> _daylightTransitionEnd
;
38 internal TimeSpan BaseUtcOffsetDelta
=> _baseUtcOffsetDelta
;
41 /// Gets a value indicating that this AdjustmentRule fixes the time zone offset
42 /// from DateStart to DateEnd without any daylight transitions in between.
44 internal bool NoDaylightTransitions
=> _noDaylightTransitions
;
46 internal bool HasDaylightSaving
=>
47 DaylightDelta
!= TimeSpan
.Zero
||
48 (DaylightTransitionStart
!= default && DaylightTransitionStart
.TimeOfDay
!= DateTime
.MinValue
) ||
49 (DaylightTransitionEnd
!= default && DaylightTransitionEnd
.TimeOfDay
!= DateTime
.MinValue
.AddMilliseconds(1));
51 public bool Equals(AdjustmentRule
? other
) =>
53 _dateStart
== other
._dateStart
&&
54 _dateEnd
== other
._dateEnd
&&
55 _daylightDelta
== other
._daylightDelta
&&
56 _baseUtcOffsetDelta
== other
._baseUtcOffsetDelta
&&
57 _daylightTransitionEnd
.Equals(other
._daylightTransitionEnd
) &&
58 _daylightTransitionStart
.Equals(other
._daylightTransitionStart
);
60 public override int GetHashCode() => _dateStart
.GetHashCode();
62 private AdjustmentRule(
65 TimeSpan daylightDelta
,
66 TransitionTime daylightTransitionStart
,
67 TransitionTime daylightTransitionEnd
,
68 TimeSpan baseUtcOffsetDelta
,
69 bool noDaylightTransitions
)
71 ValidateAdjustmentRule(dateStart
, dateEnd
, daylightDelta
,
72 daylightTransitionStart
, daylightTransitionEnd
, noDaylightTransitions
);
74 _dateStart
= dateStart
;
76 _daylightDelta
= daylightDelta
;
77 _daylightTransitionStart
= daylightTransitionStart
;
78 _daylightTransitionEnd
= daylightTransitionEnd
;
79 _baseUtcOffsetDelta
= baseUtcOffsetDelta
;
80 _noDaylightTransitions
= noDaylightTransitions
;
83 public static AdjustmentRule
CreateAdjustmentRule(
86 TimeSpan daylightDelta
,
87 TransitionTime daylightTransitionStart
,
88 TransitionTime daylightTransitionEnd
)
90 return new AdjustmentRule(
94 daylightTransitionStart
,
95 daylightTransitionEnd
,
96 baseUtcOffsetDelta: TimeSpan
.Zero
,
97 noDaylightTransitions: false);
100 internal static AdjustmentRule
CreateAdjustmentRule(
103 TimeSpan daylightDelta
,
104 TransitionTime daylightTransitionStart
,
105 TransitionTime daylightTransitionEnd
,
106 TimeSpan baseUtcOffsetDelta
,
107 bool noDaylightTransitions
)
109 AdjustDaylightDeltaToExpectedRange(ref daylightDelta
, ref baseUtcOffsetDelta
);
110 return new AdjustmentRule(
114 daylightTransitionStart
,
115 daylightTransitionEnd
,
117 noDaylightTransitions
);
121 // When Windows sets the daylight transition start Jan 1st at 12:00 AM, it means the year starts with the daylight saving on.
122 // We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
124 internal bool IsStartDateMarkerForBeginningOfYear() =>
125 !NoDaylightTransitions
&&
126 DaylightTransitionStart
.Month
== 1 && DaylightTransitionStart
.Day
== 1 && DaylightTransitionStart
.TimeOfDay
.Hour
== 0 &&
127 DaylightTransitionStart
.TimeOfDay
.Minute
== 0 && DaylightTransitionStart
.TimeOfDay
.Second
== 0 &&
128 _dateStart
.Year
== _dateEnd
.Year
;
131 // When Windows sets the daylight transition end Jan 1st at 12:00 AM, it means the year ends with the daylight saving on.
132 // We have to special case this value and not adjust it when checking if any date is in the daylight saving period.
134 internal bool IsEndDateMarkerForEndOfYear() =>
135 !NoDaylightTransitions
&&
136 DaylightTransitionEnd
.Month
== 1 && DaylightTransitionEnd
.Day
== 1 && DaylightTransitionEnd
.TimeOfDay
.Hour
== 0 &&
137 DaylightTransitionEnd
.TimeOfDay
.Minute
== 0 && DaylightTransitionEnd
.TimeOfDay
.Second
== 0 &&
138 _dateStart
.Year
== _dateEnd
.Year
;
141 /// Helper function that performs all of the validation checks for the factory methods and deserialization callback.
143 private static void ValidateAdjustmentRule(
146 TimeSpan daylightDelta
,
147 TransitionTime daylightTransitionStart
,
148 TransitionTime daylightTransitionEnd
,
149 bool noDaylightTransitions
)
151 if (dateStart
.Kind
!= DateTimeKind
.Unspecified
&& dateStart
.Kind
!= DateTimeKind
.Utc
)
153 throw new ArgumentException(SR
.Argument_DateTimeKindMustBeUnspecifiedOrUtc
, nameof(dateStart
));
156 if (dateEnd
.Kind
!= DateTimeKind
.Unspecified
&& dateEnd
.Kind
!= DateTimeKind
.Utc
)
158 throw new ArgumentException(SR
.Argument_DateTimeKindMustBeUnspecifiedOrUtc
, nameof(dateEnd
));
161 if (daylightTransitionStart
.Equals(daylightTransitionEnd
) && !noDaylightTransitions
)
163 throw new ArgumentException(SR
.Argument_TransitionTimesAreIdentical
, nameof(daylightTransitionEnd
));
166 if (dateStart
> dateEnd
)
168 throw new ArgumentException(SR
.Argument_OutOfOrderDateTimes
, nameof(dateStart
));
171 // This cannot use UtcOffsetOutOfRange to account for the scenario where Samoa moved across the International Date Line,
172 // which caused their current BaseUtcOffset to be +13. But on the other side of the line it was UTC-11 (+1 for daylight).
173 // So when trying to describe DaylightDeltas for those times, the DaylightDelta needs
174 // to be -23 (what it takes to go from UTC+13 to UTC-10)
175 if (daylightDelta
.TotalHours
< -23.0 || daylightDelta
.TotalHours
> 14.0)
177 throw new ArgumentOutOfRangeException(nameof(daylightDelta
), daylightDelta
, SR
.ArgumentOutOfRange_UtcOffset
);
180 if (daylightDelta
.Ticks
% TimeSpan
.TicksPerMinute
!= 0)
182 throw new ArgumentException(SR
.Argument_TimeSpanHasSeconds
, nameof(daylightDelta
));
185 if (dateStart
!= DateTime
.MinValue
&& dateStart
.Kind
== DateTimeKind
.Unspecified
&& dateStart
.TimeOfDay
!= TimeSpan
.Zero
)
187 throw new ArgumentException(SR
.Argument_DateTimeHasTimeOfDay
, nameof(dateStart
));
190 if (dateEnd
!= DateTime
.MaxValue
&& dateEnd
.Kind
== DateTimeKind
.Unspecified
&& dateEnd
.TimeOfDay
!= TimeSpan
.Zero
)
192 throw new ArgumentException(SR
.Argument_DateTimeHasTimeOfDay
, nameof(dateEnd
));
197 /// Ensures the daylight delta is within [-12, 12] hours
199 private static void AdjustDaylightDeltaToExpectedRange(ref TimeSpan daylightDelta
, ref TimeSpan baseUtcOffsetDelta
)
201 if (daylightDelta
> MaxDaylightDelta
)
203 daylightDelta
-= DaylightDeltaAdjustment
;
204 baseUtcOffsetDelta
+= DaylightDeltaAdjustment
;
206 else if (daylightDelta
< -MaxDaylightDelta
)
208 daylightDelta
+= DaylightDeltaAdjustment
;
209 baseUtcOffsetDelta
-= DaylightDeltaAdjustment
;
212 System
.Diagnostics
.Debug
.Assert(daylightDelta
<= MaxDaylightDelta
&& daylightDelta
>= -MaxDaylightDelta
,
213 "DaylightDelta should not ever be more than 24h");
216 void IDeserializationCallback
.OnDeserialization(object? sender
)
218 // OnDeserialization is called after each instance of this class is deserialized.
219 // This callback method performs AdjustmentRule validation after being deserialized.
223 ValidateAdjustmentRule(_dateStart
, _dateEnd
, _daylightDelta
,
224 _daylightTransitionStart
, _daylightTransitionEnd
, _noDaylightTransitions
);
226 catch (ArgumentException e
)
228 throw new SerializationException(SR
.Serialization_InvalidData
, e
);
232 void ISerializable
.GetObjectData(SerializationInfo info
, StreamingContext context
)
236 throw new ArgumentNullException(nameof(info
));
239 info
.AddValue("DateStart", _dateStart
); // Do not rename (binary serialization)
240 info
.AddValue("DateEnd", _dateEnd
); // Do not rename (binary serialization)
241 info
.AddValue("DaylightDelta", _daylightDelta
); // Do not rename (binary serialization)
242 info
.AddValue("DaylightTransitionStart", _daylightTransitionStart
); // Do not rename (binary serialization)
243 info
.AddValue("DaylightTransitionEnd", _daylightTransitionEnd
); // Do not rename (binary serialization)
244 info
.AddValue("BaseUtcOffsetDelta", _baseUtcOffsetDelta
); // Do not rename (binary serialization)
245 info
.AddValue("NoDaylightTransitions", _noDaylightTransitions
); // Do not rename (binary serialization)
248 private AdjustmentRule(SerializationInfo info
, StreamingContext context
)
252 throw new ArgumentNullException(nameof(info
));
255 _dateStart
= (DateTime
)info
.GetValue("DateStart", typeof(DateTime
))!; // Do not rename (binary serialization)
256 _dateEnd
= (DateTime
)info
.GetValue("DateEnd", typeof(DateTime
))!; // Do not rename (binary serialization)
257 _daylightDelta
= (TimeSpan
)info
.GetValue("DaylightDelta", typeof(TimeSpan
))!; // Do not rename (binary serialization)
258 _daylightTransitionStart
= (TransitionTime
)info
.GetValue("DaylightTransitionStart", typeof(TransitionTime
))!; // Do not rename (binary serialization)
259 _daylightTransitionEnd
= (TransitionTime
)info
.GetValue("DaylightTransitionEnd", typeof(TransitionTime
))!; // Do not rename (binary serialization)
261 object? o
= info
.GetValueNoThrow("BaseUtcOffsetDelta", typeof(TimeSpan
)); // Do not rename (binary serialization)
264 _baseUtcOffsetDelta
= (TimeSpan
)o
;
267 o
= info
.GetValueNoThrow("NoDaylightTransitions", typeof(bool)); // Do not rename (binary serialization)
270 _noDaylightTransitions
= (bool)o
;