Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit a4b6694

Browse files
committed
Remember AdjustementRule index
Profiling TimeZoneInfo scenarios on Unix shows most of the time being spent in GetPreviousAdjustmentRule. This is because we need to scan all of the AdjustmentRules, looking for the specified rule, and then getting the one before it. This change remembers the AdjustmentRule index when passing the AdjustmentRule into GetPreviousAdjustmentRule. This allows us to not scan all the rules, and increases the performance of DateTime.Now significantly in time zones that have daylight savings time. Fix https://github.com/dotnet/corefx/issues/3571
1 parent 3aec163 commit a4b6694

File tree

2 files changed

+68
-35
lines changed

2 files changed

+68
-35
lines changed

src/mscorlib/src/System/TimeZoneInfo.Win32.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool
398398
baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta;
399399
if (match.Rule.HasDaylightSaving)
400400
{
401-
isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, out isAmbiguousLocalDst, Local);
401+
isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, null, out isAmbiguousLocalDst, Local);
402402
baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
403403
}
404404
}

src/mscorlib/src/System/TimeZoneInfo.cs

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,11 @@ public TimeSpan[] GetAmbiguousTimeOffsets(DateTimeOffset dateTimeOffset)
172172
DateTime adjustedTime = ConvertTime(dateTimeOffset, this).DateTime;
173173

174174
bool isAmbiguous = false;
175-
AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime);
175+
int? ruleIndex;
176+
AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex);
176177
if (rule != null && rule.HasDaylightSaving)
177178
{
178-
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule);
179+
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
179180
isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
180181
}
181182

@@ -232,10 +233,11 @@ public TimeSpan[] GetAmbiguousTimeOffsets(DateTime dateTime)
232233
}
233234

234235
bool isAmbiguous = false;
235-
AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime);
236+
int? ruleIndex;
237+
AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex);
236238
if (rule != null && rule.HasDaylightSaving)
237239
{
238-
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule);
240+
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
239241
isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
240242
}
241243

@@ -263,15 +265,15 @@ public TimeSpan[] GetAmbiguousTimeOffsets(DateTime dateTime)
263265
}
264266

265267
// note the time is already adjusted
266-
private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime)
268+
private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime, out int? ruleIndex)
267269
{
268-
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
270+
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
269271
if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving)
270272
{
271273
// When using NoDaylightTransitions rules, each rule is only for one offset.
272274
// When looking for the Daylight savings rules, and we found the non-DST rule,
273275
// then we get the rule right before this rule.
274-
return GetPreviousAdjustmentRule(rule);
276+
return GetPreviousAdjustmentRule(rule, ruleIndex);
275277
}
276278

277279
return rule;
@@ -282,10 +284,15 @@ private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTim
282284
/// If the specified rule is the first AdjustmentRule, or it isn't in _adjustmentRules,
283285
/// then the specified rule is returned.
284286
/// </summary>
285-
private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule)
287+
private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule, int? ruleIndex)
286288
{
287289
Debug.Assert(rule.NoDaylightTransitions, "GetPreviousAdjustmentRule should only be used with NoDaylightTransitions rules.");
288290

291+
if (ruleIndex.HasValue && 0 < ruleIndex.Value && ruleIndex.Value < _adjustmentRules.Length)
292+
{
293+
return _adjustmentRules[ruleIndex.Value - 1];
294+
}
295+
289296
AdjustmentRule result = rule;
290297
for (int i = 1; i < _adjustmentRules.Length; i++)
291298
{
@@ -413,10 +420,11 @@ internal bool IsAmbiguousTime(DateTime dateTime, TimeZoneInfoOptions flags)
413420
dateTime.Kind == DateTimeKind.Utc ? ConvertTime(dateTime, s_utcTimeZone, this, flags, cachedData) :
414421
dateTime;
415422

416-
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
423+
int? ruleIndex;
424+
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
417425
if (rule != null && rule.HasDaylightSaving)
418426
{
419-
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule);
427+
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
420428
return GetIsAmbiguousTime(adjustedTime, rule, daylightTime);
421429
}
422430
return false;
@@ -498,10 +506,11 @@ private bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags,
498506
//
499507
// handle the normal cases...
500508
//
501-
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime);
509+
int? ruleIndex;
510+
AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex);
502511
if (rule != null && rule.HasDaylightSaving)
503512
{
504-
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule);
513+
DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex);
505514
return GetIsDaylightSavings(adjustedTime, rule, daylightTime, flags);
506515
}
507516
else
@@ -521,11 +530,12 @@ public bool IsInvalidTime(DateTime dateTime)
521530
(dateTime.Kind == DateTimeKind.Local && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local))
522531
{
523532
// only check Unspecified and (Local when this TimeZoneInfo instance is Local)
524-
AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime);
533+
int? ruleIndex;
534+
AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime, out ruleIndex);
525535

