OrionClock 0.9.0
dotnet add package OrionClock --version 0.9.0
NuGet\Install-Package OrionClock -Version 0.9.0
<PackageReference Include="OrionClock" Version="0.9.0" />
<PackageVersion Include="OrionClock" Version="0.9.0" />
<PackageReference Include="OrionClock" />
paket add OrionClock --version 0.9.0
#r "nuget: OrionClock, 0.9.0"
#:package OrionClock@0.9.0
#addin nuget:?package=OrionClock&version=0.9.0
#tool nuget:?package=OrionClock&version=0.9.0
<p align="center"> <img src="docs/logo.png" alt="OrionClock" width="150" /> </p>
OrionClock
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 aTimeProvider(CancellationTokenSource,Task.Delay, timers) accepts anOrionClock. Subclasses the BCL primitive rather than replacing it. - It is the family's
IOrionClock— one clock, registered asTimeProviderandIOrionClock, 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),ToCancellationTokenSourcefor TTL-driven cancellation.Deadline— a "must complete by" instant withIsPast,TimeRemaining, and deadline-driven cancellation.- Deterministic in tests —
FakeOrionClock(inOrionClock.Testing) freezes time and only moves onAdvance/SetUtcNow. Because it is built onTimeProvider, advancing it fires the timers and cancellation sources created through it — so a 5-minute TTL is expired afterAdvance(6m)and not afterAdvance(4m), with no real delay. - One-line DI (
AddOrionClock) — registers viaTryAdd, 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
- CHANGELOG.md — release notes.
Contributing
Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Orion.Abstractions (>= 1.0.0)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Orion.Abstractions (>= 1.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Orion.Abstractions (>= 1.0.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on OrionClock:
| Package | Downloads |
|---|---|
|
OrionClock.Testing
Testing companion for OrionClock: a controllable FakeOrionClock whose time only moves when you Advance or SetUtcNow it, so TTL expiry, deadlines, and timer-driven cancellation fire deterministically without real delays. Built on Microsoft's FakeTimeProvider, so advancing it fires the timers created through it. |
|
|
OrionRate
In-memory token-bucket and sliding-window rate limiting with OrionClock, typed decisions, and OpenTelemetry. Add OrionRate.AspNetCore for Minimal API endpoint filters. |
|
|
OrionInbox
The consumer-side other half of OrionPatch: a transport-agnostic transactional inbox for exactly-once message effects. Declare an IInboxHandler<T> and ProcessAsync dedups the message id and runs the handler in one atomic transaction, so a redelivered message lands once. This package is the framework-free core (handler, message, result, options, telemetry); add OrionInbox.EntityFrameworkCore for the store. |
|
|
OrionInbox.EntityFrameworkCore
EF Core storage for OrionInbox. Adds the OrionInbox_Messages dedup table and an atomic ProcessAsync that inserts the dedup row and runs the handler's writes in one transaction, keyed by a unique constraint so concurrent redeliveries collapse to a single effect. Includes AddOrionInbox<TDbContext> wiring and a background prune of expired dedup rows off OrionClock. |
|
|
OrionResilience
One opinionated resilience vocabulary for the Orion family: retry with jitter presets and timeout, executed over an OrionClock TimeProvider so every retry fast-forwards in tests, with OpenTelemetry by default. Configured through options, not fluent chains. AOT- and trim-clean. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.0 | 448 | 7/27/2026 |