New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@time-provider/core

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@time-provider/core

Inject time. Control it in tests.

latest
Source
npmnpm
Version
4.1.1
Version published
Weekly downloads
20
233.33%
Maintainers
1
Weekly downloads
 
Created
Source

Time-Provider

npm jsr jsr score types Node.js ^18.18 || >=20.4 Deno ~1.39.1 || >=1.43 module ESM + CJS CodeQL check codecov tests mutation score socket npm downloads dependencies unpacked-size minified size openssf best practices license

Time-Provider

🌳 Tree-shakable | 📦 Zero runtime dependencies | 🧪 No global monkey-patching | 🛡️ Type-safe | ⏱️ Deterministic timers | 🕓 Four clock strategies | 🌍 Real timezone support (when applicable) | 🔌 Bring your own date library | 🧩 Modular & Extensible

Solid clock. Own time. Better tests.

Why monkey-patch the global clock to test your code? Why rewrite the same time interface to inject it?

What if that interface doubled as your fake timers? What if it were also type-safe and thoroughly tested?

Time-Provider is that interface: ready to use, fully deterministic in tests, and several times faster than Jest or Sinon fake timers. Bring your own date library, and extend it with addons!

Philosophy

Time is scoped per call site: no global patch, no restore or cleanup step.

Compare it to Jest fake timers or Compare it to Sinon fake timers.

Time-Provider makes time an explicit, injectable dependency: a single object exposing a clock, a converter, a scheduler, and timings, swappable per call site.

Note: Non baseline or additional features are available as addons.

Features

  • Four clock strategies: system, manual, sequential and fixed - same API for production and tests.
  • Time flow and wall clock steps: make time flow, simulate a host sleep or a wall-clock correction. Also step from timer to timer.
  • Deterministic timers: driven by the clock strategy and run synchronously.
  • Bring your own date library: multiple date library plugins are available. Your code keeps working with the date type it already uses.
  • Timezone support: backed by the underlying library of your choice.
  • Tree-shakable: no deterministic runtimes bundled when not imported.
  • Zero runtime dependencies in @time-provider/core.

Install

npm install @time-provider/core @time-provider/plugin-native

Swap plugin-native for plugin-dayjs, plugin-luxon, plugin-moment, plugin-moment-timezone, or plugin-temporal depending on the date library you use.

Usage

// PROD: import the default production runtime
import { createTimeProvider } from "@time-provider/core";
import { plugin } from "@time-provider/plugin-native";

// create a production runtime
const timeProvider = createTimeProvider.for(plugin).create();

class UserService {
  constructor(private readonly timeProvider: ITimeProvider<Date>) {}

  createUser() {
    return { createdAt: this.timeProvider.clock.utcNow() };
  }
}
// TEST: import the deterministic runtime
import { createTimeProvider } from "@time-provider/core/deterministic";
import { plugin } from "@time-provider/plugin-native/deterministic";

// create a deterministic runtime
it("sets the creation date to now", () => {
  const now = "2026-01-01T00:00:00.000Z";
  using timeProvider = createTimeProvider.for(plugin).asManual().withInitialTime(now).create();

  expect(new UserService(timeProvider).createUser().createdAt).toEqual(new Date(now));
});

Similar idea for timers, closer to a real service - a retry/backoff loop injected with the time provider, unaware of which strategy backs it:

// PROD: a retry/backoff service, injected with the real time provider
class RetryingOperation {
  constructor(private readonly timeProvider: ITimeProvider<Date>) {}

  run(operation: () => boolean, onGiveUp: () => void, maxAttempts = 3) {
    let attempt = 0;
    this.timeProvider.scheduler.timers.recurring(() => {
      attempt++;
      if (operation()) return false; // succeeded, stop retrying
      if (attempt >= maxAttempts) {
        onGiveUp();
        return false;
      }
      return { seconds: attempt }; // failed, backoff: 1s, 2s, 3s...
    });
  }
}

