Documentation
¶
Overview ¶
Package clock provides narrow time capabilities backed by the standard library and deterministic implementations for tests. Accept the narrowest capability interface a consumer needs; System's zero value is the normal production implementation, while manual supplies explicit test-time control. Observe adds synchronous hooks without installing an exporter or registry.
The package does not own calendars, cron scheduling, distributed ordering, or process-global time. All implementations use Go time values; no cgo or external service is required.
Wall-clock timestamps and monotonic elapsed time are deliberately distinct. A time.Time returned by System.Now retains the process-local monotonic reading supplied by time.Now. Serialization removes that reading, so persisted values must never be used as a substitute for monotonic elapsed measurement.
Index ¶
- Constants
- Variables
- type Callback
- type CallbackClock
- type Clock
- type ElapsedClock
- type FullClock
- type Kind
- type Observation
- type ObserveOption
- type Observer
- type ObserverFunc
- type Outcome
- type Sleeper
- type System
- func (System) AfterFunc(d time.Duration, fn func()) (Callback, error)
- func (System) Measure() func() time.Duration
- func (System) NewTicker(d time.Duration) (Ticker, error)
- func (System) NewTimer(d time.Duration) (Timer, error)
- func (System) Now() time.Time
- func (System) Since(start time.Time) time.Duration
- func (System) Sleep(ctx context.Context, d time.Duration) error
- type Ticker
- type TickerFactory
- type Timer
- type TimerFactory
Examples ¶
Constants ¶
const ( // MaxObservationTags bounds labels attached to one observation. MaxObservationTags = 16 // MaxObservationTagBytes bounds each tag key and value. MaxObservationTagBytes = 64 )
Variables ¶
var ( // ErrInvalidDuration reports a duration that is not valid for an operation. ErrInvalidDuration = errors.New("clock: invalid duration") // ErrInvalidCallback reports a nil callback function. ErrInvalidCallback = errors.New("clock: invalid callback") // ErrOverflow reports unrepresentable duration arithmetic or exhaustion of // the manual clock's event registration sequence. ErrOverflow = errors.New("clock: duration overflow") )
var ( // ErrInvalidClock reports a nil clock passed to Observe. ErrInvalidClock = errors.New("clock: invalid clock") // ErrInvalidObserver reports a nil observer passed to Observe. ErrInvalidObserver = errors.New("clock: invalid observer") // ErrObservationTags reports tags outside the documented bounds. ErrObservationTags = errors.New("clock: invalid observation tags") )
Functions ¶
This section is empty.
Types ¶
type Callback ¶
type Callback interface {
// Stop reports whether it prevented a scheduled invocation from starting.
// It does not wait for an invocation already running.
Stop() bool
// Reset reports prior scheduling activity and schedules another invocation.
// It does not wait for a running invocation, so calls can overlap.
Reset(time.Duration) (bool, error)
}
Callback is an owned timer callback.
Stop reports whether it prevented the callback from starting. It does not wait for an already-started callback to finish.
type CallbackClock ¶
type CallbackClock interface {
// AfterFunc schedules a non-nil function and transfers callback ownership.
// A nil function returns nil and ErrInvalidCallback; non-positive durations
// are immediately due. Panic and execution policy depend on the backend.
AfterFunc(time.Duration, func()) (Callback, error)
}
CallbackClock creates owned timer callbacks.
type Clock ¶
type Clock interface {
// Now returns the implementation's current wall timestamp.
Now() time.Time
}
Clock obtains the current wall-clock time.
Example ¶
package main
import (
"context"
"fmt"
"time"
"github.com/faustbrian/go-clock/manual"
)
func main() {
start := 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.Close(); err != nil {
panic(err)
}
}
Output: 2026-01-02T03:05:05Z
type ElapsedClock ¶
type ElapsedClock interface {
// Since measures from start using the implementation's timestamp semantics.
// Manual clocks cannot recover elapsed progress from a wall timestamp.
Since(time.Time) time.Duration
// Measure captures elapsed progress and returns a reusable measurement.
// Fixed clocks always report zero; manual wall jumps do not affect it.
Measure() func() time.Duration
}
ElapsedClock measures implementation-local elapsed time. Since accepts a time.Time with time.Time.Sub semantics, including wall subtraction when no compatible monotonic readings exist. Measure returns a closure tied to the implementation's monotonic source and is the safe choice across explicit manual wall-clock jumps.
type FullClock ¶
type FullClock interface {
Clock
ElapsedClock
Sleeper
TimerFactory
TickerFactory
CallbackClock
}
FullClock is a convenience composition for consumers that genuinely need every capability. Consumers should normally accept a narrower interface.
func Observe ¶
func Observe(base FullClock, observer Observer, options ...ObserveOption) (FullClock, error)
Observe decorates a FullClock with bounded synchronous lifecycle hooks. It starts no goroutine and owns no exporter or global registry. The caller still owns the base clock and its release; the wrapper does not expose base-specific methods such as manual.Clock.Close.
Nil interfaces and a nil ObserverFunc are rejected; other typed-nil dynamic values are not detected. Nil options are ignored. Clock validation failures return nil with ErrInvalidClock, ErrInvalidObserver or the option's error. Concurrent use requires a concurrency-safe base and observer.
Now, Since and Measure delegate without notifications. Timer/ticker channel receives are not observed. Sleep reports after return without changing its error; callbacks report after return or during panic unwinding and re-panic the original value. Callback observations retain the initial duration and measurement origin even after Reset; resets have their own observations.
Example ¶
package main
import (
"fmt"
"time"
clock "github.com/faustbrian/go-clock"
)
func main() {
observed, err := clock.Observe(clock.System{}, clock.ObserverFunc(func(observation clock.Observation) {
fmt.Println(observation.Kind, observation.Outcome)
}))
if err != nil {
panic(err)
}
timer, err := observed.NewTimer(time.Hour)
if err != nil {
panic(err)
}
timer.Stop()
}
Output: timer created timer stopped
type Kind ¶
type Kind string
Kind identifies an observed time resource.
const ( // KindSleep identifies a context-aware sleep. KindSleep Kind = "sleep" // KindTimer identifies a one-shot channel timer. KindTimer Kind = "timer" // KindTicker identifies a periodic channel ticker. KindTicker Kind = "ticker" // KindCallback identifies an AfterFunc callback. KindCallback Kind = "callback" )
type Observation ¶
type Observation struct {
// Kind identifies the operation or resource being observed.
Kind Kind
// Outcome identifies the transition, completion or rejection.
Outcome Outcome
// Requested is the duration supplied to creation, sleep or reset.
// Stop observations have no requested duration and use zero.
Requested time.Duration
// Elapsed measures sleep or callback progress from the original call.
// Resource creation, stop and reset observations leave it zero.
Elapsed time.Duration
// Tags is a fresh map per observation, or nil when no tags are configured.
// The observer may retain or mutate it without affecting other observations.
Tags map[string]string
}
Observation contains bounded lifecycle metadata. Clock-generated metadata excludes callback functions, panic payloads, timestamps and contexts. Tag contents are caller-supplied and are not redacted; never attach secrets.
type ObserveOption ¶
type ObserveOption func(*observeConfig) error
ObserveOption configures an observed clock.
func WithTags ¶
func WithTags(tags map[string]string) ObserveOption
WithTags validates and copies tags when Observe applies the option, then supplies a separate copy to each observation. Keys must be non-empty; at most MaxObservationTags entries and MaxObservationTagBytes bytes per key/value are accepted. Nil tags are valid. A later WithTags replaces an earlier one. Do not mutate the input concurrently with construction or put secrets in tags.
type Observer ¶
type Observer interface {
// Observe consumes one observation and may retain its independently owned tags.
Observe(Observation)
}
Observer consumes lifecycle metadata synchronously. Implementations must return promptly and synchronize their own state: concurrent clock operations and callbacks may call Observe concurrently. Calls happen outside wrapper locks; reentrant observed operations produce nested notifications. Panics are isolated, but blocking an observer blocks the corresponding clock operation.
type ObserverFunc ¶
type ObserverFunc func(Observation)
ObserverFunc adapts a function to Observer.
func (ObserverFunc) Observe ¶
func (function ObserverFunc) Observe(observation Observation)
Observe calls function with observation synchronously. A nil function panics when called directly; the clock decorator recovers observer panics.
type Outcome ¶
type Outcome string
Outcome identifies an observed lifecycle transition.
const ( // OutcomeCreated reports successful resource creation. OutcomeCreated Outcome = "created" // OutcomeCompleted reports successful synchronous completion. OutcomeCompleted Outcome = "completed" // OutcomeCanceled reports context cancellation. OutcomeCanceled Outcome = "canceled" // OutcomeDeadline reports context deadline expiration. OutcomeDeadline Outcome = "deadline" // OutcomeFailed reports another synchronous operation failure. OutcomeFailed Outcome = "failed" // OutcomeStopped reports a successful active-to-stopped transition. OutcomeStopped Outcome = "stopped" // OutcomeInactive reports an operation on an inactive resource. OutcomeInactive Outcome = "inactive" // OutcomeReset reports successful rescheduling. OutcomeReset Outcome = "reset" // OutcomeFired reports normal callback return. OutcomeFired Outcome = "fired" // OutcomePanicked reports a callback panic without its payload. OutcomePanicked Outcome = "panicked" // OutcomeRejected reports validation or resource rejection. OutcomeRejected Outcome = "rejected" )
type Sleeper ¶
type Sleeper interface {
// Sleep accepts a non-nil context and returns nil on completion or an error
// on cancellation or resource failure. Non-positive durations need no wait.
// Context cancellation does not stop other resources owned by the clock.
Sleep(context.Context, time.Duration) error
}
Sleeper waits for a duration or until its context is canceled.
type System ¶
type System struct{}
System delegates time operations to the Go standard library.
Its zero value is ready for concurrent use and owns no background resources.
Example ¶
package main
import (
"fmt"
clock "github.com/faustbrian/go-clock"
)
func main() {
var timestamps clock.Clock = clock.System{}
_ = timestamps.Now()
fmt.Println("system clock ready")
}
Output: system clock ready
func (System) AfterFunc ¶
AfterFunc returns an owned wrapper around time.AfterFunc. A nil fn returns nil and ErrInvalidCallback. The function runs in a standard-library goroutine; its panic is not recovered. Stop/Reset do not wait for a running function, and resetting a started callback can schedule an overlapping invocation.
func (System) Measure ¶
Measure captures the current standard-library monotonic reading and returns a closure that reports elapsed time from it.
func (System) NewTicker ¶
NewTicker returns an owned wrapper around time.NewTicker. A non-positive period returns nil and ErrInvalidDuration instead of panicking.
func (System) NewTimer ¶
NewTimer returns an owned wrapper around time.NewTimer and a nil error. Non-positive durations are immediately due. Stop/Reset follow time.Timer.
func (System) Since ¶
Since returns time.Since(start). When start contains a monotonic reading, the standard library uses it instead of rollback-prone wall time.
func (System) Sleep ¶
Sleep waits for d or context cancellation. Non-positive durations complete immediately unless the context is already canceled. Its timer is always stopped on cancellation so the resource can be released promptly. A nil context panics. If completion and cancellation race, either may win.
type Ticker ¶
type Ticker interface {
// C returns tick timestamps; slow receivers may miss ticks.
C() <-chan time.Time
// Stop idempotently prevents future ticks without closing C.
Stop()
// Reset starts a new period from the current instant. Non-positive periods
// return ErrInvalidDuration without changing the existing schedule.
Reset(time.Duration) error
}
Ticker is an owned periodic time event.
The owner must call Stop. Ticks may be dropped for a slow receiver, matching time.Ticker. Reset returns ErrInvalidDuration for a non-positive duration instead of exposing the standard library's panic across an interface seam.
type TickerFactory ¶
type TickerFactory interface {
// NewTicker requires a positive period and transfers ownership on success.
// Invalid periods return nil and ErrInvalidDuration.
NewTicker(time.Duration) (Ticker, error)
}
TickerFactory creates owned periodic tickers.
type Timer ¶
type Timer interface {
// C returns the caller-owned receive side; callers must not close it.
C() <-chan time.Time
// Stop reports whether it stopped an active timer. It does not close C.
Stop() bool
// Reset reschedules from the current instant and reports prior activity.
// Non-positive durations are immediately due. Rejected manual resets leave
// the prior schedule/value intact; successful manual resets drain old values.
Reset(time.Duration) (bool, error)
}
Timer is an owned, one-shot time event.
The owner must stop a timer it no longer needs. System uses the synchronous channel semantics of Go 1.23 and later; manual timers buffer one value and retain an already-delivered value after Stop. Implementations document their delivery and reset rules. A timer channel is not closed by Stop.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package clocktest bridges clock capabilities to testing/synctest and manual advancement.
|
Package clocktest bridges clock capabilities to testing/synctest and manual advancement. |
|
Package manual provides fixed timestamps and explicitly advanced test clocks.
|
Package manual provides fixed timestamps and explicitly advanced test clocks. |