OrionClock 0.9.0

dotnet add package OrionClock --version 0.9.0
                    
NuGet\Install-Package OrionClock -Version 0.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OrionClock" Version="0.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OrionClock" Version="0.9.0" />
                    
Directory.Packages.props
<PackageReference Include="OrionClock" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OrionClock --version 0.9.0
                    
#r "nuget: OrionClock, 0.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OrionClock@0.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OrionClock&version=0.9.0
                    
Install as a Cake Addin
#tool nuget:?package=OrionClock&version=0.9.0
                    
Install as a Cake Tool

<p align="center"> <img src="docs/logo.png" alt="OrionClock" width="150" /> </p>

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.

License

MIT.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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