Skip to content

Commit a5b8d6f

Browse files
steveisokakoeplinger
authored andcommitted
[2019-06] Fix time zone issue when jumping into DST (#17062)
There is an issue with jumping into DST for some time zones when the incorrect date-time offset is returned for date-time in UTC (which comes from DateTime.Now). The fix is to just check if the incoming date-time is in UTC Also I added set of tests for some time zones but they verify jumping into DST in general. Fixes #16395 Backport of #16430
1 parent 390e6e1 commit a5b8d6f

File tree

2 files changed

+259
-2
lines changed

2 files changed

+259
-2
lines changed

mcs/class/corlib/System/TimeZoneInfo.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,10 +1216,13 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset, out
12161216
return false;
12171217
}
12181218

1219+
var isUtc = false;
12191220
if (dateTime.Kind != DateTimeKind.Utc) {
12201221
if (!TryAddTicks (date, -BaseUtcOffset.Ticks, out date, DateTimeKind.Utc))
12211222
return false;
1222-
}
1223+
} else
1224+
isUtc = true;
1225+
12231226

12241227
AdjustmentRule current = GetApplicableRule (date);
12251228
if (current != null) {
@@ -1231,7 +1234,7 @@ private bool TryGetTransitionOffset (DateTime dateTime, out TimeSpan offset, out
12311234
if (forOffset)
12321235
isDst = true;
12331236
offset = baseUtcOffset;
1234-
if (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc))
1237+
if (isUtc || (date >= new DateTime (tStart.Ticks + current.DaylightDelta.Ticks, DateTimeKind.Utc)))
12351238
{
12361239
offset += current.DaylightDelta;
12371240
isDst = true;

mcs/class/corlib/Test/System/TimeZoneInfoTest.cs

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ public static string MapTimeZoneId (string id)
5454
return "New Zealand Standard Time";
5555
case "Europe/Athens":
5656
return "GTB Standard Time";
57+
case "Europe/Chisinau":
58+
return "E. Europe Standard Time";
5759
case "US/Eastern":
5860
return "Eastern Standard Time";
61+
case "America/Chicago":
5962
case "US/Central":
6063
return "Central Standard Time";
6164
case "US/Pacific":
@@ -64,18 +67,57 @@ public static string MapTimeZoneId (string id)
6467
case "Australia/Melbourne":
6568
return "AUS Eastern Standard Time";
6669
case "Europe/Brussels":
70+
case "Europe/Copenhagen":
71+
case "Europe/Paris":
72+
case "Europe/Madrid":
6773
return "Romance Standard Time";
6874
case "Africa/Kinshasa":
6975
return "W. Central Africa Standard Time";
7076
case "Europe/Rome":
7177
case "Europe/Vatican":
78+
case "Europe/Vienna":
79+
case "Europe/Berlin":
80+
case "Europe/Luxembourg":
81+
case "Europe/Malta":
82+
case "Europe/Monaco":
83+
case "Europe/Amsterdam":
84+
case "Europe/Oslo":
85+
case "Europe/San_Marino":
7286
return "W. Europe Standard Time";
7387
case "Canada/Eastern":
7488
return "Eastern Standard Time";
7589
case "Asia/Tehran":
7690
return "Iran Standard Time";
7791
case "Europe/Guernsey":
92+
case "Europe/Dublin":
93+
case "Europe/Isle_of_Man":
94+
case "Europe/Jersey":
95+
case "Europe/Lisbon":
96+
case "Europe/London":
7897
return "GMT Standard Time";
98+
case "America/Havana":
99+
return "Cuba Standard Time";
100+
case "America/Anchorage":
101+
return "Alaskan Standard Time";
102+
case "Atlantic/Azores":
103+
return "Azores Standard Time";
104+
case "Asia/Jerusalem":
105+
return "Israel Standard Time";
106+
case "Asia/Amman":
107+
return "Jordan Standard Time";
108+
case "Europe/Tirane":
109+
case "Europe/Warsaw":
110+
return "Central European Standard Time";
111+
case "Europe/Sofia":
112+
case "Europe/Tallinn":
113+
case "Europe/Riga":
114+
case "Europe/Vilnius":
115+
case "Europe/Kiev":
116+
return "FLE Standard Time";
117+
case "Europe/Prague":
118+
case "Europe/Budapest":
119+
case "Europe/Bratislava":
120+
return "Central Europe Standard Time";
79121
default:
80122
Assert.Fail ($"No mapping defined for zone id '{id}'");
81123
return null;
@@ -482,6 +524,218 @@ public void Bug_9664 ()
482524
Assert.AreEqual (new TimeSpan (0, 0, 0), tzi.GetUtcOffset (date));
483525
#endif
484526
}
527+
528+
[Test]
529+
public void Bug_16395 ()
530+
{
531+
// Cuba, Havana (Cuba Standard Time): Jumps ahead at 12:00 AM on 3/8/2020 to 1:00 AM
532+
CheckJumpingIntoDST ("America/Havana",
533+
new DateTime (2020, 3, 8, 0, 0, 0), new DateTime (2020, 3, 8, 0, 30, 0), new DateTime (2020, 3, 8, 1, 0, 0),
534+
new TimeSpan (-5, 0, 0), new TimeSpan (-4, 0, 0));
535+
536+
// US, Kansas City, MO (US Central Time): Jumps ahead at 2:00 AM on 3/8/2020 to 3:00 AM
537+
CheckJumpingIntoDST ("America/Chicago",
538+
new DateTime (2020, 3, 8, 2, 0, 0), new DateTime (2020, 3, 8, 2, 30, 0), new DateTime (2020, 3, 8, 3, 0, 0),
539+
new TimeSpan (-6, 0, 0), new TimeSpan (-5, 0, 0));
540+
541+
// Anchorage, AK (Alaska Time): Jumps ahead at 2:00 AM on 3/8/2020 to 3:00 AM
542+
CheckJumpingIntoDST ("America/Anchorage",
543+
new DateTime (2020, 3, 8, 2, 0, 0), new DateTime (2020, 3, 8, 2, 30, 0), new DateTime (2020, 3, 8, 3, 0, 0),
544+
new TimeSpan (-9, 0, 0), new TimeSpan (-8, 0, 0));
545+
546+
// Azores ST (Ponta Delgada, Portugal): Jumps ahead at 12:00 AM on 3/29/2020 to 1:00 AM
547+
CheckJumpingIntoDST ("Atlantic/Azores",
548+
new DateTime (2020, 3, 29, 0, 0, 0), new DateTime (2020, 3, 29, 0, 30, 0), new DateTime (2020, 3, 29, 1, 0, 0),
549+
new TimeSpan (-1, 0, 0), new TimeSpan (0, 0, 0));
550+
551+
// Iran, Tehran (Iran ST): Jumps ahead at 12:00 AM on 3/21/2020 to 1:00 AM
552+
CheckJumpingIntoDST ("Asia/Tehran",
553+
new DateTime (2020, 3, 21, 0, 0, 0), new DateTime (2020, 3, 21, 0, 30, 0), new DateTime (2020, 3, 21, 1, 0, 0),
554+
new TimeSpan (3, 30, 0), new TimeSpan (4, 30, 0));
555+
556+
// Israel, Jerusalem (Israel ST): Jumps ahead at 2:00 AM on 3/27/2020 to 3:00 AM
557+
CheckJumpingIntoDST ("Asia/Jerusalem",
558+
new DateTime (2020, 3, 27, 2, 0, 0), new DateTime (2020, 3, 27, 2, 30, 0), new DateTime (2020, 3, 27, 3, 0, 0),
559+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
560+
561+
// Jordan, Amman (Eastern European ST): Jumps ahead at 12:00 AM on 3/27/2020 to 1:00 AM
562+
CheckJumpingIntoDST ("Asia/Amman",
563+
new DateTime (2020, 3, 27, 0, 0, 0), new DateTime (2020, 3, 27, 0, 30, 0), new DateTime (2020, 3, 27, 1, 0, 0),
564+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
565+
566+
// Albania, Tirana (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
567+
CheckJumpingIntoDST ("Europe/Tirane",
568+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
569+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
570+
571+
// Austria, Vienna (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
572+
CheckJumpingIntoDST ("Europe/Vienna",
573+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
574+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
575+
576+
// Belgium, Brussels (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
577+
CheckJumpingIntoDST ("Europe/Brussels",
578+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
579+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
580+
581+
// Bulgaria, Sofia (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
582+
CheckJumpingIntoDST ("Europe/Sofia",
583+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
584+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
585+
586+
// Czechia, Prague (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
587+
CheckJumpingIntoDST ("Europe/Prague",
588+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
589+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
590+
591+
// Denmark, Copenhagen (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
592+
CheckJumpingIntoDST ("Europe/Copenhagen",
593+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
594+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
595+
596+
// Estonia, Tallinn (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
597+
CheckJumpingIntoDST ("Europe/Tallinn",
598+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
599+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
600+
601+
// France, Paris (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
602+
CheckJumpingIntoDST ("Europe/Paris",
603+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
604+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
605+
606+
// Germany, Berlin (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
607+
CheckJumpingIntoDST ("Europe/Berlin",
608+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
609+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
610+
611+
// Greece, Athens (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
612+
CheckJumpingIntoDST ("Europe/Athens",
613+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
614+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
615+
616+
// Guernsey (UK) Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
617+
CheckJumpingIntoDST ("Europe/Guernsey",
618+
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
619+
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
620+
621+
// Holy See, Vatican City (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
622+
CheckJumpingIntoDST ("Europe/Vatican",
623+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
624+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
625+
626+
// Hungary, Budapest (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
627+
CheckJumpingIntoDST ("Europe/Budapest",
628+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
629+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
630+
631+
// // Ireland, Dublin (Greenwich Mean Time -> Irish Standard Time): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
632+
// CheckJumpingIntoDST ("Europe/Dublin",
633+
// new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
634+
// new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
635+
636+
// UK, Douglas, Isle of Man (GMT+1:00): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
637+
CheckJumpingIntoDST ("Europe/Isle_of_Man",
638+
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
639+
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
640+
641+
// Italy, Rome (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
642+
CheckJumpingIntoDST ("Europe/Rome",
643+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
644+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
645+
646+
// Jersey (UK): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
647+
CheckJumpingIntoDST ("Europe/Jersey",
648+
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
649+
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
650+
651+
// Latvia, Riga (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
652+
CheckJumpingIntoDST ("Europe/Riga",
653+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
654+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
655+
656+
// Lithuania, Vilnius (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
657+
CheckJumpingIntoDST ("Europe/Vilnius",
658+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
659+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
660+
661+
// Luxembourg, Luxembourg (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
662+
CheckJumpingIntoDST ("Europe/Luxembourg",
663+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
664+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
665+
666+
// Malta, Valletta (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
667+
CheckJumpingIntoDST ("Europe/Malta",
668+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
669+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
670+
671+
// Moldova, Chişinău (Eastern European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
672+
CheckJumpingIntoDST ("Europe/Chisinau",
673+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
674+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
675+
676+
// Monaco, Monaco (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
677+
CheckJumpingIntoDST ("Europe/Monaco",
678+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
679+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
680+
681+
// Netherlands, Amsterdam (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
682+
CheckJumpingIntoDST ("Europe/Amsterdam",
683+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
684+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
685+
686+
// Norway, Oslo (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
687+
CheckJumpingIntoDST ("Europe/Oslo",
688+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
689+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
690+
691+
// Poland, Warsaw (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
692+
CheckJumpingIntoDST ("Europe/Warsaw",
693+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
694+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
695+
696+
// Portugal, Lisbon (Western European ST): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
697+
CheckJumpingIntoDST ("Europe/Lisbon",
698+
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
699+
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
700+
701+
// San Marino, San Marino (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
702+
CheckJumpingIntoDST ("Europe/San_Marino",
703+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
704+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
705+
706+
// Slovakia, Bratislava (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
707+
CheckJumpingIntoDST ("Europe/Bratislava",
708+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
709+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
710+
711+
// Spain, Madrid (Central European ST): Jumps ahead at 2:00 AM on 3/29/2020 to 3:00 AM
712+
CheckJumpingIntoDST ("Europe/Madrid",
713+
new DateTime (2020, 3, 29, 2, 0, 0), new DateTime (2020, 3, 29, 2, 30, 0), new DateTime (2020, 3, 29, 3, 0, 0),
714+
new TimeSpan (1, 0, 0), new TimeSpan (2, 0, 0));
715+
716+
// Ukraine, Kiev (Eastern European ST): Jumps ahead at 3:00 AM on 3/29/2020 to 4:00 AM
717+
CheckJumpingIntoDST ("Europe/Kiev",
718+
new DateTime (2020, 3, 29, 3, 0, 0), new DateTime (2020, 3, 29, 3, 30, 0), new DateTime (2020, 3, 29, 4, 0, 0),
719+
new TimeSpan (2, 0, 0), new TimeSpan (3, 0, 0));
720+
721+
// United Kingdom, London (British ST): Jumps ahead at 1:00 AM on 3/29/2020 to 2:00 AM
722+
CheckJumpingIntoDST ("Europe/London",
723+
new DateTime (2020, 3, 29, 1, 0, 0), new DateTime (2020, 3, 29, 1, 30, 0), new DateTime (2020, 3, 29, 2, 0, 0),
724+
new TimeSpan (0, 0, 0), new TimeSpan (1, 0, 0));
725+
}
726+
727+
void CheckJumpingIntoDST (string tzId, DateTime dstDeltaStart, DateTime inDstDelta, DateTime dstDeltaEnd, TimeSpan baseOffset, TimeSpan dstOffset)
728+
{
729+
var tzi = TimeZoneInfo.FindSystemTimeZoneById (MapTimeZoneId (tzId));
730+
Assert.IsFalse (tzi.IsDaylightSavingTime (dstDeltaStart), $"{tzId}: #1");
731+
Assert.AreEqual (baseOffset, tzi.GetUtcOffset (dstDeltaStart), $"{tzId}: #2");
732+
733+
Assert.IsFalse (tzi.IsDaylightSavingTime (inDstDelta), $"{tzId}: #3");
734+
Assert.AreEqual (baseOffset, tzi.GetUtcOffset (inDstDelta), $"{tzId}: #4");
735+
736+
Assert.IsTrue (tzi.IsDaylightSavingTime (dstDeltaEnd), $"{tzId}: #5");
737+
Assert.AreEqual (dstOffset, tzi.GetUtcOffset (dstDeltaEnd), $"{tzId}: #6");
738+
}
485739
}
486740

487741
[TestFixture]

0 commit comments

Comments
 (0)