Skip to content

Commit

Permalink
Added unit tests to DateTest.cs exercising the edge cases where the s…
Browse files Browse the repository at this point in the history
…tart/end times are exactly equal to the daylight savings time transition window start/end

Updated ComputeRealRange to detect when start and end are in the same window, and to handle the edge case where start is exactly the last valid date/time before the window.
  • Loading branch information
logiclrd committed Apr 1, 2021
1 parent 4660f9c commit 347efeb
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 3 deletions.
84 changes: 84 additions & 0 deletions Source/Bogus.Tests/DataSetTests/DateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,90 @@ public void will_adjust_end_time_to_avoid_dst_transition()
}
}

[FactWhenDaylightSavingsSupported]
public void works_when_range_is_exactly_daylight_savings_transition_window()
{
// Arrange
var faker = new Faker();

faker.Random = new Randomizer(localSeed: 5);

var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();

var now = DateTime.Now;

var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));

var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;

// Act & Assert
for (int i = 0; i < 10000; i++)
{
var sample = faker.Date.Between(transitionStartTime, transitionEndTime);

sample.Should().BeOneOf(transitionStartTime, transitionEndTime);
}
}

[FactWhenDaylightSavingsSupported]
public void works_when_range_start_is_exactly_daylight_savings_transition_window_start()
{
// Arrange
var faker = new Faker();

faker.Random = new Randomizer(localSeed: 5);

var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();

var now = DateTime.Now;

var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));

var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;

var windowStart = transitionStartTime;
var windowEnd = transitionEndTime.AddMinutes(-5);

// Act & Assert
for (int i = 0; i < 10000; i++)
{
var sample = faker.Date.Between(windowStart, windowEnd);

sample.Should().Be(windowStart);
}
}

[FactWhenDaylightSavingsSupported]
public void works_when_range_end_is_exactly_daylight_savings_transition_window_end()
{
// Arrange
var faker = new Faker();

faker.Random = new Randomizer(localSeed: 5);

var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();

var now = DateTime.Now;

var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));

var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;

var windowStart = transitionStartTime.AddMinutes(5);
var windowEnd = transitionEndTime;

// Act & Assert
for (int i = 0; i < 10000; i++)
{
var sample = faker.Date.Between(windowStart, windowEnd);

sample.Should().Be(windowEnd);
}
}

private DateTime CalculateTransitionDateTime(DateTime now, TimeZoneInfo.TransitionTime transition)
{
// Based on code found at: https://docs.microsoft.com/en-us/dotnet/api/system.timezoneinfo.transitiontime.isfixeddaterule
Expand Down
23 changes: 20 additions & 3 deletions Source/Bogus/DataSets/Date.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,17 @@ public DateTime Between(DateTime start, DateTime end)
var value = new DateTime(minTicks, DateTimeKind.Utc) + partTimeSpan;

if (start.Kind != DateTimeKind.Utc)
{
value = value.ToLocalTime();

// Right around daylight savings time transition, there can be two different local DateTime values
// that are actually exactly the same DateTime. The ToLocalTime conversion might pick the wrong
// one in edge cases; it will pick the later one, and if the caller's window includes the earlier
// one, we should return that instead to follow the principle of least surprise.
if (value > end)
value = end;
}

return value;
}

Expand Down Expand Up @@ -178,12 +187,20 @@ private void ComputeRealRange(ref DateTime start, ref DateTime end)
#if !NETSTANDARD1_3
var window = GetForwardDSTTransitionWindow(start);

if ((start > window.Start) && (start <= window.End))
start = new DateTime(window.End.Ticks, start.Kind);
if ((start >= window.Start) && (start <= window.End))
{
if ((start == window.Start) && (end >= window.Start) && (end <= window.End))
end = start;
else
start = new DateTime(window.End.Ticks, start.Kind);

if (start == end)
return;
}

window = GetForwardDSTTransitionWindow(end);

if ((end >= window.Start) && (end < window.End))
if ((end >= window.Start) && (end <= window.End))
end = new DateTime(window.Start.Ticks, end.Kind);

if (start > end)
Expand Down

0 comments on commit 347efeb

Please sign in to comment.