Skip to content

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>().

  • TestabilityFakeTimeProvider lets 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.UtcNow create hidden dependencies that break test isolation

Quick Start

[Runtime]
[Uses(typeof(ClockModule))]
public sealed partial class AppRuntime;

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

// Get time through the effects pipeline
from now in ClockModule.Clock.GetUtcNow<AppRuntime>()
from _ in AppStore.Orders.Save<AppRuntime>(
    order with { CreatedAt = now })
select unit;
// Direct time access — breaks testability
var now = DateTimeOffset.UtcNow;  // BAD
from _ in AppStore.Orders.Save<AppRuntime>(
    order with { CreatedAt = now })
select unit;

Interface

public interface IClock
{
    DateTimeOffset GetUtcNow();
}

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:

public static Eff<RT, Token> CreateToken<RT>(string userId)
    where RT : IHasClock =>
    from now in ClockModule.Clock.GetUtcNow<RT>()
    select new Token(userId, ExpiresAt: now.AddHours(1));