@@ -805,7 +805,7 @@ public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
805805 return GetUtcOffset ( dateTimeOffset . UtcDateTime , out isDST ) ;
806806 }
807807
808- private TimeSpan GetUtcOffset ( DateTime dateTime , out bool isDST )
808+ private TimeSpan GetUtcOffset ( DateTime dateTime , out bool isDST , bool forOffset = false )
809809 {
810810 isDST = false ;
811811
@@ -817,7 +817,7 @@ private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
817817 tz = TimeZoneInfo . Local ;
818818
819819 bool isTzDst ;
820- var tzOffset = GetUtcOffsetHelper ( dateTime , tz , out isTzDst ) ;
820+ var tzOffset = GetUtcOffsetHelper ( dateTime , tz , out isTzDst , forOffset ) ;
821821
822822 if ( tz == this ) {
823823 isDST = isTzDst ;
@@ -828,11 +828,11 @@ private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
828828 if ( ! TryAddTicks ( dateTime , - tzOffset . Ticks , out utcDateTime , DateTimeKind . Utc ) )
829829 return BaseUtcOffset ;
830830
831- return GetUtcOffsetHelper ( utcDateTime , this , out isDST ) ;
831+ return GetUtcOffsetHelper ( utcDateTime , this , out isDST , forOffset ) ;
832832 }
833833
834834 // This is an helper method used by the method above, do not use this on its own.
835- private static TimeSpan GetUtcOffsetHelper ( DateTime dateTime , TimeZoneInfo tz , out bool isDST )
835+ private static TimeSpan GetUtcOffsetHelper ( DateTime dateTime , TimeZoneInfo tz , out bool isDST , bool forOffset = false )
836836 {
837837 if ( dateTime . Kind == DateTimeKind . Local && tz != TimeZoneInfo . Local )
838838 throw new Exception ( ) ;
@@ -843,7 +843,7 @@ private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz,
843843 return TimeSpan . Zero ;
844844
845845 TimeSpan offset ;
846- if ( tz . TryGetTransitionOffset ( dateTime , out offset , out isDST ) )
846+ if ( tz . TryGetTransitionOffset ( dateTime , out offset , out isDST , forOffset ) )
847847 return offset ;
848848
849849 if ( dateTime . Kind == DateTimeKind . Utc ) {
@@ -870,10 +870,12 @@ private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz,
870870
871871 if ( tzRule != null && tz . IsInDST ( tzRule , dateTime ) ) {
872872 // Replicate what .NET does when given a time which falls into the hour which is lost when
873- // DST starts. isDST should always be true but the offset should be BaseUtcOffset without the
873+ // DST starts. isDST should be false and the offset should be BaseUtcOffset without the
874874 // DST delta while in that hour.
875- isDST = true ;
875+ if ( forOffset )
876+ isDST = true ;
876877 if ( tz . IsInDST ( tzRule , dstUtcDateTime ) ) {
878+ isDST = true ;
877879 return tz . BaseUtcOffset + tzRule . DaylightDelta ;
878880 } else {
879881 return tz . BaseUtcOffset ;
@@ -925,7 +927,33 @@ public bool IsAmbiguousTime (DateTime dateTime)
925927 AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
926928 if ( rule != null ) {
927929 DateTime tpoint = TransitionPoint ( rule . DaylightTransitionEnd , dateTime . Year ) ;
928- if ( dateTime > tpoint - rule . DaylightDelta && dateTime <= tpoint )
930+ if ( dateTime >= tpoint - rule . DaylightDelta && dateTime < tpoint )
931+ return true ;
932+ }
933+
934+ return false ;
935+ }
936+
937+ private bool IsAmbiguousLocalDstFromUtc ( DateTime dateTime )
938+ {
939+ // This method determines if a dateTime in UTC falls into the Dst side
940+ // of the ambiguous local time (the local time that occurs twice).
941+
942+ if ( dateTime . Kind == DateTimeKind . Local )
943+ return false ;
944+
945+ if ( this == TimeZoneInfo . Utc )
946+ return false ;
947+
948+ AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
949+ if ( rule != null ) {
950+ DateTime tpoint = TransitionPoint ( rule . DaylightTransitionEnd , dateTime . Year ) ;
951+ // tpoint is the local time in daylight savings time when daylight savings time will end, convert it to UTC
952+ DateTime tpointUtc ;
953+ if ( ! TryAddTicks ( tpoint , - ( BaseUtcOffset . Ticks + rule . DaylightDelta . Ticks ) , out tpointUtc , DateTimeKind . Utc ) )
954+ return false ;
955+
956+ if ( dateTime >= tpointUtc - rule . DaylightDelta && dateTime < tpointUtc )
929957 return true ;
930958 }
931959
@@ -944,7 +972,18 @@ private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
944972 return true ;
945973
946974 // We might be in the dateTime previous year's DST period
947- return dateTime . Year > 1 && IsInDSTForYear ( rule , dateTime , dateTime . Year - 1 ) ;
975+ if ( dateTime . Year > 1 && IsInDSTForYear ( rule , dateTime , dateTime . Year - 1 ) )
976+ return true ;
977+
978+ // If we are checking an ambiguous local time, that is the local time that occurs twice during a DST "fall back"
979+ // check if it was marked as being in the DST side of the ambiguous time when it was created
980+ // We need to re-check IsAmbiguousTime because the IsAmbiguousDaylightSavingTime flag is not cleared when using DateTime.Add/Subtract
981+ if ( dateTime . Kind == DateTimeKind . Local && IsAmbiguousTime ( dateTime ) )
982+ {
983+ return dateTime . IsAmbiguousDaylightSavingTime ( ) ;
984+ }
985+
986+ return false ;
948987 }
949988
950989 bool IsInDSTForYear ( AdjustmentRule rule , DateTime dateTime , int year )
@@ -953,8 +992,9 @@ bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
953992 DateTime DST_end = TransitionPoint ( rule . DaylightTransitionEnd , year + ( ( rule . DaylightTransitionStart . Month < rule . DaylightTransitionEnd . Month ) ? 0 : 1 ) ) ;
954993 if ( dateTime . Kind == DateTimeKind . Utc ) {
955994 DST_start -= BaseUtcOffset ;
956- DST_end -= ( BaseUtcOffset + rule . DaylightDelta ) ;
995+ DST_end -= BaseUtcOffset ;
957996 }
997+ DST_end -= rule . DaylightDelta ;
958998 return ( dateTime >= DST_start && dateTime < DST_end ) ;
959999 }
9601000
@@ -982,7 +1022,21 @@ internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags
9821022
9831023 public bool IsDaylightSavingTime ( DateTimeOffset dateTimeOffset )
9841024 {
985- return IsDaylightSavingTime ( dateTimeOffset . DateTime ) ;
1025+ var dateTime = dateTimeOffset . DateTime ;
1026+
1027+ if ( dateTime . Kind == DateTimeKind . Local && IsInvalidTime ( dateTime ) )
1028+ throw new ArgumentException ( "dateTime is invalid and Kind is Local" ) ;
1029+
1030+ if ( this == TimeZoneInfo . Utc )
1031+ return false ;
1032+
1033+ if ( ! SupportsDaylightSavingTime )
1034+ return false ;
1035+
1036+ bool isDst ;
1037+ GetUtcOffset ( dateTime , out isDst , true ) ;
1038+
1039+ return isDst ;
9861040 }
9871041
9881042 internal DaylightTime GetDaylightChanges ( int year )
@@ -1219,7 +1273,7 @@ private AdjustmentRule GetApplicableRule (DateTime dateTime)
12191273 return null ;
12201274 }
12211275
1222- private bool TryGetTransitionOffset ( DateTime dateTime , out TimeSpan offset , out bool isDst )
1276+ private bool TryGetTransitionOffset ( DateTime dateTime , out TimeSpan offset , out bool isDst , bool forOffset = false )
12231277 {
12241278 offset = BaseUtcOffset ;
12251279 isDst = false ;
@@ -1235,18 +1289,45 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out
12351289 return false ;
12361290 }
12371291
1292+ var isUtc = false ;
12381293 if ( dateTime . Kind != DateTimeKind . Utc ) {
12391294 if ( ! TryAddTicks ( date , - BaseUtcOffset . Ticks , out date , DateTimeKind . Utc ) )
12401295 return false ;
1241- }
1296+ } else
1297+ isUtc = true ;
1298+
12421299
1243- AdjustmentRule current = GetApplicableRule ( date ) ;
1300+ AdjustmentRule current = GetApplicableRule ( date ) ;
12441301 if ( current != null ) {
1245- DateTime tStart = TransitionPoint ( current . DaylightTransitionStart , date . Year ) ;
1246- DateTime tEnd = TransitionPoint ( current . DaylightTransitionEnd , date . Year ) ;
1302+ DateTime tStart = TransitionPoint ( current . DaylightTransitionStart , date . Year ) ;
1303+ DateTime tEnd = TransitionPoint ( current . DaylightTransitionEnd , date . Year ) ;
1304+ TryAddTicks ( tStart , - BaseUtcOffset . Ticks , out tStart , DateTimeKind . Utc ) ;
1305+ TryAddTicks ( tEnd , - BaseUtcOffset . Ticks , out tEnd , DateTimeKind . Utc ) ;
12471306 if ( ( date >= tStart ) && ( date <= tEnd ) ) {
1248- offset = baseUtcOffset + current . DaylightDelta ;
1249- isDst = true ;
1307+ if ( forOffset )
1308+ isDst = true ;
1309+ offset = baseUtcOffset ;
1310+ if ( isUtc || ( date >= new DateTime ( tStart . Ticks + current . DaylightDelta . Ticks , DateTimeKind . Utc ) ) )
1311+ {
1312+ offset += current . DaylightDelta ;
1313+ isDst = true ;
1314+ }
1315+
1316+ if ( date >= new DateTime ( tEnd . Ticks - current . DaylightDelta . Ticks , DateTimeKind . Utc ) )
1317+ {
1318+ offset = baseUtcOffset ;
1319+ isDst = false ;
1320+ }
1321+
1322+ // If we are checking an ambiguous local time, that is the local time that occurs twice during a DST "fall back"
1323+ // check if it was marked as being in the DST side of the ambiguous time when it was created
1324+ // We need to re-check IsAmbiguousTime because the IsAmbiguousDaylightSavingTime flag is not cleared when using DateTime.Add/Subtract
1325+ if ( ! isDst && dateTime . Kind == DateTimeKind . Local && IsAmbiguousTime ( dateTime ) && dateTime . IsAmbiguousDaylightSavingTime ( ) )
1326+ {
1327+ offset += current . DaylightDelta ;
1328+ isDst = true ;
1329+ }
1330+
12501331 return true ;
12511332 }
12521333 }
@@ -1255,8 +1336,11 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out
12551336
12561337 private static DateTime TransitionPoint ( TransitionTime transition , int year )
12571338 {
1258- if ( transition . IsFixedDateRule )
1259- return new DateTime ( year , transition . Month , transition . Day ) + transition . TimeOfDay . TimeOfDay ;
1339+ if ( transition . IsFixedDateRule ) {
1340+ var daysInMonth = DateTime . DaysInMonth ( year , transition . Month ) ;
1341+ var transitionDay = transition . Day <= daysInMonth ? transition . Day : daysInMonth ;
1342+ return new DateTime ( year , transition . Month , transitionDay ) + transition . TimeOfDay . TimeOfDay ;
1343+ }
12601344
12611345 DayOfWeek first = ( new DateTime ( year , transition . Month , 1 ) ) . DayOfWeek ;
12621346 int day = 1 + ( transition . Week - 1 ) * 7 + ( transition . DayOfWeek - first + 7 ) % 7 ;
@@ -1540,7 +1624,7 @@ static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone,
15401624 isAmbiguousLocalDst = false ;
15411625 TimeSpan baseOffset = zone . BaseUtcOffset ;
15421626
1543- if ( zone . IsAmbiguousTime ( time ) ) {
1627+ if ( zone . IsAmbiguousLocalDstFromUtc ( time ) ) {
15441628 isAmbiguousLocalDst = true ;
15451629// return baseOffset;
15461630 }
@@ -1570,4 +1654,4 @@ public override string ToString ()
15701654 }
15711655#endif
15721656 }
1573- }
1657+ }
0 commit comments