526536
if (rule != null && rule.HasDaylightSaving)
527537
{
528-
DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule);
538+
DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule, ruleIndex);
529539
isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime);
530540
}
531541
else
@@ -667,7 +677,8 @@ private static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZo
667677
// performance for the normal case at the expense of the 'ArgumentException'
668678
// case and Loss-less Local special cases.
669679
//
670-
AdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime);
680+
int? sourceRuleIndex;
681+
AdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime, out sourceRuleIndex);
671682
TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset;
672683

673684
if (sourceRule != null)
@@ -676,7 +687,7 @@ private static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZo
676687
if (sourceRule.HasDaylightSaving)
677688
{
678689
bool sourceIsDaylightSavings = false;
679-
DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule);
690+
DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule, sourceRuleIndex);
680691

681692
// 'dateTime' might be in an invalid time range since it is in an AdjustmentRule
682693
// period that supports DST
@@ -1054,10 +1065,16 @@ private TimeZoneInfo(SerializationInfo info, StreamingContext context)
10541065
_supportsDaylightSavingTime = (bool)info.GetValue("SupportsDaylightSavingTime", typeof(bool));
10551066
}
10561067

1057-
private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc = false)
1068+
private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, out int? ruleIndex)
1069+
{
1070+
return GetAdjustmentRuleForTime(dateTime, dateTimeisUtc: false, ruleIndex: out ruleIndex);
1071+
}
1072+
1073+
private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc, out int? ruleIndex)
10581074
{
10591075
if (_adjustmentRules == null || _adjustmentRules.Length == 0)
10601076
{
1077+
ruleIndex = null;
10611078
return null;
10621079
}
10631080

@@ -1082,6 +1099,7 @@ private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTime
10821099
int compareResult = CompareAdjustmentRuleToDateTime(rule, previousRule, dateTime, date, dateTimeisUtc);
10831100
if (compareResult == 0)
10841101
{
1102+
ruleIndex = median;
10851103
return rule;
10861104
}
10871105
else if (compareResult < 0)
@@ -1094,6 +1112,7 @@ private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTime
10941112
}
10951113
}
10961114

1115+
ruleIndex = null;
10971116
return null;
10981117
}
10991118

@@ -1205,7 +1224,7 @@ private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinatio
12051224
/// <summary>
12061225
/// Helper function that returns a DaylightTime from a year and AdjustmentRule.
12071226
/// </summary>
1208-
private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule)
1227+
private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule, int? ruleIndex)
12091228
{
12101229
TimeSpan delta = rule.DaylightDelta;
12111230
DateTime startTime;
@@ -1217,7 +1236,7 @@ private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule)
12171236
// Convert the UTC times into adjusted time zone times.
12181237

12191238
// use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
1220-
AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule);
1239+
AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex);
12211240
startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta);
12221241

12231242
endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta);
@@ -1307,12 +1326,12 @@ private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, Day
13071326
/// <summary>
13081327
/// Gets the offset that should be used to calculate DST start times from a UTC time.
13091328
/// </summary>
1310-
private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule)
1329+
private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule, int? ruleIndex)
13111330
{
13121331
if (rule.NoDaylightTransitions)
13131332
{
13141333
// use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule
1315-
AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule);
1334+
AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex);
13161335
return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta;
13171336
}
13181337
else
@@ -1334,7 +1353,7 @@ private TimeSpan GetDaylightSavingsEndOffsetFromUtc(TimeSpan baseUtcOffset, Adju
13341353
/// Helper function that checks if a given dateTime is in Daylight Saving Time (DST).
13351354
/// This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone.
13361355
/// </summary>
1337-
private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, out bool isAmbiguousLocalDst, TimeZoneInfo zone)
1356+
private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, int? ruleIndex, out bool isAmbiguousLocalDst, TimeZoneInfo zone)
13381357
{
13391358
isAmbiguousLocalDst = false;
13401359

@@ -1344,7 +1363,7 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa
13441363
}
13451364

