Skip to content

Commit 347efeb

Browse files
committed
Added unit tests to DateTest.cs exercising the edge cases where the start/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.
1 parent 4660f9c commit 347efeb

File tree

2 files changed

+104
-3
lines changed

2 files changed

+104
-3
lines changed

Source/Bogus.Tests/DataSetTests/DateTest.cs

+84
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,90 @@ public void will_adjust_end_time_to_avoid_dst_transition()
460460
}
461461
}
462462

463+
[FactWhenDaylightSavingsSupported]
464+
public void works_when_range_is_exactly_daylight_savings_transition_window()
465+
{
466+
// Arrange
467+
var faker = new Faker();
468+
469+
faker.Random = new Randomizer(localSeed: 5);
470+
471+
var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();
472+
473+
var now = DateTime.Now;
474+
475+
var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));
476+
477+
var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
478+
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;
479+
480+
// Act & Assert
481+
for (int i = 0; i < 10000; i++)
482+
{
483+
var sample = faker.Date.Between(transitionStartTime, transitionEndTime);
484+
485+
sample.Should().BeOneOf(transitionStartTime, transitionEndTime);
486+
}
487+
}
488+
489+
[FactWhenDaylightSavingsSupported]
490+
public void works_when_range_start_is_exactly_daylight_savings_transition_window_start()
491+
{
492+
// Arrange
493+
var faker = new Faker();
494+
495+
faker.Random = new Randomizer(localSeed: 5);
496+
497+
var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();
498+
499+
var now = DateTime.Now;
500+
501+
var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));
502+
503+
var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
504+
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;
505+
506+
var windowStart = transitionStartTime;
507+
var windowEnd = transitionEndTime.AddMinutes(-5);
508+
509+
// Act & Assert
510+
for (int i = 0; i < 10000; i++)
511+
{
512+
var sample = faker.Date.Between(windowStart, windowEnd);
513+
514+
sample.Should().Be(windowStart);
515+
}
516+
}
517+
518+
[FactWhenDaylightSavingsSupported]
519+
public void works_when_range_end_is_exactly_daylight_savings_transition_window_end()
520+
{
521+
// Arrange
522+
var faker = new Faker();
523+
524+
faker.Random = new Randomizer(localSeed: 5);
525+
526+
var dstRules = TimeZoneInfo.Local.GetAdjustmentRules();
527+
528+
var now = DateTime.Now;
529+
530+
var effectiveRule = dstRules.Single(rule => (rule.DateStart <= now) && (rule.DateEnd >= now));
531+
532+
var transitionStartTime = CalculateTransitionDateTime(now, effectiveRule.DaylightTransitionStart);
533+
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta;
534+
535+
var windowStart = transitionStartTime.AddMinutes(5);
536+
var windowEnd = transitionEndTime;
537+
538+
// Act & Assert
539+
for (int i = 0; i < 10000; i++)
540+
{
541+
var sample = faker.Date.Between(windowStart, windowEnd);
542+
543+
sample.Should().Be(windowEnd);
544+
}
545+
}
546+
463547
private DateTime CalculateTransitionDateTime(DateTime now, TimeZoneInfo.TransitionTime transition)
464548
{
465549
// Based on code found at: https://docs.microsoft.com/en-us/dotnet/api/system.timezoneinfo.transitiontime.isfixeddaterule

Source/Bogus/DataSets/Date.cs

+20-3
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,17 @@ public DateTime Between(DateTime start, DateTime end)
144144
var value = new DateTime(minTicks, DateTimeKind.Utc) + partTimeSpan;
145145

146146
if (start.Kind != DateTimeKind.Utc)
147+
{
147148
value = value.ToLocalTime();
148149

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

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

181-
if ((start > window.Start) && (start <= window.End))
182-
start = new DateTime(window.End.Ticks, start.Kind);
190+
if ((start >= window.Start) && (start <= window.End))
191+
{
192+
if ((start == window.Start) && (end >= window.Start) && (end <= window.End))
193+
end = start;
194+
else
195+
start = new DateTime(window.End.Ticks, start.Kind);
196+
197+
if (start == end)
198+
return;
199+
}
183200

184201
window = GetForwardDSTTransitionWindow(end);
185202

186-
if ((end >= window.Start) && (end < window.End))
203+
if ((end >= window.Start) && (end <= window.End))
187204
end = new DateTime(window.Start.Ticks, end.Kind);
188205

189206
if (start > end)

0 commit comments

Comments
 (0)