diff --git a/Dummies.UnitTests/AnyDateTimeOffsetOffsetTests.cs b/Dummies.UnitTests/AnyDateTimeOffsetOffsetTests.cs
new file mode 100644
index 0000000..8d995fe
--- /dev/null
+++ b/Dummies.UnitTests/AnyDateTimeOffsetOffsetTests.cs
@@ -0,0 +1,81 @@
+#region Usings declarations
+
+using NFluent;
+
+#endregion
+
+namespace Dummies.UnitTests;
+
+///
+/// Behaviour of the offset dimension — WithOffset pins it,
+/// WithOffsetBetween draws it bounded, the default stays UTC, and values stay valid at the domain edges
+/// because the instant is tightened before the offset is drawn.
+///
+public sealed class AnyDateTimeOffsetOffsetTests {
+
+ private const int SampleCount = 200;
+
+ [Fact(DisplayName = "Offset: unconstrained, generated values carry UTC (zero) offset.")]
+ public void DefaultOffsetIsZero() {
+ for (int i = 0; i < SampleCount; i++) {
+ Check.That(Any.DateTimeOffset().Generate().Offset).IsEqualTo(TimeSpan.Zero);
+ }
+ }
+
+ [Fact(DisplayName = "WithOffset: every generated value carries the pinned offset.")]
+ public void WithOffsetPins() {
+ TimeSpan offset = TimeSpan.FromHours(2);
+ for (int i = 0; i < SampleCount; i++) {
+ Check.That(Any.DateTimeOffset().WithOffset(offset).Generate().Offset).IsEqualTo(offset);
+ }
+ }
+
+ [Fact(DisplayName = "WithOffsetBetween: offsets stay within the range, in whole minutes, and vary.")]
+ public void WithOffsetBetweenBounds() {
+ TimeSpan min = TimeSpan.FromHours(-5);
+ TimeSpan max = TimeSpan.FromHours(5);
+ HashSet seen = new();
+ for (int i = 0; i < SampleCount; i++) {
+ DateTimeOffset value = Any.DateTimeOffset().WithOffsetBetween(min, max).Generate();
+ Check.That(value.Offset >= min && value.Offset <= max).IsTrue();
+ Check.That(value.Offset.Ticks % TimeSpan.TicksPerMinute).IsEqualTo(0L);
+ seen.Add(value.Offset);
+ }
+
+ Check.That(seen.Count).IsStrictlyGreaterThan(1);
+ }
+
+ [Fact(DisplayName = "WithOffset: stays valid and after the floor at the top of the domain.")]
+ public void WithOffsetValidNearMaxValue() {
+ DateTimeOffset floor = DateTimeOffset.MaxValue.AddDays(-1);
+ for (int i = 0; i < SampleCount; i++) {
+ DateTimeOffset value = Any.DateTimeOffset().After(floor).WithOffset(TimeSpan.FromHours(14)).Generate();
+ Check.That(value.Offset).IsEqualTo(TimeSpan.FromHours(14));
+ Check.That(value.UtcTicks > floor.UtcTicks).IsTrue();
+ }
+ }
+
+ [Fact(DisplayName = "WithOffset: an instant window with no room for the offset conflicts eagerly.")]
+ public void WithOffsetImpossibleWindowConflicts() {
+ // The last 12h of the domain cannot host a +14h offset: the local ticks would overflow.
+ Check.ThatCode(() => Any.DateTimeOffset().After(DateTimeOffset.MaxValue.AddHours(-12)).WithOffset(TimeSpan.FromHours(14)))
+ .Throws();
+ }
+
+ [Fact(DisplayName = "WithOffset: arguments are validated (whole minutes, ±14:00, ordered range).")]
+ public void WithOffsetArguments() {
+ Check.ThatCode(() => Any.DateTimeOffset().WithOffset(TimeSpan.FromSeconds(30))).Throws();
+ Check.ThatCode(() => Any.DateTimeOffset().WithOffset(TimeSpan.FromHours(15))).Throws();
+ Check.ThatCode(() => Any.DateTimeOffset().WithOffsetBetween(TimeSpan.FromHours(2), TimeSpan.FromHours(-2))).Throws();
+ }
+
+ [Fact(DisplayName = "WithOffset: a second, different offset is rejected as already declared.")]
+ public void WithOffsetDeclaredOnce() {
+ Check.ThatCode(() => Any.DateTimeOffset().WithOffset(TimeSpan.FromHours(2)).WithOffset(TimeSpan.FromHours(3)))
+ .Throws();
+ // The same offset twice is idempotent, not a conflict.
+ Check.That(Any.DateTimeOffset().WithOffset(TimeSpan.FromHours(2)).WithOffset(TimeSpan.FromHours(2)).Generate().Offset)
+ .IsEqualTo(TimeSpan.FromHours(2));
+ }
+
+}
diff --git a/Dummies.UnitTests/SurfaceParityTests.cs b/Dummies.UnitTests/SurfaceParityTests.cs
index a8b16bf..360bb3b 100644
--- a/Dummies.UnitTests/SurfaceParityTests.cs
+++ b/Dummies.UnitTests/SurfaceParityTests.cs
@@ -140,6 +140,13 @@ private static string Signature(MethodInfo method) {
"Between", "OneOf", "Except", "DifferentFrom", "WithGranularity"
];
+ // AnyDateTimeOffset additionally exposes the offset dimension (WithOffset/WithOffsetBetween) — the only instant
+ // type carrying a second, offset dimension on top of the instant.
+ private static readonly string[] InstantWithGranularityAndOffsetAlgebra = [
+ "After", "AfterOrEqualTo", "Before", "BeforeOrEqualTo",
+ "Between", "OneOf", "Except", "DifferentFrom", "WithGranularity", "WithOffset", "WithOffsetBetween"
+ ];
+
public static IEnumerable