dotnet-faketime
Wraps .NET's TimeProvider abstraction (System.TimeProvider, introduced .NET 8) and FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing: SetUtcNow, Advance, AutoAdvanceAmount, CreateTimer, Delay, and the pre-.NET-8 ISystemClock migration path. Use when testing C# or F# code that reads the current time, uses timers, or awaits Task.Delay.
Install with skills.sh (any agent)
npx skills add testland/qa --skill dotnet-faketimedotnet-faketime
Overview
.NET 8 introduced System.TimeProvider, an abstract class in System.Runtime.dll that provides a testable abstraction for time. The production singleton TimeProvider.System wraps DateTimeOffset.UtcNow, the local TimeZoneInfo, Stopwatch for high-frequency timestamps, and System.Threading.Timer.
For tests, FakeTimeProvider (namespace Microsoft.Extensions.Time.Testing, assembly Microsoft.Extensions.TimeProvider.Testing.dll) subclasses TimeProvider and gives full control over the fake clock.
When to use
How to use
Step 1 - Add the NuGet package
FakeTimeProvider ships in a separate testing package, not in the BCL. Install it only in test projects:
<!-- In your test .csproj -->
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing"
Version="9.*" />The package name is Microsoft.Extensions.TimeProvider.Testing; it ships the FakeTimeProvider type. TimeProvider itself is built into the .NET 8+ runtime and needs no extra package for production code targeting net8.0 or later.
Step 2 - Inject TimeProvider into production code
Replace direct calls to DateTime.UtcNow or DateTimeOffset.UtcNow with a constructor-injected TimeProvider. Wire TimeProvider.System in the DI container; pass FakeTimeProvider in tests.
// Production code
public class TokenService
{
private readonly TimeProvider _time;
public TokenService(TimeProvider time)
{
_time = time;
}
public bool IsExpired(DateTimeOffset expiresAt)
=> _time.GetUtcNow() > expiresAt;
}
// DI registration (Startup / Program.cs)
services.AddSingleton(TimeProvider.System);Step 3 - Write a deterministic test with SetUtcNow
using Microsoft.Extensions.Time.Testing;
[Fact]
public void IsExpired_ReturnsFalse_WhenTokenNotYetExpired()
{
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(new DateTimeOffset(2026, 5, 20, 12, 0, 0, TimeSpan.Zero));
var svc = new TokenService(fakeTime);
Assert.False(svc.IsExpired(new DateTimeOffset(2026, 5, 20, 13, 0, 0, TimeSpan.Zero)));
}SetUtcNow(DateTimeOffset) sets the frozen instant. The value must be equal to or later than the current fake time; the clock cannot go backwards.
Step 4 - Advance time by a duration
Advance(TimeSpan) moves the clock forward from its current position.
[Fact]
public void IsExpired_ReturnsTrue_AfterTokenLifetime()
{
var fakeTime = new FakeTimeProvider(
new DateTimeOffset(2026, 5, 20, 12, 0, 0, TimeSpan.Zero));
var svc = new TokenService(fakeTime);
// Token valid for 1 hour
var expiresAt = fakeTime.GetUtcNow().AddHours(1);
fakeTime.Advance(TimeSpan.FromHours(2)); // jump past expiry
Assert.True(svc.IsExpired(expiresAt));
}The FakeTimeProvider(DateTimeOffset) constructor overload sets the starting instant directly.
Verify: run dotnet test and confirm the boundary assertions pass. If a test hangs or times out instead, the code under test is calling real Task.Delay or Thread.Sleep rather than the injected TimeProvider - fix the injection and re-run before relying on the suite.
Advanced clock control
For auto-advancing reads (AutoAdvanceAmount), virtual-clock timers and Task.Delay (CreateTimer / Delay), and local time zone testing (SetLocalTimeZone / GetLocalNow), see references/advanced-clock-control.md.
Migrating from ISystemClock
Code on the pre-.NET-8 ISystemClock pattern replaces that injection with TimeProvider and its fakes with FakeTimeProvider. The legacy interface, a fake implementation, and the full migration path are in references/isystemclock-migration.md.
CI integration
No special runner configuration is needed. Tests complete instantly because no wall-clock sleeping occurs.
jobs:
dotnet-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.x'
- run: dotnet test --configuration ReleaseAnti-patterns and limitations
Common misuses (direct DateTime.UtcNow, static DateTime mocks, backwards SetUtcNow, missing Task.Yield()) and the tool's limits (Task.Delay(int), Thread.Sleep, GetTimestamp(), third-party DateTime.UtcNow, no leap seconds) are cataloged in references/anti-patterns-and-limitations.md.
References
Advanced clock control
View source (opens in new window)Advanced clock control
Beyond SetUtcNow and Advance, FakeTimeProvider controls auto-advancing reads, virtual-clock timers, and the local time zone.
Auto-advance on every read
AutoAdvanceAmount makes the clock tick forward by a fixed amount each time GetUtcNow() is called. Per learn.microsoft.com/dotnet/api/microsoft.extensions.time.testing.faketimeprovider.autoadvanceamount (opens in new window): "Gets or sets the amount of time by which time advances whenever the clock is read."
var fakeTime = new FakeTimeProvider();
fakeTime.AutoAdvanceAmount = TimeSpan.FromMilliseconds(100);
// Each GetUtcNow() call adds 100 ms
var t1 = fakeTime.GetUtcNow();
var t2 = fakeTime.GetUtcNow();
Assert.Equal(TimeSpan.FromMilliseconds(100), t2 - t1);Use AutoAdvanceAmount for elapsed-time assertions, not for tests that need precise control - explicit Advance calls are more readable there.
Test Task.Delay and timers
FakeTimeProvider controls the virtual clock used by Delay and CreateTimer so that async waiting does not block wall-clock time in tests.
[Fact]
public async Task Poller_DoesNotFireBeforeInterval()
{
var fakeTime = new FakeTimeProvider();
var fired = false;
// timeProvider.Delay(...) is an extension method from
// System.Threading.Tasks.TimeProviderTaskExtensions
var delayTask = fakeTime.Delay(TimeSpan.FromSeconds(30));
_ = delayTask.ContinueWith(_ => fired = true);
fakeTime.Advance(TimeSpan.FromSeconds(10));
await Task.Yield(); // let continuations run
Assert.False(fired);
fakeTime.Advance(TimeSpan.FromSeconds(20));
await delayTask;
Assert.True(fired);
}Delay(TimeProvider, TimeSpan, CancellationToken) is an extension method in System.Threading.Tasks.TimeProviderTaskExtensions. Per learn.microsoft.com/dotnet/api/system.threading.tasks.timeprovidertaskextensions.delay (opens in new window): "Creates a task that completes after a specified time interval."
CreateTimer works analogously: the callback fires only when Advance moves the virtual clock past the due time. Per learn.microsoft.com/dotnet/api/microsoft.extensions.time.testing.faketimeprovider.createtimer (opens in new window): "Creates a new ITimer instance, using TimeSpan values to measure time intervals."
Local time zone testing
Set a custom LocalTimeZone on FakeTimeProvider to test timezone-sensitive branches without touching the system clock or environment variables:
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(new DateTimeOffset(2026, 3, 8, 7, 0, 0, TimeSpan.Zero));
fakeTime.SetLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("America/New_York"));
DateTimeOffset local = fakeTime.GetLocalNow();
// local is UTC-5 or UTC-4 depending on DST; see dst-transition-referencePer learn.microsoft.com/dotnet/api/system.timeprovider.getlocalnow (opens in new window): GetLocalNow() returns the UTC instant converted to the provider's LocalTimeZone. Use the companion dst-transition-reference for expected offset values around spring/fall transitions.
Anti-patterns and limitations
View source (opens in new window)Anti-patterns and limitations
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
DateTime.UtcNow directly in code | Not injectable; test is forced to use wall-clock | Inject TimeProvider; call _time.GetUtcNow() |
DateTimeOffset.UtcNow directly in code | Same problem | Inject TimeProvider |
Static mock of DateTime via Fakes/Harmony | Requires special test runner config or IL rewriting | Use DI with TimeProvider |
Call SetUtcNow with a value earlier than current | Throws ArgumentOutOfRangeException | Use Advance or construct fresh FakeTimeProvider |
Forget await Task.Yield() after Advance | Continuations haven't had a chance to run yet | Yield or await the completed task |
AutoAdvanceAmount in tests needing exact instants | Clock shifts unexpectedly between reads | Set AutoAdvanceAmount = TimeSpan.Zero (default) |
Register TimeProvider.System in test DI | Tests become time-dependent and flaky | Register FakeTimeProvider in test DI setup |
Limitations
Pre-.NET-8 pattern: ISystemClock migration
View source (opens in new window)Pre-.NET-8 pattern: ISystemClock migration
Before TimeProvider, the Microsoft.Extensions stack used ISystemClock (namespace Microsoft.Extensions.Internal, assembly Microsoft.Extensions.Caching.Abstractions.dll). Per learn.microsoft.com/dotnet/api/microsoft.extensions.internal.isystemclock (opens in new window): "Abstracts the system clock to facilitate testing." It exposed a single property, UtcNow, and carried the notice "This API supports the .NET infrastructure and is not intended to be used directly from your code."
// Legacy pattern (pre-.NET 8)
public interface ISystemClock
{
DateTimeOffset UtcNow { get; }
}
// Test implementation
public class FakeSystemClock : ISystemClock
{
public DateTimeOffset UtcNow { get; set; }
}Migration path: replace ISystemClock injection with TimeProvider, and replace fake implementations with FakeTimeProvider. The ISystemClock approach covers only UtcNow; TimeProvider also covers high-frequency timestamps and timers, making it the complete replacement.
Related skills
dst-transition-reference
Pure-reference catalog of Daylight Saving Time (DST) transition patterns and their canonical bug classes. Covers the spring-forward (skipped hour: 02:00 → 03:00 local) and fall-back (repeated hour: 02:00 → 01:00 local) transitions, the historical irregularity of DST (different jurisdictions, transitions on different dates, some regions abolish DST or never adopted it), the IANA timezone database (tz / Olson DB) as the canonical source, and the testable behaviors DST creates (duplicate / missing local timestamps, cron jobs that fire 0 or 2 times, billing periods that miss / double-count, recurring meetings on transition days). Per-jurisdiction DST-rule tables and refreshable per-region test-data fixtures live in references/. Use when designing or auditing time-handling code or test cases.
freezegun-python
Wraps freezegun (github.com/spulec/freezegun), the Python time-mocking library: @freeze_time decorator / context manager, freeze_time(...).start() + stop(), tick / move_to / tz_offset, and integration with datetime.now / time.time / time.localtime. Use when testing Python code that calls datetime / time.
iso-8601-vs-rfc-3339-reference
Pure-reference catalog of the ISO 8601 vs RFC 3339 distinction. Covers the relationship (RFC 3339 is a strict subset of ISO 8601 designed for internet protocols), the syntactic differences (RFC 3339 disallows ISO 8601's '+02' offset short-form requires '+02:00'; RFC 3339 mandates a date-time separator T or space; ISO 8601 allows much more), the canonical date-time string format (YYYY-MM-DDTHH:MM:SS[.fff]±HH:MM or Z), per-language parser behaviour (Python isoformat, Java Instant.parse, JS Date.parse non-spec), and serialisation rules for APIs. Use when choosing a wire format, parsing third-party datetimes, or auditing time-string handling.
jest-fake-timers
Wraps Jest's built-in modern fake-timers (built on @sinonjs/fake-timers since Jest 27): jest.useFakeTimers(), jest.setSystemTime(), jest.advanceTimersByTime(), jest.runAllTimers(), and jest.useRealTimers() for selective restoration. Use when testing JS/TS code in Jest where setTimeout / setInterval / Date / Date.now need deterministic control.
leap-second-reference
Pure-reference catalog of leap-second mechanics and the bugs they cause: the 23:59:60 UTC insertion (announced ~6 months ahead by IERS Bulletin C; 27 inserted 1972-2016; abolished by 2035 per CGPM 2022), the Google/AWS leap-smear alternative, and the four bug classes a real insertion exposes - time_t non-monotonicity, negative durations, NTP cascading, and cross-node clock skew - each with a monotonic-clock fix and a freezegun simulation. Use when auditing time-sensitive code (financial timestamping, distributed logs, NTP-driven schedulers) for second-by-second progress assumptions; for the far more common daylight-saving-time transition hazards, use dst-transition-reference instead.
libfaketime-c
Wraps libfaketime (github.com/wolfcw/libfaketime), the LD_PRELOAD library that fakes the clock for any binary by intercepting time() / gettimeofday() / clock_gettime(). Covers absolute-date mode (FAKETIME='2026-12-31 23:59:00'), relative offset (FAKETIME='-1d'), advance-rate (FAKETIME='@2026-12-31 23:59:00 x5' for 5x speed), per-process scope via LD_PRELOAD, and FAKETIME_NO_CACHE for high-resolution mocking. Use when you need to fake time, mock the clock, or freeze time for C/C++ or any native binary that needs deterministic wall-clock time.
mockclock-jvm
Wraps Java's java.time.Clock + InstantSource dependency-injection pattern for testing time-sensitive code. Covers Clock.fixed(instant, zone), Clock.offset(baseClock, duration), Clock.systemDefaultZone() for production, the InstantSource interface (Java 17+), and the recommended dependency-injection pattern (constructor-inject Clock instead of calling Instant.now() directly). Use when you need to wire the clock-injection pattern (Clock.fixed, Clock.offset, MutableClock, InstantSource, Spring @Bean) into JVM (Java / Kotlin / Scala) production or test code. For pure DST transition reference (skipped or repeated hours, IANA DB, cron-double-fire bug classes) without a clock-injection need, use dst-transition-reference instead.
sinon-fake-timers-js
Wraps Sinon's standalone @sinonjs/fake-timers library for JS/TS testing: install(), tick() / tickAsync(), setSystemTime(), restore(); covers timers (setTimeout / setInterval / requestAnimationFrame), Date / performance.now() / hrtime, and the toFake option for selective override. Runner-agnostic - drives the clock directly in Mocha, AVA, Jasmine, node:test, or the browser. Use when JS/TS code needs deterministic timer or clock control and the test runner does not already expose this library behind its own built-in fake-timer API.
timecop-ruby
Wraps timecop (github.com/travisjeffery/timecop), the Ruby time-mocking gem: Timecop.freeze, Timecop.travel, Timecop.scale (time-speedup), Timecop.return (cleanup), and RSpec-friendly helpers. Use when testing Ruby/Rails code that calls Time / Date / DateTime.
timezone-test-matrix-builder
Builds a timezone, daylight saving time (DST), and leap year / leap second test matrix from wherever a codebase reads or formats dates and times. Finds time-handling code (grep for datetime / Date / Instant / time.time / timezone), sorts each spot into storage, business-logic, display, cron, or billing, picks the edge cases that matter (DST spring-forward / fall-back, ambiguous local time, leap day Feb 29, ISO 8601 / RFC 3339 round-trip, zone-database updates), and emits per-spot test stubs wired to the language's fake-clock (mock-time) library. Use when a codebase needs timezone, DST, and leap-year test coverage derived from its own date/time usage.