Skip to content

Commit

Permalink
Allow locally set time references for Date and Faker[T] (#500)
Browse files Browse the repository at this point in the history
* Move to locally defined DateTime and away from Date.SystemClock.

* Add additional unit tests by @garcipat from #508

* Xml comment fix from @garcipat from #508

* Start documentation on new local/global datetime reference seeding.

* Small tweaks to documentation; use .DateTimeReference property for Faker.

* Give credit to @garcipat for his PR work.
  • Loading branch information
bchavez authored Mar 9, 2024
1 parent 85071b2 commit d9d91cd
Show file tree
Hide file tree
Showing 13 changed files with 559 additions and 49 deletions.
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## vNext
Release Date: Unreleased
* PR 500: Allows locally set time references for Date calculations instead of global statics. See Faker[T].UseDateTimeReference(), Faker.DateTimeReference, and DataSets.Date.LocalSystemClock. Thanks @garcipat!

## v35.4.1
Release Date: 2024-03-02
* PR 529: XML Docs: Add inclusive / exclusive number ranges documentation for Randomizer. Thanks @Mitchman215!
Expand Down
38 changes: 21 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1032,28 +1032,32 @@ As a general rule of thumb,

**Bogus** can generate deterministic dates and times. However, generating deterministic dates and times requires the following:

1. Setting up a [local or global](#determinism) seed value.
1. Setting up a global anchor source of time in `Bogus.DataSets.Date.SystemClock`.
1. Setting up a [seed value](#determinism).
1. Setting up a time reference for your Faker object instance.

The following code shows how to setup deterministic dates and times:

```csharp
// Setup some kind of seed, global or local. Here, we use a global seed.
Randomizer.Seed = new Random(1338);

// Setup a static source of time.
Bogus.DataSets.Date.SystemClock = () => DateTime.Parse("8/8/2019 2:00 PM");

// Now use Bogus as you normally would. All dates and times
// generated by Bogus should now be deterministic.
var p = new Person();
p.DateOfBirth; // 1996-06-09T15:38:11
var f = new Faker();
f.Date.Past(); // 2018-09-29T07:42:26
f.Date.Future(); // 2020-02-13T08:10:27
// Faker[T]: Set a local seed and a time reference
var fakerT = new Faker<Order>()
.UseSeed(1338)
.UseDateTimeReference(DateTime.Parse("1/1/1980"))
.RuleFor(o => o.SoonValue, f => f.Date.Soon())
.RuleFor(o => o.RecentValue, f => f.Date.Recent());
fakerT.Generate().Dump();
// { "SoonValue": "1980-01-01T17:33:05",
// "RecentValue": "1979-12-31T14:07:31" }
// Faker: Set a local seed and a time reference
var faker = new Faker
{
Random = new Randomizer(1338),
DateTimeReference = DateTime.Parse("1/1/1980")
};
faker.Date.Soon(); // "1980-01-01T17:33:05"
faker.Date.Recent(); // "1979-12-31T14:07:31"
```
With the `Bogus.DataSets.Date.SystemClock` set and a [local or global](#determinism) seed, dates and times should be deterministic across multiple runs of a program.
With a time reference set and a [seed](#determinism), dates and times should be deterministic across multiple runs of a program.


F# and VB.NET Examples
Expand Down
24 changes: 19 additions & 5 deletions Source/Bogus.Tests/CloneTests.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
using FluentAssertions;
using System;
using Xunit;

namespace Bogus.Tests;

public class CloneTests : SeededTest
{
public class Order
{
public int OrderId { get; set; }
public string Item { get; set; }
public int Quantity { get; set; }
public int? LotNumber { get; set; }
public DateTime Created { get; set; }
}

[Fact]
public void can_create_a_simple_clone()
{
var orderFaker = new Faker<Examples.Order>()
var orderFaker = new Faker<Order>()
.UseSeed(88)
.UseDateTimeReference(new DateTime(2022, 2, 2))
.RuleFor(o => o.OrderId, f => f.IndexVariable++)
.RuleFor(o => o.Quantity, f => f.Random.Number(1, 3))
.RuleFor(o => o.Item, f => f.Commerce.Product());
.RuleFor(o => o.Item, f => f.Commerce.Product())
.RuleFor(o => o.Created, f => f.Date.Recent());

var clone = orderFaker.Clone();

var clonedOrder = clone.Generate();

var rootOrder = orderFaker.Generate();

clonedOrder.Should().BeEquivalentTo(rootOrder);
clonedOrder.Created.Should().BeAtLeast(TimeSpan.FromDays(1));
}

[Fact]
public void clone_has_different_rules()
{
var rootFaker = new Faker<Examples.Order>()
var rootFaker = new Faker<Order>()
.UseSeed(88)
.UseDateTimeReference(new DateTime(2022, 2, 2))
.RuleFor(o => o.OrderId, f => f.IndexVariable++)
.RuleFor(o => o.Quantity, f => f.Random.Number(1, 3))
.RuleFor(o => o.Item, f => f.Commerce.Product());
.RuleFor(o => o.Item, f => f.Commerce.Product())
.RuleFor(o => o.Created, f => f.Date.Recent());

var cloneFaker = rootFaker.Clone()
.RuleFor(o => o.Quantity, f => f.Random.Number(4, 6));
Expand Down
114 changes: 114 additions & 0 deletions Source/Bogus.Tests/DataSetTests/DateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public void can_get_date_in_future()
.BeOnOrAfter(starting);
}

[Fact]
public void can_get_date_in_future_with_set_clock()
{
var refDate = DateTime.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate;
date.Future().Should()
.BeOnOrBefore(refDate.AddYears(1))
.And
.BeOnOrAfter(refDate);
}

[Fact]
public void can_get_dateOffset_in_future()
{
Expand All @@ -59,6 +70,16 @@ public void can_get_dateOffset_in_future()
.And
.BeOnOrAfter(starting);
}
[Fact]
public void can_get_dateOffset_in_future_with_set_clock()
{
var refDate = DateTimeOffset.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate.DateTime;
date.FutureOffset().Should()
.BeOnOrBefore(refDate.AddYears(1))
.And
.BeOnOrAfter(refDate);
}

[Fact]
public void can_get_date_in_future_with_options()
Expand Down Expand Up @@ -90,6 +111,17 @@ public void can_get_date_in_past()
.BeOnOrAfter(starting.AddYears(-1));
}

[Fact]
public void can_get_date_in_past_with_set_clock()
{
var refDate = DateTime.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate;
date.Past().Should()
.BeOnOrBefore(refDate)
.And
.BeOnOrAfter(refDate.AddYears(-1));
}

[Fact]
public void can_get_dateOffset_in_past()
{
Expand All @@ -100,6 +132,17 @@ public void can_get_dateOffset_in_past()
.BeOnOrAfter(starting.AddYears(-1));
}

[Fact]
public void can_get_dateOffset_in_past_with_set_clock()
{
var refDate = DateTimeOffset.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate.DateTime;
date.PastOffset().Should()
.BeOnOrBefore(refDate)
.And
.BeOnOrAfter(refDate.AddYears(-1));
}

[Fact]
public void can_get_date_in_past_0_days_results_in_random_time()
{
Expand Down Expand Up @@ -149,6 +192,17 @@ public void can_get_date_recently_within_the_year()
.BeOnOrAfter(start.AddDays(-1));
}

[Fact]
public void can_get_date_recently_with_set_clock()
{
var refDate = DateTime.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate;
date.Recent().Should()
.BeOnOrBefore(refDate)
.And
.BeOnOrAfter(refDate.AddDays(-1));
}

[Fact]
public void can_get_dateOffset_recently_within_the_year()
{
Expand All @@ -160,6 +214,17 @@ public void can_get_dateOffset_recently_within_the_year()
.BeOnOrAfter(start.AddDays(-1));
}

[Fact]
public void can_get_dateOffset_recently_with_set_clock()
{
var refDate = DateTimeOffset.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate.DateTime;
date.RecentOffset().Should()
.BeOnOrBefore(refDate)
.And
.BeOnOrAfter(refDate.AddDays(-1));
}

[Fact]
public void can_get_random_time_between_two_dates()
{
Expand Down Expand Up @@ -207,13 +272,35 @@ public void get_a_date_that_will_happen_soon()
date.Soon(3).Should().BeAfter(now).And.BeBefore(now.AddDays(3));
}

[Fact]
public void can_get_date_soon_with_set_clock()
{
var refDate = DateTime.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate;
date.Soon().Should()
.BeOnOrAfter(refDate)
.And
.BeBefore(refDate.AddDays(1));
}

[Fact]
public void get_a_dateOffsets_that_will_happen_soon()
{
var now = DateTimeOffset.Now;
date.SoonOffset(3).Should().BeAfter(now).And.BeBefore(now.AddDays(3));
}

[Fact]
public void can_get_dateOffset_soon_with_set_clock()
{
var refDate = DateTimeOffset.Parse("6/7/2015 4:17:41 PM");
date.LocalSystemClock = () => refDate.DateTime;
date.SoonOffset().Should()
.BeOnOrAfter(refDate)
.And
.BeBefore(refDate.AddDays(1));
}

[Fact]
public void soon_explicit_refdate_in_utc_should_return_utc_kind()
{
Expand Down Expand Up @@ -347,6 +434,33 @@ public void can_set_global_static_time_source()
d.RecentOffset().Offset.Should().Be(DateTimeOffset.Now.Offset);
}

[Fact]
public void use_dataset_localclock_date_if_set()
{
var refDate = new DateTime(2009, 12, 30, 12, 30, 0);
var d = new Date() { LocalSystemClock = () => refDate };


d.Recent(0).Should()
.BeOnOrBefore(refDate)
.And
.BeOnOrAfter(refDate.Date);
}

[Fact]
public void use_now_param_over_localclock_date()
{
var refDate = new DateTime(2009, 12, 30, 12, 30, 0);
var now = DateTime.Now;

var d = new Date { LocalSystemClock = () => refDate };

d.Recent(0, now).Should()
.BeOnOrBefore(now)
.And
.BeOnOrAfter(now.Date);
}

[Fact]
public void can_get_timezone_string()
{
Expand Down
Loading

0 comments on commit d9d91cd

Please sign in to comment.