@@ -805,7 +805,7 @@ public TimeSpan GetUtcOffset (DateTimeOffset dateTimeOffset)
805
805
return GetUtcOffset ( dateTimeOffset . UtcDateTime , out isDST ) ;
806
806
}
807
807
808
- private TimeSpan GetUtcOffset ( DateTime dateTime , out bool isDST )
808
+ private TimeSpan GetUtcOffset ( DateTime dateTime , out bool isDST , bool forOffset = false )
809
809
{
810
810
isDST = false ;
811
811
@@ -817,7 +817,7 @@ private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
817
817
tz = TimeZoneInfo . Local ;
818
818
819
819
bool isTzDst ;
820
- var tzOffset = GetUtcOffsetHelper ( dateTime , tz , out isTzDst ) ;
820
+ var tzOffset = GetUtcOffsetHelper ( dateTime , tz , out isTzDst , forOffset ) ;
821
821
822
822
if ( tz == this ) {
823
823
isDST = isTzDst ;
@@ -828,11 +828,11 @@ private TimeSpan GetUtcOffset (DateTime dateTime, out bool isDST)
828
828
if ( ! TryAddTicks ( dateTime , - tzOffset . Ticks , out utcDateTime , DateTimeKind . Utc ) )
829
829
return BaseUtcOffset ;
830
830
831
- return GetUtcOffsetHelper ( utcDateTime , this , out isDST ) ;
831
+ return GetUtcOffsetHelper ( utcDateTime , this , out isDST , forOffset ) ;
832
832
}
833
833
834
834
// 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 )
836
836
{
837
837
if ( dateTime . Kind == DateTimeKind . Local && tz != TimeZoneInfo . Local )
838
838
throw new Exception ( ) ;
@@ -843,7 +843,7 @@ private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz,
843
843
return TimeSpan . Zero ;
844
844
845
845
TimeSpan offset ;
846
- if ( tz . TryGetTransitionOffset ( dateTime , out offset , out isDST ) )
846
+ if ( tz . TryGetTransitionOffset ( dateTime , out offset , out isDST , forOffset ) )
847
847
return offset ;
848
848
849
849
if ( dateTime . Kind == DateTimeKind . Utc ) {
@@ -870,10 +870,12 @@ private static TimeSpan GetUtcOffsetHelper (DateTime dateTime, TimeZoneInfo tz,
870
870
871
871
if ( tzRule != null && tz . IsInDST ( tzRule , dateTime ) ) {
872
872
// 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
874
874
// DST delta while in that hour.
875
- isDST = true ;
875
+ if ( forOffset )
876
+ isDST = true ;
876
877
if ( tz . IsInDST ( tzRule , dstUtcDateTime ) ) {
878
+ isDST = true ;
877
879
return tz . BaseUtcOffset + tzRule . DaylightDelta ;
878
880
} else {
879
881
return tz . BaseUtcOffset ;
@@ -925,7 +927,33 @@ public bool IsAmbiguousTime (DateTime dateTime)
925
927
AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
926
928
if ( rule != null ) {
927
929
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 )
929
957
return true ;
930
958
}
931
959
@@ -944,7 +972,18 @@ private bool IsInDST (AdjustmentRule rule, DateTime dateTime)
944
972
return true ;
945
973
946
974
// 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 ;
948
987
}
949
988
950
989
bool IsInDSTForYear ( AdjustmentRule rule , DateTime dateTime , int year )
@@ -953,8 +992,9 @@ bool IsInDSTForYear (AdjustmentRule rule, DateTime dateTime, int year)
953
992
DateTime DST_end = TransitionPoint ( rule . DaylightTransitionEnd , year + ( ( rule . DaylightTransitionStart . Month < rule . DaylightTransitionEnd . Month ) ? 0 : 1 ) ) ;
954
993
if ( dateTime . Kind == DateTimeKind . Utc ) {
955
994
DST_start -= BaseUtcOffset ;
956
- DST_end -= ( BaseUtcOffset + rule . DaylightDelta ) ;
995
+ DST_end -= BaseUtcOffset ;
957
996
}
997
+ DST_end -= rule . DaylightDelta ;
958
998
return ( dateTime >= DST_start && dateTime < DST_end ) ;
959
999
}
960
1000
@@ -982,7 +1022,21 @@ internal bool IsDaylightSavingTime (DateTime dateTime, TimeZoneInfoOptions flags
982
1022
983
1023
public bool IsDaylightSavingTime ( DateTimeOffset dateTimeOffset )
984
1024
{
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 ;
986
1040
}
987
1041
988
1042
internal DaylightTime GetDaylightChanges ( int year )
@@ -1219,7 +1273,7 @@ private AdjustmentRule GetApplicableRule (DateTime dateTime)
1219
1273
return null ;
1220
1274
}
1221
1275
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 )
1223
1277
{
1224
1278
offset = BaseUtcOffset ;
1225
1279
isDst = false ;
@@ -1235,18 +1289,45 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out
1235
1289
return false ;
1236
1290
}
1237
1291
1292
+ var isUtc = false ;
1238
1293
if ( dateTime . Kind != DateTimeKind . Utc ) {
1239
1294
if ( ! TryAddTicks ( date , - BaseUtcOffset . Ticks , out date , DateTimeKind . Utc ) )
1240
1295
return false ;
1241
- }
1296
+ } else
1297
+ isUtc = true ;
1298
+
1242
1299
1243
- AdjustmentRule current = GetApplicableRule ( date ) ;
1300
+ AdjustmentRule current = GetApplicableRule ( date ) ;
1244
1301
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 ) ;
1247
1306
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
+
1250
1331
return true ;
1251
1332
}
1252
1333
}
@@ -1255,8 +1336,11 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset,out
1255
1336
1256
1337
private static DateTime TransitionPoint ( TransitionTime transition , int year )
1257
1338
{
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
+ }
1260
1344
1261
1345
DayOfWeek first = ( new DateTime ( year , transition . Month , 1 ) ) . DayOfWeek ;
1262
1346
int day = 1 + ( transition . Week - 1 ) * 7 + ( transition . DayOfWeek - first + 7 ) % 7 ;
@@ -1540,7 +1624,7 @@ static internal TimeSpan GetUtcOffsetFromUtc (DateTime time, TimeZoneInfo zone,
1540
1624
isAmbiguousLocalDst = false ;
1541
1625
TimeSpan baseOffset = zone . BaseUtcOffset ;
1542
1626
1543
- if ( zone . IsAmbiguousTime ( time ) ) {
1627
+ if ( zone . IsAmbiguousLocalDstFromUtc ( time ) ) {
1544
1628
isAmbiguousLocalDst = true ;
1545
1629
// return baseOffset;
1546
1630
}
@@ -1570,4 +1654,4 @@ public override string ToString ()
1570
1654
}
1571
1655
#endif
1572
1656
}
1573
- }
1657
+ }
0 commit comments