Skip to main content

OrionClock

OrionClock

CI/CD NuGet

A TimeProvider-based clock for the Orion family. Time is the most-faked and worst-faked dependency in a .NET backend: teams reach for DateTime.UtcNow, sprinkle it through domain code, then discover none of it is testable. .NET 8 shipped TimeProvider — the right primitive — but it deliberately left out a schedule/TTL vocabulary, so every package re-derives its own expiry math and flaky "wait for it to expire" tests.

OrionClock is the thin, opinionated layer on top. It is a TimeProvider (so it drops into any BCL API that takes one), it is the family's IOrionClock, and it adds the Ttl/Deadline vocabulary the whole suite shares — deterministic under a fake clock that advances the entire suite at once.

Features​

  • It is a TimeProvider — anything that accepts a TimeProvider (CancellationTokenSource, Task.Delay, timers) accepts an OrionClock. Subclasses the BCL primitive rather than replacing it.
  • It is the family's IOrionClock — one clock, registered as TimeProvider and IOrionClock, so every Orion package reads the same time source.
  • Ttl — a time-to-live as a first-class value (issue instant + expiry captured together). IsExpired, Remaining (clamped non-negative), ToCancellationTokenSource for TTL-driven cancellation.
  • Deadline — a "must complete by" instant with IsPast, TimeRemaining, and deadline-driven cancellation.
  • Deterministic in tests — FakeOrionClock (in OrionClock.Testing) freezes time and only moves on Advance/SetUtcNow. Because it is built on TimeProvider, advancing it fires the timers and cancellation sources created through it — so a 5-minute TTL is expired after Advance(6m) and not after Advance(4m), with no real delay.
  • One-line DI (AddOrionClock) — registers via TryAdd, so a consumer override wins and calling it twice is a no-op.
  • AOT- and trim-clean, verified by a native-binary smoke test in CI. Multi-targets net8.0, net9.0, net10.0.

Install​

dotnet add package OrionClock

# Testing companion (FakeOrionClock), reference from test projects only
dotnet add package OrionClock.Testing

Quick start​

using Microsoft.Extensions.DependencyInjection;
using Moongazing.Orion.Abstractions.Time;
using Moongazing.OrionClock;

var services = new ServiceCollection();
services.AddOrionClock(); // registers OrionClock as TimeProvider AND IOrionClock

using var provider = services.BuildServiceProvider();
var clock = provider.GetRequiredService<IOrionClock>();

// A TTL as a value, not a raw DateTimeOffset you must remember to compare in UTC.
Ttl ttl = clock.TtlFor(TimeSpan.FromMinutes(5));
if (ttl.IsExpired(clock)) { /* ... */ }
TimeSpan left = ttl.Remaining(clock); // never negative

// A deadline that drives cancellation.
Deadline deadline = clock.Deadline(TimeSpan.FromSeconds(30));
using var cts = deadline.ToCancellationTokenSource((OrionClock)clock);
await DoWorkAsync(cts.Token);

Testing​

Point the clock at FakeOrionClock and advance time by hand — no real delays, no flakiness.

using Moongazing.OrionClock;
using Moongazing.OrionClock.Testing;

var clock = new FakeOrionClock(); // frozen at 2026-01-01Z by default
var ttl = clock.TtlFor(TimeSpan.FromMinutes(5));

clock.Advance(TimeSpan.FromMinutes(4));
Assert.False(ttl.IsExpired(clock));

clock.Advance(TimeSpan.FromMinutes(2)); // now +6m
Assert.True(ttl.IsExpired(clock));

Register the fake through options so the whole graph uses it:

var fake = new FakeOrionClock();
services.AddOrionClock(o => o.TimeProvider = fake);

Because FakeOrionClock is a TimeProvider, a CancellationTokenSource built from a Ttl or Deadline cancels exactly when you advance past it — deterministically.

Versioning​

Follows Semantic Versioning. Multi-targets net8.0, net9.0, and net10.0. Binds to Orion.Abstractions 1.x.

Documentation​

Contributing​

Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.

More from the Orion family​

Focused .NET libraries built to one quality bar. Each is usable on its own; several share the small Orion.Abstractions contracts spine, but there is no deep dependency web — pick only what you need:

  • OrionGuard — validation, guard clauses, DDD primitives, domain events
  • Orion.Abstractions — the shared contracts spine: telemetry, options, result, clock
  • OrionAudit — automatic EF Core change-audit trail
  • OrionBeacon — leader election with fencing tokens
  • OrionGrant — permission / authorization checks
  • OrionKey — source-generated strongly-typed IDs
  • OrionLedger — API-key issuance, verification, and rotation
  • OrionLens — ambient correlation-context propagation
  • OrionLock — distributed locks with fencing tokens
  • OrionOnce — idempotency keys for exactly-once request handling
  • OrionPatch — transactional outbox for EF Core
  • OrionRelay — outbound webhook delivery (HMAC, retries, backoff)
  • OrionResult — Result/Option types and a shared error vocabulary
  • OrionSaga — sagas / process managers for long-running workflows
  • OrionShade — sensitive-data redaction for logs and telemetry
  • OrionStream — server-sent events / streaming hub
  • OrionVault — field-level encryption for EF Core

See it all working together in OrionShowcase, a production-shaped banking sample.

License​

MIT.

Packages​

PackageVersionDownloads
OrionClock0.9.0449
OrionClock.Testing0.9.0294