From 711363299152ad936ac65107aa0a650a0264bc7a Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 16 Mar 2021 17:42:45 -0700 Subject: [PATCH 1/6] Fix Time Zones Adjustment Rules on Linux --- .../src/System/TimeZoneInfo.AdjustmentRule.cs | 18 +++++ .../src/System/TimeZoneInfo.Unix.cs | 71 +++++++++++++++++-- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.AdjustmentRule.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.AdjustmentRule.cs index caac7f0a4b3cdf..fb148e31235f12 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.AdjustmentRule.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.AdjustmentRule.cs @@ -76,6 +76,24 @@ private AdjustmentRule( _noDaylightTransitions = noDaylightTransitions; } + internal static AdjustmentRule CreateAdjustmentRule( + DateTime dateStart, + DateTime dateEnd, + TimeSpan daylightDelta, + TransitionTime daylightTransitionStart, + TransitionTime daylightTransitionEnd, + TimeSpan baseUtcOffsetDelta) + { + return new AdjustmentRule( + dateStart, + dateEnd, + daylightDelta, + daylightTransitionStart, + daylightTransitionEnd, + baseUtcOffsetDelta, + noDaylightTransitions: false); + } + public static AdjustmentRule CreateAdjustmentRule( DateTime dateStart, DateTime dateEnd, diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index fd2d2556527c6d..f11c12ce44820c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -114,6 +114,17 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled) ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime); } + // The TransitionTime fields are not used when AdjustmentRule.NoDaylightTransitions == true. + // However, there are some cases in the past where DST = true, and the daylight savings offset + // now equals what the current BaseUtcOffset is. In that case, the AdjustmentRule.DaylightOffset + // is going to be TimeSpan.Zero. But we still need to return 'true' from AdjustmentRule.HasDaylightSaving. + // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic + // in HasDaylightSaving return true. + private static readonly TransitionTime s_daylightRuleMarker = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1); + + // Truncate the date and the time to Milliseconds precision + private static DateTime GetTimeOnly(DateTime input) => new DateTime((input.TimeOfDay.Ticks / TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond); + /// /// Returns a cloned array of AdjustmentRule objects /// @@ -128,11 +139,20 @@ public AdjustmentRule[] GetAdjustmentRules() // as the rules now is public, we should fill it properly so the caller doesn't have to know how we use it internally // and can use it as it is used in Windows - AdjustmentRule[] rules = new AdjustmentRule[_adjustmentRules.Length]; + List rulesList = new List(); for (int i = 0; i < _adjustmentRules.Length; i++) { AdjustmentRule? rule = _adjustmentRules[i]; + + if (rule.NoDaylightTransitions && + rule.DaylightTransitionStart != s_daylightRuleMarker && + rule.DaylightDelta == TimeSpan.Zero && rule.BaseUtcOffsetDelta == TimeSpan.Zero) + { + // This rule has no any time transition, ignore it. + continue; + } + DateTime start = rule.DateStart.Kind == DateTimeKind.Utc ? // At the daylight start we didn't start the daylight saving yet then we convert to Local time // by adding the _baseUtcOffset to the UTC time @@ -144,13 +164,50 @@ public AdjustmentRule[] GetAdjustmentRules() new DateTime(rule.DateEnd.Ticks + _baseUtcOffset.Ticks + rule.DaylightDelta.Ticks, DateTimeKind.Unspecified) : rule.DateEnd; - TransitionTime startTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, start.Hour, start.Minute, start.Second), start.Month, start.Day); - TransitionTime endTransition = TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, end.Hour, end.Minute, end.Second), end.Month, end.Day); + if (start.Year == end.Year || !rule.NoDaylightTransitions) + { + TransitionTime startTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnly(start), start.Month, start.Day) : rule.DaylightTransitionStart; + TransitionTime endTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnly(end), end.Month, end.Day) : rule.DaylightTransitionEnd; + rulesList.Add(AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); + } + else + { + // For rules spanning more than one year. The time transition inside this rule would apply for the whole time spanning these years + // and not for partial time of every year. + // AdjustmentRule cannot express express such rule using the DaylightTransitionStart and DaylightTransitionEnd because + // the DaylightTransitionStart and DaylightTransitionEnd express the transition for every year in the rule year range. + // We split the rule into more rules. The first rule will start from the start of the original rule and ends at the end of the same year. + // The transition for this rule will cover the whole time from the start to the end of the rule year. + // The second splitted rule would cover the middle range of the original rule and ranging from the year start+1 to + // year end-1. The transition time in this rule would start from Jan 1st to end of December. + // The last splitted rule would start from the Jan 1st of the end year of the original rule and ends at the end transition time of the original rule. + + // Add the first rule. + DateTime endForFirstRule = new DateTime(start.Year + 1, 1, 1).AddMilliseconds(-1); // At the end of the first year + TransitionTime startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(start), start.Month, start.Day); + TransitionTime endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(endForFirstRule), endForFirstRule.Month, endForFirstRule.Day); + rulesList.Add(AdjustmentRule.CreateAdjustmentRule(start.Date, endForFirstRule.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); + + // Check if there is range of years between the start and the end years + if (end.Year - start.Year > 1) + { + // Add the middle rule. + DateTime middleYearStart = new DateTime(start.Year + 1, 1, 1); + DateTime middleYearEnd = new DateTime(end.Year, 1, 1).AddMilliseconds(-1); + startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(middleYearStart), middleYearStart.Month, middleYearStart.Day); + endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(middleYearEnd), middleYearEnd.Month, middleYearEnd.Day); + rulesList.Add(AdjustmentRule.CreateAdjustmentRule(middleYearStart.Date, middleYearEnd.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); + } - rules[i] = TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition); + // Add the end rule. + DateTime endYearStart = new DateTime(end.Year, 1, 1); // At the beginning of the last year + startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(endYearStart), endYearStart.Month, endYearStart.Day); + endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(end), end.Month, end.Day); + rulesList.Add(AdjustmentRule.CreateAdjustmentRule(endYearStart.Date, end.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); + } } - return rules; + return rulesList.ToArray(); } private static void PopulateAllSystemTimeZones(CachedData cachedData) @@ -957,7 +1014,7 @@ private static void TZif_GenerateAdjustmentRule(ref int index, TimeSpan timeZone // is going to be TimeSpan.Zero. But we still need to return 'true' from AdjustmentRule.HasDaylightSaving. // To ensure we always return true from HasDaylightSaving, make a "special" dstStart that will make the logic // in HasDaylightSaving return true. - dstStart = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1); + dstStart = s_daylightRuleMarker; } else { @@ -1068,7 +1125,7 @@ private static TZifType TZif_GetEarlyDateTransitionType(TZifType[] transitionTyp /// Creates an AdjustmentRule given the POSIX TZ environment variable string. /// /// - /// See http://man7.org/linux/man-pages/man3/tzset.3.html for the format and semantics of this POSX string. + /// See http://man7.org/linux/man-pages/man3/tzset.3.html for the format and semantics of this POSIX string. /// private static AdjustmentRule? TZif_CreateAdjustmentRuleForPosixFormat(string posixFormat, DateTime startTransitionDate, TimeSpan timeZoneBaseUtcOffset) { From be75f6e3f348bb19b2ad79750d3d0c0b30f6ef59 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 16 Mar 2021 17:54:33 -0700 Subject: [PATCH 2/6] Comment --- .../src/System/TimeZoneInfo.Unix.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index f11c12ce44820c..7ce605c3c264af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -149,7 +149,7 @@ public AdjustmentRule[] GetAdjustmentRules() rule.DaylightTransitionStart != s_daylightRuleMarker && rule.DaylightDelta == TimeSpan.Zero && rule.BaseUtcOffsetDelta == TimeSpan.Zero) { - // This rule has no any time transition, ignore it. + // This rule has no time transition, ignore it. continue; } @@ -174,10 +174,9 @@ public AdjustmentRule[] GetAdjustmentRules() { // For rules spanning more than one year. The time transition inside this rule would apply for the whole time spanning these years // and not for partial time of every year. - // AdjustmentRule cannot express express such rule using the DaylightTransitionStart and DaylightTransitionEnd because - // the DaylightTransitionStart and DaylightTransitionEnd express the transition for every year in the rule year range. - // We split the rule into more rules. The first rule will start from the start of the original rule and ends at the end of the same year. - // The transition for this rule will cover the whole time from the start to the end of the rule year. + // AdjustmentRule cannot express such rule using the DaylightTransitionStart and DaylightTransitionEnd because + // the DaylightTransitionStart and DaylightTransitionEnd express the transition for every year. + // We split the rule into more rules. The first rule will start from the start year of the original rule and ends at the end of the same year. // The second splitted rule would cover the middle range of the original rule and ranging from the year start+1 to // year end-1. The transition time in this rule would start from Jan 1st to end of December. // The last splitted rule would start from the Jan 1st of the end year of the original rule and ends at the end transition time of the original rule. From b2cd6017dd8520da3523513cfa2702c1c9c9484f Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Tue, 16 Mar 2021 20:34:55 -0700 Subject: [PATCH 3/6] Init list with initial capacity --- .../System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 7ce605c3c264af..3b6f711faa6219 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -139,7 +139,7 @@ public AdjustmentRule[] GetAdjustmentRules() // as the rules now is public, we should fill it properly so the caller doesn't have to know how we use it internally // and can use it as it is used in Windows - List rulesList = new List(); + List rulesList = new List(_adjustmentRules.Length); for (int i = 0; i < _adjustmentRules.Length; i++) { From 4d242615e312705827dda36e10a2127e9c91f00e Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 18 Mar 2021 19:14:01 -0700 Subject: [PATCH 4/6] Address the feedback --- .../src/System/TimeZoneInfo.Unix.cs | 22 ++++---- .../tests/System/TimeZoneInfoTests.cs | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 3b6f711faa6219..bdaacf01e42b8e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -123,7 +123,7 @@ private TimeZoneInfo(byte[] data, string id, bool dstDisabled) private static readonly TransitionTime s_daylightRuleMarker = TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(2), 1, 1); // Truncate the date and the time to Milliseconds precision - private static DateTime GetTimeOnly(DateTime input) => new DateTime((input.TimeOfDay.Ticks / TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond); + private static DateTime GetTimeOnlyInMillisecondsPrecision(DateTime input) => new DateTime((input.TimeOfDay.Ticks / TimeSpan.TicksPerMillisecond) * TimeSpan.TicksPerMillisecond); /// /// Returns a cloned array of AdjustmentRule objects @@ -143,7 +143,7 @@ public AdjustmentRule[] GetAdjustmentRules() for (int i = 0; i < _adjustmentRules.Length; i++) { - AdjustmentRule? rule = _adjustmentRules[i]; + AdjustmentRule rule = _adjustmentRules[i]; if (rule.NoDaylightTransitions && rule.DaylightTransitionStart != s_daylightRuleMarker && @@ -166,8 +166,10 @@ public AdjustmentRule[] GetAdjustmentRules() if (start.Year == end.Year || !rule.NoDaylightTransitions) { - TransitionTime startTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnly(start), start.Month, start.Day) : rule.DaylightTransitionStart; - TransitionTime endTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnly(end), end.Month, end.Day) : rule.DaylightTransitionEnd; + // If the rule is covering only one year then the start and end transitions would occure in that year, we don't need to split the rule. + // Also, rule.NoDaylightTransitions be false in case the rule was created from a POSIX time zone string and having a DST transition. We can represent this in one rule too + TransitionTime startTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(start), start.Month, start.Day) : rule.DaylightTransitionStart; + TransitionTime endTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(end), end.Month, end.Day) : rule.DaylightTransitionEnd; rulesList.Add(AdjustmentRule.CreateAdjustmentRule(start.Date, end.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); } else @@ -183,8 +185,8 @@ public AdjustmentRule[] GetAdjustmentRules() // Add the first rule. DateTime endForFirstRule = new DateTime(start.Year + 1, 1, 1).AddMilliseconds(-1); // At the end of the first year - TransitionTime startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(start), start.Month, start.Day); - TransitionTime endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(endForFirstRule), endForFirstRule.Month, endForFirstRule.Day); + TransitionTime startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(start), start.Month, start.Day); + TransitionTime endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(endForFirstRule), endForFirstRule.Month, endForFirstRule.Day); rulesList.Add(AdjustmentRule.CreateAdjustmentRule(start.Date, endForFirstRule.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); // Check if there is range of years between the start and the end years @@ -193,15 +195,15 @@ public AdjustmentRule[] GetAdjustmentRules() // Add the middle rule. DateTime middleYearStart = new DateTime(start.Year + 1, 1, 1); DateTime middleYearEnd = new DateTime(end.Year, 1, 1).AddMilliseconds(-1); - startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(middleYearStart), middleYearStart.Month, middleYearStart.Day); - endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(middleYearEnd), middleYearEnd.Month, middleYearEnd.Day); + startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(middleYearStart), middleYearStart.Month, middleYearStart.Day); + endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(middleYearEnd), middleYearEnd.Month, middleYearEnd.Day); rulesList.Add(AdjustmentRule.CreateAdjustmentRule(middleYearStart.Date, middleYearEnd.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); } // Add the end rule. DateTime endYearStart = new DateTime(end.Year, 1, 1); // At the beginning of the last year - startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(endYearStart), endYearStart.Month, endYearStart.Day); - endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnly(end), end.Month, end.Day); + startTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(endYearStart), endYearStart.Month, endYearStart.Day); + endTransition = TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(end), end.Month, end.Day); rulesList.Add(AdjustmentRule.CreateAdjustmentRule(endYearStart.Date, end.Date, rule.DaylightDelta, startTransition, endTransition, rule.BaseUtcOffsetDelta)); } } diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index ab96edddb8bc31..ee6cb23326d53b 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -1819,6 +1819,62 @@ public static void IsDaylightSavingTime_CasablancaMultiYearDaylightSavings(strin Assert.Equal(offset, s_casablancaTz.GetUtcOffset(dt)); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))] + public static void TestSplittingRulesWhenReported() + { + // This test confirm we are splitting the rules which span multiple years on Linux + // we use "America/Los_Angeles" which has the rule covering 2/9/194 to 8/14/1945 + // with daylight transition by 01:00:00. This rule should be split into 3 rules: + // - rule 1 from 2/9/1942 to 12/31/1942 + // - rule 2 from 1/1/1943 to 12/31/1944 + // - rule 3 from 1/1/1945 to 8/14/1945 + TimeZoneInfo.AdjustmentRule[] rules = TimeZoneInfo.FindSystemTimeZoneById(s_strPacific).GetAdjustmentRules(); + + for (int i = 0; i < rules.Length; i++) + { + if (rules[i].DateStart == new DateTime(1942, 2, 9)) + { + Assert.True(i + 2 <= rules.Length - 1); + TimeSpan daylightDelta = TimeSpan.FromHours(1); + + // DateStart : 2/9/1942 12:00:00 AM (Unspecified) + // DateEnd : 12/31/1942 12:00:00 AM (Unspecified) + // DaylightDelta : 01:00:00 + // DaylightTransitionStart : ToD:02:00:00 M:2, D:9, W:1, DoW:Sunday, FixedDate:True + // DaylightTransitionEnd : ToD:23:59:59.9990000 M:12, D:31, W:1, DoW:Sunday, FixedDate:True + + Assert.Equal(new DateTime(1942, 12, 31), rules[i].DateEnd); + Assert.Equal(daylightDelta, rules[i].DaylightDelta); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 2, 0, 0), 2, 9), rules[i].DaylightTransitionStart); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 23, 59, 59, 999), 12, 31), rules[i].DaylightTransitionEnd); + + // DateStart : 1/1/1943 12:00:00 AM (Unspecified) + // DateEnd : 12/31/1944 12:00:00 AM (Unspecified) + // DaylightDelta : 01:00:00 + // DaylightTransitionStart : ToD:00:00:00 M:1, D:1, W:1, DoW:Sunday, FixedDate:True + // DaylightTransitionEnd : ToD:23:59:59.9990000 M:12, D:31, W:1, DoW:Sunday, FixedDate:True + + Assert.Equal(new DateTime(1943, 1, 1), rules[i + 1].DateStart); + Assert.Equal(new DateTime(1944, 12, 31), rules[i + 1].DateEnd); + Assert.Equal(daylightDelta, rules[i + 1].DaylightDelta); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 0, 0, 0), 1, 1), rules[i + 1].DaylightTransitionStart); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 23, 59, 59, 999), 12, 31), rules[i + 1].DaylightTransitionEnd); + + // DateStart : 1/1/1945 12:00:00 AM (Unspecified) + // DateEnd : 8/14/1945 12:00:00 AM (Unspecified) + // DaylightDelta : 01:00:00 + // DaylightTransitionStart : ToD:00:00:00 M:1, D:1, W:1, DoW:Sunday, FixedDate:True + // DaylightTransitionEnd : ToD:15:59:59.9990000 M:8, D:14, W:1, DoW:Sunday, FixedDate:True + + Assert.Equal(new DateTime(1945, 1, 1), rules[i + 2].DateStart); + Assert.Equal(new DateTime(1945, 8, 14), rules[i + 2].DateEnd); + Assert.Equal(daylightDelta, rules[i + 2].DaylightDelta); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 0, 0, 0), 1, 1), rules[i + 2].DaylightTransitionStart); + Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 15, 59, 59, 999), 8, 14), rules[i + 2].DaylightTransitionEnd); + } + } + } + [Theory] [PlatformSpecific(TestPlatforms.AnyUnix)] // Linux will use local mean time for DateTimes before standard time came into effect. // in 1996 Europe/Lisbon changed from standard time to DST without changing the UTC offset From 738070c709b8f4d6d3738b68a90724b9d286940b Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Mar 2021 14:56:03 -0700 Subject: [PATCH 5/6] More Feedback --- .../src/System/TimeZoneInfo.Unix.cs | 2 +- .../System.Runtime/tests/System/TimeZoneInfoTests.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index bdaacf01e42b8e..6e595737317d18 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -166,7 +166,7 @@ public AdjustmentRule[] GetAdjustmentRules() if (start.Year == end.Year || !rule.NoDaylightTransitions) { - // If the rule is covering only one year then the start and end transitions would occure in that year, we don't need to split the rule. + // If the rule is covering only one year then the start and end transitions would occur in that year, we don't need to split the rule. // Also, rule.NoDaylightTransitions be false in case the rule was created from a POSIX time zone string and having a DST transition. We can represent this in one rule too TransitionTime startTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(start), start.Month, start.Day) : rule.DaylightTransitionStart; TransitionTime endTransition = rule.NoDaylightTransitions ? TransitionTime.CreateFixedDateRule(GetTimeOnlyInMillisecondsPrecision(end), end.Month, end.Day) : rule.DaylightTransitionEnd; diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index ee6cb23326d53b..23ac74f3f68b6a 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -1823,13 +1823,14 @@ public static void IsDaylightSavingTime_CasablancaMultiYearDaylightSavings(strin public static void TestSplittingRulesWhenReported() { // This test confirm we are splitting the rules which span multiple years on Linux - // we use "America/Los_Angeles" which has the rule covering 2/9/194 to 8/14/1945 + // we use "America/Los_Angeles" which has the rule covering 2/9/1942 to 8/14/1945 // with daylight transition by 01:00:00. This rule should be split into 3 rules: // - rule 1 from 2/9/1942 to 12/31/1942 // - rule 2 from 1/1/1943 to 12/31/1944 // - rule 3 from 1/1/1945 to 8/14/1945 TimeZoneInfo.AdjustmentRule[] rules = TimeZoneInfo.FindSystemTimeZoneById(s_strPacific).GetAdjustmentRules(); + bool ruleEncountered = false; for (int i = 0; i < rules.Length; i++) { if (rules[i].DateStart == new DateTime(1942, 2, 9)) @@ -1871,8 +1872,13 @@ public static void TestSplittingRulesWhenReported() Assert.Equal(daylightDelta, rules[i + 2].DaylightDelta); Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 0, 0, 0), 1, 1), rules[i + 2].DaylightTransitionStart); Assert.Equal(TimeZoneInfo.TransitionTime.CreateFixedDateRule(new DateTime(1, 1, 1, 15, 59, 59, 999), 8, 14), rules[i + 2].DaylightTransitionEnd); + + ruleEncountered = true; + break; } } + + Assert.True(ruleEncountered, "The 1942 rule of America/Los_Angeles not found."); } [Theory] From de307d79c1d1b8b04f68c0fbde91eae83374103a Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Mar 2021 15:33:13 -0700 Subject: [PATCH 6/6] feedback --- src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index 23ac74f3f68b6a..35861477d23883 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -1819,7 +1819,8 @@ public static void IsDaylightSavingTime_CasablancaMultiYearDaylightSavings(strin Assert.Equal(offset, s_casablancaTz.GetUtcOffset(dt)); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows))] + [Fact] + [PlatformSpecific(~TestPlatforms.Windows)] public static void TestSplittingRulesWhenReported() { // This test confirm we are splitting the rules which span multiple years on Linux