new RetryingOperation(timeProvider).run(sendRequest, pageOnCallEngineer);
// TEST: same service, injected with a manual provider instead
describe("RetryingOperation ~ run", () => {
  it("retries until it succeeds", () => {
    using timeProvider = createTimeProvider.for(plugin).asManual().create();
    let attempts = 0;
    let gaveUp = false;
    new RetryingOperation(timeProvider).run(
      () => ++attempts === 3, // succeeds on the 3rd try
      () => (gaveUp = true),
    );

    timeProvider.clock.moveBy({ seconds: 1 }); // 2nd attempt
    timeProvider.clock.moveBy({ seconds: 2 }); // 3rd attempt, succeeds

    expect(attempts).toBe(3);
    expect(gaveUp).toBe(false);
  });
});

Clock strategies

StrategyBehaviorTypical useNote
SystemReal time, real timersProduction
FixedAlways the same instantDeterministic frozen single-instant testsTimers never fire
ManualAdvances only when told toSimulations, timer/retry logic tests
SequentialReturns a predefined instant sequenceTests asserting on changing timestampsTime reads move to the next instant
createTimeProvider.for(plugin).asFixed().withFixedTime("2026-01-01T00:00Z").create();
createTimeProvider.for(plugin).asManual().withInitialTime("2026-01-01T00:00Z").create();
createTimeProvider
  .for(plugin)
  .asSequential()
  .withSequentialTime("2026-01-01T00:01Z")
  .withSequentialTime(1767225720000)
  .create();

Manual and sequential clocks run synchronously. A due timer callback fires in-line, as a direct side effect of the call that made it due (and not on a real event-loop tick). This is what makes them deterministic without await, but it also means call ordering can differ subtly from a real async run.

Addons vs. Plugins

Within the scope of this library, these two terms refer to different concepts.

  • A plugin is essentially an adapter. It allows you to connect your preferred date library to the Time-Provider core library without adding any new functionality. Its sole purpose is to bridge the two libraries.
  • An addon, as the name suggests, extends the library by introducing new functionality or enhancing existing facades.

Available addons

  • Animation-frame API addon - access browser-specific animation frame timers
  • Compat addon - keep calling native-style setTimeout/setInterval/queueMicrotask/performance while you migrate
  • Cron addon - schedule recurring callbacks with the cron syntax or a JSON-friendlier one
  • ETA addon - get the ETA (estimated time of arrival) for a task by notifying its progression
  • Idle addon - run callbacks when the host reports itself idle, drained on demand on a deterministic clock

Available plugins

Baseline API

Every time provider exposes the same four-part surface:

interface ITimeProvider<TDate> extends IHasAbortSignal, IDisposable {
  clock: IClock<TDate>; // localNow, utcNow, timestampNow, monotonicNow, withTimezone
  converter: IConverter<TDate>; // convertToUtc, convertToLocal
  scheduler: IScheduler; // timers (once, every, recurring, wait), microtasks
  timings: ITimings; // mark, measure, entries, clear
}

clock gives the current time: as a timestamp, as a monotonic value, or as a UTC or local date in your date library's type.

converter turns a string, a timestamp or a date into a UTC (or local) date of your date library's type.

scheduler is where everything that schedules a callback to run later sits, timers included.

timings holds performance marks and measures. Monotonic time and its origin stay on clock.

Learn more

  • Guide - Read the guide
  • API - Browse the library API
  • ARCHITECTURE.md - how the packages fit together, the plugin/adapter model, why native Date and plain Moment.js are UTC-only.
  • CONTRIBUTING.md - development setup, workflow, reporting bugs/features.
  • BENCHMARK.md - faster than Jest/Sinon fake timers.
  • Per-package README (packages/<name>/README.md) for adapter-specific notes.
  • CHANGELOG.md - changes log from the core library and all plugins.

License

MIT

Keywords

clock

FAQs

Package last updated on 29 Oct 2026

Related posts