clock is a small, production-oriented clock foundation for Go 1.27.0 and
later. It keeps time.Time and time.Duration as public values, separates wall
time from elapsed time, and provides deterministic timers, tickers, sleeps, and
callbacks without changing the process-wide clock.
The module is a stable v1 public library. It owns explicit process-local time capabilities and deterministic test clocks; it does not own calendars, scheduling, distributed ordering, or a process-global clock.
Use the standard time package directly when no dependency seam is needed. Use
testing/synctest when a complete test can live inside one fake-time bubble.
Use this module when business timestamps, explicit wall jumps, package
contracts, or selectively controlled time require dependency injection.
go get github.com/faustbrian/go-clock@v1The module has no runtime dependencies.
Current-main variants are compiler-checked in
example_test.go. The installed-v1 manual-clock quick start
below is separately verified in a clean external module pinned to v1.0.0;
it handles every construction and wait error and releases the clock explicitly.
Depend on only the capability an operation needs:
func stamp(clock interface{ Now() time.Time }) time.Time {
return clock.Now()
}
createdAt := stamp(clock.System{})System.Now returns time.Now() unchanged, including its location and
process-local monotonic reading. System.Sleep owns and releases its timer when
the context is canceled.
fixed := manual.NewFixed(time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC))
fmt.Println(fixed.Now().Format(time.RFC3339))
// 2026-01-02T03:04:05Zstart := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
manualClock, err := manual.New(start)
if err != nil {
panic(err)
}
timer, err := manualClock.NewTimer(time.Minute)
if err != nil {
panic(err)
}
waiter, err := manualClock.Advance(time.Minute)
if err != nil {
panic(err)
}
if _, err := waiter.Wait(context.Background()); err != nil {
panic(err)
}
fmt.Println((<-timer.C()).Format(time.RFC3339))
if err := manualClock.Shutdown(); err != nil {
panic(err)
}
// 2026-01-02T03:05:05ZEvents fire by deadline and then registration order. A ticker has a one-value
buffer and drops backpressured ticks. Always stop resources that remain active,
and release the manual clock when its owner is done. The versioned quick start
uses Shutdown because it is available across the complete v1 line; Close is
the preferred additive name on current main, and Shutdown remains its
deprecated exact delegation.
clocktest.SystemBubble(t, func(t *testing.T, system clock.System) {
started := system.Now()
require.NoError(t, system.Sleep(t.Context(), time.Hour))
require.Equal(t, time.Hour, system.Since(started))
})The helper delegates fake time and goroutine quiescence to the standard library. It does not install another scheduler.
| Need | Interface |
|---|---|
| Business timestamp | Clock |
| Monotonic elapsed measurement | ElapsedClock |
| Cancelable bounded delay | Sleeper |
| Owned one-shot event | TimerFactory and Timer |
| Owned periodic event | TickerFactory and Ticker |
| Owned callback | CallbackClock and Callback |
FullClock is a convenience only. Libraries should accept the narrowest row
that meets their contract.
All packages are released together from the root module and use root
v<version> tags.
| Import path | Package | Role |
|---|---|---|
github.com/faustbrian/go-clock |
clock |
Public capabilities, standard-library implementation, and bounded observations |
github.com/faustbrian/go-clock/manual |
manual |
Public fixed and explicitly advanced deterministic clocks |
github.com/faustbrian/go-clock/clocktest |
clocktest |
Test-support bridge to testing/synctest |
clock.System{} is ready without construction and delegates to the standard
library. manual.NewFixed returns an immutable fixed wall clock.
manual.New(start, options...) strips the start value's process-local
monotonic reading and applies explicit resource limits before returning a
concurrency-safe clock. Its defaults permit 65,536 scheduled objects, 65,536
outstanding advancement waiters, and 1,000,000 triggered events per advance;
manual.WithLimits replaces those limits and rejects zero or negative values.
clock.Observe rejects nil clock and observer interfaces, a nil
ObserverFunc, and invalid tags before creating its wrapper. Other non-nil
dynamic values remain caller-owned collaborators. WithTags copies at most 16
non-empty-key tags with keys and values no longer than 64 bytes. A later
WithTags option replaces an earlier one, and nil options are ignored.
Constructors read no environment or filesystem configuration.
Advancenever accepts negative elapsed movement; useJumpfor wall-clock rollback or forward correction.Mark,SinceMark, andMeasureuse manual monotonic progress and are not affected byJump.- Callbacks never run while an internal lock is held. They may create, stop, or
reset work. A callback waiting for future work must issue and wait on a nested
Advance; same-instant work wakes the active coordinator automatically. - Callback panics are recovered by the manual clock and counted without keeping
the payload. The system clock retains standard
time.AfterFuncpanic policy. - Active objects and work per advancement are bounded. Invalid durations, overflow, closure, and exhausted budgets return documented errors.
- Observers receive bounded lifecycle metadata, never callback values, panic payloads, contexts, or timestamps.
- Sleep observations distinguish completed, deadline, canceled, and other failed outcomes while returning the exact base error.
- Documentation index
- API and ownership
- Integration and adoption
- Concurrency and callback ownership
- Security model
- Compatibility and migration
- Performance and operations troubleshooting
- Current-main executable examples and
testing/synctesthelpers - FAQ, support, and release history
- API reference
- Private vulnerability reporting
Use the versioned Golib ecosystem catalog and its Foundations family guidance to compare this module with related foundations and composition packages.
Run make cohesion for the repository's design-language contract and
make check for its package gates. See CONTRIBUTING.md for
focused and release verification.
This module does not implement calendars, date-only values, timezone data,
interval algebra, cron, scheduling, distributed ordering, or a timestamp
oracle. calendar, temporal, scheduler, and lease own those
concerns.
MIT. See LICENSE.