clock

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 4 Imported by: 0

README

clock

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

clock is a small, production-oriented clock foundation for Go 1.26 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.

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.

Install

go get github.com/faustbrian/go-clock@v1

The module has no runtime dependencies.

Five-minute quickstarts

System clock

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 clock
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:05Z
Manual clock
start := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
manualClock, _ := manual.New(start)
timer, _ := manualClock.NewTimer(time.Minute)

waiter, _ := manualClock.Advance(time.Minute)
_, _ = waiter.Wait(context.Background())
fmt.Println((<-timer.C()).Format(time.RFC3339))
// 2026-01-02T03:05:05Z

Events 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 call Shutdown when the manual clock's owner is done.

testing/synctest
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.

Capability map

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.

Semantics at a glance

  • Advance never accepts negative elapsed movement; use Jump for wall-clock rollback or forward correction.
  • Mark, SinceMark, and Measure use manual monotonic progress and are not affected by Jump.
  • 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.AfterFunc panic 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.

Documentation

Local release gates

make install-tools
make check staticcheck lint nilaway vuln benchmark mutation

The exact commands are implemented by the Makefile. Production statement coverage is required to be 100.0% for the root and manual packages. The clocktest package is test infrastructure and is exercised separately.

Scope

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.

License

MIT. See LICENSE.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package clock provides narrow time capabilities backed by the standard library and deterministic implementations for tests.

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

Examples

Constants

View Source
const (
	// MaxObservationTags bounds labels attached to one observation.
	MaxObservationTags = 16
	// MaxObservationTagBytes bounds each tag key and value.
	MaxObservationTagBytes = 64
)

Variables

View Source
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 a time operation outside time.Duration's range.
	ErrOverflow = errors.New("clock: duration overflow")
)
View Source
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() bool
	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(time.Duration, func()) (Callback, error)
}

CallbackClock creates owned timer callbacks.

type Clock

type Clock interface {
	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))
}
Output:
2026-01-02T03:05:05Z

type ElapsedClock

type ElapsedClock interface {
	Since(time.Time) time.Duration
	Measure() func() time.Duration
}

ElapsedClock measures process-local elapsed time. Since accepts a time.Time with standard-library monotonic semantics. Measure returns a closure tied to the implementation's monotonic source and is the safe choice across explicit manual wall-clock jumps.

type FullClock

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.

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      Kind
	Outcome   Outcome
	Requested time.Duration
	Elapsed   time.Duration
	Tags      map[string]string
}

Observation contains bounded lifecycle metadata. It never contains callback functions, panic payloads, timestamps, contexts, or other sensitive values.

type ObserveOption

type ObserveOption func(*observeConfig) error

ObserveOption configures an observed clock.

func WithTags

func WithTags(tags map[string]string) ObserveOption

WithTags attaches a bounded defensive copy of tags to every observation.

type Observer

type Observer interface {
	Observe(Observation)
}

Observer consumes lifecycle metadata. Implementations must return promptly; panics are isolated from clock behavior.

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.

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"
	// 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 callback execution.
	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(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

func (System) AfterFunc(d time.Duration, fn func()) (Callback, error)

AfterFunc returns an owned wrapper around time.AfterFunc.

func (System) Measure

func (System) Measure() func() time.Duration

Measure captures the current standard-library monotonic reading and returns a closure that reports elapsed time from it.

func (System) NewTicker

func (System) NewTicker(d time.Duration) (Ticker, error)

NewTicker returns an owned wrapper around time.NewTicker.

func (System) NewTimer

func (System) NewTimer(d time.Duration) (Timer, error)

NewTimer returns an owned wrapper around time.NewTimer.

func (System) Now

func (System) Now() time.Time

Now returns time.Now without changing its location or monotonic reading.

func (System) Since

func (System) Since(start time.Time) time.Duration

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

func (System) Sleep(ctx context.Context, d time.Duration) error

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.

type Ticker

type Ticker interface {
	C() <-chan time.Time
	Stop()
	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(time.Duration) (Ticker, error)
}

TickerFactory creates owned periodic tickers.

type Timer

type Timer interface {
	C() <-chan time.Time
	Stop() bool
	Reset(time.Duration) (bool, error)
}

Timer is an owned, one-shot time event.

Stop and Reset have the same return-value semantics as time.Timer. The owner must stop a timer it no longer needs. As of Go 1.26, timer channels are synchronous and an unbuffered receive after Stop reports true cannot observe a stale value from the prior configuration.

type TimerFactory

type TimerFactory interface {
	NewTimer(time.Duration) (Timer, error)
}

TimerFactory creates owned one-shot timers.

Directories

Path Synopsis
Package clocktest contains deterministic testing helpers for clock.
Package clocktest contains deterministic testing helpers for clock.
Package manual provides deterministic fixed and manually advanced clocks.
Package manual provides deterministic fixed and manually advanced clocks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL