diff --git a/Source/Bogus.Tests/DataSetTests/DateTest.cs b/Source/Bogus.Tests/DataSetTests/DateTest.cs index b9373b53..fa88d649 100644 --- a/Source/Bogus.Tests/DataSetTests/DateTest.cs +++ b/Source/Bogus.Tests/DataSetTests/DateTest.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using Bogus.DataSets; using FluentAssertions; using Xunit; @@ -352,5 +353,83 @@ 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); + + var transitionEndTime = transitionStartTime.ToUniversalTime().AddHours(1).ToLocalTime(); + + // Act + var value = faker.Date.Between(transitionStartTime.AddHours(-1), transitionEndTime.AddHours(+1)); + + // Assert + if ((value >= transitionStartTime) && (value < transitionStartTime.AddHours(1))) + value.Should().NotBeBefore(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); + } + + var calendar = CultureInfo.CurrentCulture.Calendar; + + var startOfWeek = transition.Week * 7 - 6; + + 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); + } } } \ No newline at end of file diff --git a/Source/Bogus/DataSets/Date.cs b/Source/Bogus/DataSets/Date.cs index 7a73508c..4862eda5 100644 --- a/Source/Bogus/DataSets/Date.cs +++ b/Source/Bogus/DataSets/Date.cs @@ -44,11 +44,7 @@ public DateTime Past(int yearsToGoBack = 1, DateTime? refDate = null) var minDate = maxDate.AddYears(-yearsToGoBack); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return maxDate - partTimeSpan; + return Between(minDate, maxDate); } /// @@ -62,11 +58,7 @@ public DateTimeOffset PastOffset(int yearsToGoBack = 1, DateTimeOffset? refDate var minDate = maxDate.AddYears(-yearsToGoBack); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return maxDate - partTimeSpan; + return BetweenOffset(minDate, maxDate); } /// @@ -112,11 +104,7 @@ public DateTime Future(int yearsToGoForward = 1, DateTime? refDate = null) var maxDate = minDate.AddYears(yearsToGoForward); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return minDate + partTimeSpan; + return Between(minDate, maxDate); } /// @@ -130,11 +118,7 @@ public DateTimeOffset FutureOffset(int yearsToGoForward = 1, DateTimeOffset? ref var maxDate = minDate.AddYears(yearsToGoForward); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return minDate + partTimeSpan; + return BetweenOffset(minDate, maxDate); } /// @@ -144,14 +128,22 @@ public DateTimeOffset FutureOffset(int yearsToGoForward = 1, DateTimeOffset? ref /// End time public DateTime Between(DateTime start, DateTime end) { - var minTicks = Math.Min(start.Ticks, end.Ticks); - var maxTicks = Math.Max(start.Ticks, end.Ticks); + var startTicks = start.ToUniversalTime().Ticks; + var endTicks = end.ToUniversalTime().Ticks; + + var minTicks = Math.Min(startTicks, endTicks); + var maxTicks = Math.Max(startTicks, endTicks); var totalTimeSpanTicks = maxTicks - minTicks; var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - return new DateTime(minTicks, start.Kind) + partTimeSpan; + var value = new DateTime(minTicks, DateTimeKind.Utc) + partTimeSpan; + + if (start.Kind != DateTimeKind.Utc) + value = value.ToLocalTime(); + + return value; } /// @@ -161,14 +153,19 @@ public DateTime Between(DateTime start, DateTime end) /// End time public DateTimeOffset BetweenOffset(DateTimeOffset start, DateTimeOffset end) { - var minTicks = Math.Min(start.Ticks, end.Ticks); - var maxTicks = Math.Max(start.Ticks, end.Ticks); + var startTicks = start.ToUniversalTime().Ticks; + var endTicks = end.ToUniversalTime().Ticks; + + var minTicks = Math.Min(startTicks, endTicks); + var maxTicks = Math.Max(startTicks, endTicks); var totalTimeSpanTicks = maxTicks - minTicks; var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - return new DateTimeOffset(minTicks, start.Offset) + partTimeSpan; + var dateTime = new DateTime(minTicks, DateTimeKind.Unspecified) + partTimeSpan; + + return new DateTimeOffset(dateTime + start.Offset, start.Offset); } /// @@ -182,11 +179,7 @@ public DateTime Recent(int days = 1, DateTime? refDate = null) var minDate = days == 0 ? SystemClock().Date : maxDate.AddDays(-days); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return maxDate - partTimeSpan; + return Between(minDate, maxDate); } /// @@ -200,11 +193,7 @@ public DateTimeOffset RecentOffset(int days = 1, DateTimeOffset? refDate = null) var minDate = days == 0 ? SystemClock().Date : maxDate.AddDays(-days); - var totalTimeSpanTicks = (maxDate - minDate).Ticks; - - var partTimeSpan = RandomTimeSpanFromTicks(totalTimeSpanTicks); - - return maxDate - partTimeSpan; + return BetweenOffset(minDate, maxDate); } ///