-
-
Notifications
You must be signed in to change notification settings - Fork 499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the Date data set to avoid DST transition windows #366
base: master
Are you sure you want to change the base?
Changes from all commits
18e0cfb
b598ea0
718a240
41cf3ed
bf1ef28
93edfa9
de19a3b
5c6013e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,23 @@ | ||
using System; | ||
using System.Globalization; | ||
using System.Linq; | ||
using Bogus.DataSets; | ||
using FluentAssertions; | ||
using FluentAssertions.Execution; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace Bogus.Tests.DataSetTests | ||
{ | ||
public partial class DateTest : SeededTest | ||
{ | ||
public DateTest() | ||
private readonly ITestOutputHelper _testOutput; | ||
|
||
public DateTest(ITestOutputHelper testOutput) | ||
{ | ||
date = new Date(); | ||
|
||
_testOutput = testOutput; | ||
} | ||
|
||
private readonly Date date; | ||
|
@@ -352,5 +359,285 @@ public void can_get_timezone_string() | |
{ | ||
date.TimeZoneString().Should().Be("Asia/Yerevan"); | ||
} | ||
|
||
public class FactWhenDaylightSavingsSupported : FactAttribute | ||
{ | ||
public FactWhenDaylightSavingsSupported() | ||
{ | ||
if (!TimeZoneInfo.Local.SupportsDaylightSavingTime) | ||
{ | ||
Skip = "Test is only meaningful when Daylight Savings is supported by the local timezone."; | ||
} | ||
} | ||
} | ||
|
||
[FactWhenDaylightSavingsSupported] | ||
public void will_not_generate_values_that_do_not_exist_due_to_daylight_savings() | ||
{ | ||
// 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); | ||
Comment on lines
+378
to
+388
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New to this code base, but what's the consensus on generating test helper methods or classes that can contain boiler plate construction and arrangement of objects? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My convention when writing tests has been to make the test methods completely logically independent of one another, but I have seen tests and test suites that reuse a great deal of setup. This could be refactored, but was written this way intentionally (if without a great deal of thought, per se). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also prefer consolidation of the test initialization, what's your opinion @bchavez? |
||
|
||
// When converting back, .NET picks the end of the transition window instead of the start. | ||
var transitionEndTime = transitionStartTime.ToUniversalTime().ToLocalTime(); | ||
|
||
// Act | ||
var value = faker.Date.Between(transitionStartTime.AddHours(-1), transitionEndTime.AddHours(+2)); | ||
|
||
// Assert | ||
logiclrd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
using (new AssertionScope()) | ||
{ | ||
transitionEndTime.Should().NotBe(transitionStartTime); | ||
|
||
if ((value >= transitionStartTime) && (value < transitionStartTime.AddHours(1))) | ||
value.Should().NotBeBefore(transitionEndTime); | ||
} | ||
} | ||
|
||
[FactWhenDaylightSavingsSupported] | ||
public void will_adjust_start_time_to_avoid_dst_transition() | ||
{ | ||
// 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); | ||
Comment on lines
+410
to
+420
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we can probably toss all of this initial setup into a base method (except for the rule) and just call the method at the beginning of the tests |
||
var transitionEndTime = transitionStartTime + effectiveRule.DaylightDelta; | ||
|
||
var windowStart = transitionStartTime + TimeSpan.FromTicks((transitionEndTime - transitionStartTime).Ticks / 2); | ||
var windowEnd = transitionEndTime.AddMinutes(30); | ||
|
||
// Act & Assert | ||
using (new AssertionScope()) | ||
{ | ||
bool haveSampleThatIsNotWindowEnd = false; | ||
|
||
for (int i = 0; i < 10000; i++) | ||
{ | ||
var sample = faker.Date.Between(windowStart, windowEnd); | ||
|
||
sample.Should().BeOnOrAfter(transitionEndTime); | ||
sample.Should().BeOnOrBefore(windowEnd); | ||
|
||
haveSampleThatIsNotWindowEnd = (sample < windowEnd); | ||
} | ||
|
||
haveSampleThatIsNotWindowEnd.Should().BeTrue(because: $"the effective range should include values other than {nameof(windowEnd)}"); | ||
} | ||
} | ||
|
||
[FactWhenDaylightSavingsSupported] | ||
public void will_adjust_end_time_to_avoid_dst_transition() | ||
{ | ||
// 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(-30); | ||
var windowEnd = transitionStartTime + TimeSpan.FromTicks((transitionEndTime - transitionStartTime).Ticks / 2); | ||
|
||
// Act & Assert | ||
using (new AssertionScope()) | ||
{ | ||
for (int i = 0; i < 10000; i++) | ||
{ | ||
var sample = faker.Date.Between(windowStart, windowEnd); | ||
|
||
sample.Should().BeOnOrAfter(windowStart); | ||
sample.Should().BeOnOrBefore(transitionStartTime); | ||
} | ||
} | ||
} | ||
|
||
[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 | ||
using (new AssertionScope()) | ||
{ | ||
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 | ||
using (new AssertionScope()) | ||
{ | ||
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 | ||
using (new AssertionScope()) | ||
{ | ||
for (int i = 0; i < 10000; i++) | ||
{ | ||
var sample = faker.Date.Between(windowStart, windowEnd); | ||
|
||
sample.Should().Be(windowEnd); | ||
} | ||
} | ||
} | ||
|
||
[FactWhenDaylightSavingsSupported] | ||
public void works_when_start_time_is_invalid_due_to_DST_change_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; | ||
|
||
var date1 = new DateTimeOffset(transitionEndTime.AddMinutes(-1), TimeZoneInfo.Local.BaseUtcOffset); | ||
var date2 = new DateTimeOffset(transitionEndTime, TimeZoneInfo.Local.BaseUtcOffset + effectiveRule.DaylightDelta); | ||
|
||
// Act | ||
var sample = faker.Date.BetweenOffset(date1, date2); | ||
|
||
// Assert | ||
_testOutput.WriteLine("BetweenOffset result: {0}", sample); | ||
|
||
sample.Should().Be(transitionEndTime); | ||
} | ||
|
||
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 | ||
|
||
if (transition.IsFixedDateRule) | ||
{ | ||
return new DateTime( | ||
now.Year, | ||
transition.Month, | ||
transition.Day, | ||
transition.TimeOfDay.Hour, | ||
transition.TimeOfDay.Minute, | ||
transition.TimeOfDay.Second, | ||
transition.TimeOfDay.Millisecond, | ||
DateTimeKind.Local); | ||
} | ||
|
||
var calendar = CultureInfo.CurrentCulture.Calendar; | ||
|
||
var startOfWeek = transition.Week * 7 - 6; | ||
logiclrd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var firstDayOfWeek = (int)calendar.GetDayOfWeek(new DateTime(now.Year, transition.Month, 1)); | ||
var changeDayOfWeek = (int)transition.DayOfWeek; | ||
|
||
int transitionDay = | ||
firstDayOfWeek <= changeDayOfWeek | ||
? startOfWeek + changeDayOfWeek - firstDayOfWeek | ||
: startOfWeek + changeDayOfWeek - firstDayOfWeek + 7; | ||
|
||
if (transitionDay > calendar.GetDaysInMonth(now.Year, transition.Month)) | ||
transitionDay -= 7; | ||
|
||
return new DateTime( | ||
now.Year, | ||
transition.Month, | ||
transitionDay, | ||
transition.TimeOfDay.Hour, | ||
transition.TimeOfDay.Minute, | ||
transition.TimeOfDay.Second, | ||
transition.TimeOfDay.Millisecond, | ||
DateTimeKind.Local); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, I had no clue this was a thing.