13461365
// Get the daylight changes for the year of the specified time.
1347-
DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule);
1366+
DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule, ruleIndex);
13481367

13491368
// The start and end times represent the range of universal times that are in DST for that year.
13501369
// Within that there is an ambiguous hour, usually right at the end, but at the beginning in
@@ -1358,14 +1377,20 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa
13581377
// Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving.
13591378

13601379
bool ignoreYearAdjustment = false;
1361-
TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule);
1380+
TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex);
13621381
DateTime startTime;
13631382
if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year)
13641383
{
1365-
AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.Start.Year - 1, 12, 31));
1384+
int? previousYearRuleIndex;
1385+
AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime(
1386+
new DateTime(daylightTime.Start.Year - 1, 12, 31),
1387+
out previousYearRuleIndex);
13661388
if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear())
13671389
{
1368-
DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime(daylightTime.Start.Year - 1, previousYearRule);
1390+
DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime(
1391+
daylightTime.Start.Year - 1,
1392+
previousYearRule,
1393+
previousYearRuleIndex);
13691394
startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta;
13701395
ignoreYearAdjustment = true;
13711396
}
@@ -1383,7 +1408,10 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa
13831408
DateTime endTime;
13841409
if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year)
13851410
{
1386-
AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.End.Year + 1, 1, 1));
1411+
int? nextYearRuleIndex;
1412+
AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime(
1413+
new DateTime(daylightTime.End.Year + 1, 1, 1),
1414+
out nextYearRuleIndex);
13871415
if (nextYearRule != null && nextYearRule.IsStartDateMarkerForBeginningOfYear())
13881416
{
13891417
if (nextYearRule.IsEndDateMarkerForEndOfYear())
@@ -1393,7 +1421,10 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa
13931421
}
13941422
else
13951423
{
1396-
DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime(daylightTime.End.Year + 1, nextYearRule);
1424+
DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime(
1425+
daylightTime.End.Year + 1,
1426+
nextYearRule,
1427+
nextYearRuleIndex);
13971428
endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta;
13981429
}
13991430
ignoreYearAdjustment = true;
@@ -1650,14 +1681,15 @@ private static bool GetIsInvalidTime(DateTime time, AdjustmentRule rule, Dayligh
16501681
private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags)
16511682
{
16521683
TimeSpan baseOffset = zone.BaseUtcOffset;
1653-
AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time);
1684+
int? ruleIndex;
1685+
AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time, out ruleIndex);
16541686

16551687
if (rule != null)
16561688
{
16571689
baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
16581690
if (rule.HasDaylightSaving)
16591691
{
1660-
DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule);
1692+
DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule, ruleIndex);
16611693
bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags);
16621694
baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
16631695
}
@@ -1696,21 +1728,22 @@ internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, o
16961728
isAmbiguousLocalDst = false;
16971729
TimeSpan baseOffset = zone.BaseUtcOffset;
16981730
int year;
1731+
int? ruleIndex;
16991732
AdjustmentRule rule;
17001733

17011734
if (time > s_maxDateOnly)
17021735
{
1703-
rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue);
1736+
rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue, out ruleIndex);
17041737
year = 9999;
17051738
}
17061739
else if (time < s_minDateOnly)
17071740
{
1708-
rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue);
1741+
rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue, out ruleIndex);
17091742
year = 1;
17101743
}
17111744
else
17121745
{
1713-
rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true);
1746+
rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true, ruleIndex: out ruleIndex);
17141747

17151748
// As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset,
17161749
// sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases:
@@ -1725,7 +1758,7 @@ internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, o
17251758
baseOffset = baseOffset + rule.BaseUtcOffsetDelta;
17261759
if (rule.HasDaylightSaving)
17271760
{
1728-
isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, out isAmbiguousLocalDst, zone);
1761+
isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, ruleIndex, out isAmbiguousLocalDst, zone);
17291762
baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */);
17301763
}
17311764
}

0 commit comments

Comments
 (0)