Clock¶
The Clock module wraps IClock as a composable effect, giving your handlers access to the current time through the runtime — and making time fully controllable in tests.
Always use ClockModule for time
Never use DateTime.UtcNow or DateTimeOffset.UtcNow directly in handlers or effect pipelines. Always use ClockModule.Clock.GetUtcNow<RT>().
- Testability —
FakeTimeProviderlets tests freeze, advance, and control time deterministically - Consistency — all time flows through the effects pipeline with tracing and error context
- Correctness — direct calls to
DateTime.UtcNowcreate hidden dependencies that break test isolation
Quick Start¶
Use the generated effect methods in pipelines:
from now in ClockModule.Clock.GetUtcNow<AppRuntime>()
select new Token(userId, ExpiresAt: now.AddHours(1));
Do / Don't¶
Interface¶
The default implementation, TimeProviderClock, delegates to TimeProvider.System.
Common Patterns¶
Timestamping records in command handlers¶
[CommandHandler]
public static Eff<AppRuntime, OrderCreated> Handle(CreateOrder cmd) =>
from now in ClockModule.Clock.GetUtcNow<AppRuntime>()
from _ in AppStore.Orders.Save<AppRuntime>(
new Order(OrderId.New(), cmd.Name, cmd.Quantity, CreatedAt: now))
select new OrderCreated(cmd.Name);
Filtering by time window in query handlers¶
[QueryHandler]
public static Eff<AppRuntime, QueryResult<AuditEntry>> Handle(GetRecentActivity query) =>
from now in ClockModule.Clock.GetUtcNow<AppRuntime>()
let cutoff = now.AddHours(-24)
from entries in AppStore.AuditEntries.Query<AppRuntime>(
e => e.Timestamp >= cutoff)
select entries;
Checking expiration in scheduled jobs¶
[JobHandler]
public static Eff<AppRuntime, Unit> Handle(ExpireStaleTokens job) =>
from now in ClockModule.Clock.GetUtcNow<AppRuntime>()
from stale in AppStore.Tokens.Query<AppRuntime>(
t => t.ExpiresAt < now)
from _ in stale.Traverse(t => AppStore.Tokens.Delete<AppRuntime>(t.Id))
select unit;
Using static import for brevity
With using static Deepstaging.Clock.ClockModule; you can write Clock.GetUtcNow<RT>() directly — the same pattern used in the walkthrough.
Default Implementation¶
public sealed class TimeProviderClock(TimeProvider provider) : IClock
{
public TimeProviderClock() : this(TimeProvider.System) { }
public DateTimeOffset GetUtcNow() => provider.GetUtcNow();
}
Registered as a singleton via TryAddSingleton<IClock, TimeProviderClock>.
Testing¶
Use FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing to control time in tests:
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
services.AddSingleton<IClock>(new TimeProviderClock(fakeTime));
// Advance time
fakeTime.Advance(TimeSpan.FromHours(1));
Because ClockModule uses TryAddSingleton, registering your fake clock before the runtime bootstrapper ensures it takes precedence.
Effects Composition¶
ClockModule is an [EffectsModule] wrapping IClock. The generator produces a nested Clock class with effect methods:
// Generated — nested inside ClockModule
public static partial class Clock
{
public static Eff<RT, DateTimeOffset> GetUtcNow<RT>()
where RT : IHasClock => ...
}
Use it in effect pipelines: