clock

package
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package clock provides a flexible interface for time-related operations, allowing for consistent time handling in both production and test environments.

The key benefit of this package is that it enables deterministic testing of time-dependent code by providing mock implementations that can be controlled in tests, while using the system clock in production.

The package offers: - A standard interface for getting the current time - A production implementation that uses the system clock - A test implementation that allows precise control over time

Example:

// In production code:
func ProcessWithExpiry(clock clock.Clock, data []Item) []Item {
    now := clock.Now()
    result := make([]Item, 0)
    for _, item := range data {
        if item.ExpiresAt.After(now) {
            result = append(result, item)
        }
    }
    return result
}

// In production:
processor := &Processor{clock: clock.New()}

// In tests:
func TestProcessWithExpiry(t *testing.T) {
    clock := clock.NewTestClock()

    // Set a fixed time for deterministic testing
    fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
    clock.Set(fixedTime)

    // Create test data that expires at different times
    data := []Item{
        {ID: "1", ExpiresAt: fixedTime.Add(-time.Hour)},  // expired
        {ID: "2", ExpiresAt: fixedTime.Add(time.Hour)},   // not expired
    }

    result := ProcessWithExpiry(clock, data)

    // Should only contain the non-expired item
    assert.Len(t, result, 1)
    assert.Equal(t, "2", result[0].ID)

    // Advance time past the expiration of the second item
    clock.Tick(2 * time.Hour)

    // Now all items should be expired
    result = ProcessWithExpiry(clock, data)
    assert.Empty(t, result)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CachedClock

type CachedClock struct {
	// contains filtered or unexported fields
}

CachedClock implements the Clock interface using a cached atomic value to avoid the overhead of system calls from time.Now().

func NewCachedClock

func NewCachedClock(resolution time.Duration) *CachedClock

NewCachedClock creates a new CachedClock that uses the system time cached every [resolution].

Example:

clock := clock.NewCachedClock(time.Millisecond)
currentTime := clock.Now()

func (*CachedClock) Close

func (c *CachedClock) Close()

Close stops the background goroutine that updates the cached time. After calling Close, the clock will continue to return the last cached time but will no longer update. This method should be called to clean up resources when the CachedClock is no longer needed.

func (*CachedClock) NewTicker

func (c *CachedClock) NewTicker(d time.Duration) Ticker

NewTicker delegates to time.NewTicker. Cached time is only an optimization for Now reads; ticker scheduling still runs against the system clock so periodic work fires on real wall-time intervals.

func (*CachedClock) Now

func (c *CachedClock) Now() time.Time

Now returns the current system time. This implementation returns the cached time value.

type Clock

type Clock interface {
	// Now returns the current time.
	// In production implementations, this returns the system time.
	// In test implementations, this returns a controlled time that
	// can be manipulated for testing purposes.
	Now() time.Time

	// NewTicker returns a Ticker that fires every d. Production
	// implementations delegate to time.NewTicker. Test implementations
	// fire when simulated time advances past the ticker period, so a
	// background goroutine driven by NewTicker observes the same notion
	// of time as the rest of the test instead of running on real time.
	NewTicker(d time.Duration) Ticker
}

Clock is an interface for getting the current time and creating tickers. By abstracting time operations behind this interface, code can be written that works with both the system clock in production and controlled time in tests, enabling deterministic testing of time-dependent logic.

This approach helps avoid flaky tests caused by timing dependencies and allows for simulating time-based scenarios without waiting for real time to pass.

type RealClock

type RealClock struct {
}

RealClock implements the Clock interface using the system clock. This is the implementation that should be used in production code.

func New

func New() *RealClock

New creates a new RealClock that uses the system time. This is the standard clock implementation for production use.

Example:

clock := clock.New()
currentTime := clock.Now()

func (*RealClock) NewTicker

func (c *RealClock) NewTicker(d time.Duration) Ticker

NewTicker delegates to time.NewTicker. The returned ticker is a thin adapter around time.Ticker and shares its drop-on-slow-consumer semantics.

func (*RealClock) Now

func (c *RealClock) Now() time.Time

Now returns the current system time. This implementation simply delegates to time.Now().

type TestClock

type TestClock struct {
	// contains filtered or unexported fields
}

TestClock implements the Clock interface with a controlled time value. It allows tests to manually set and advance time to create deterministic test scenarios for time-dependent code.

func NewTestClock

func NewTestClock(now ...time.Time) *TestClock

NewTestClock creates a new TestClock instance. If a specific time is provided, the clock will be initialized to that time. Otherwise, it will be initialized to the current system time.

Example:

// Create a test clock with the current time
clock := clock.NewTestClock()

// Create a test clock with a specific time
fixedTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)
clock := clock.NewTestClock(fixedTime)

func (*TestClock) NewTicker

func (c *TestClock) NewTicker(d time.Duration) Ticker

NewTicker registers a Ticker driven by simulated time. Each Tick or forward Set call delivers every tick whose scheduled fire time fell within the advance, in order, blocking on the consumer for each delivery. A zero or negative period panics, matching the contract of time.NewTicker.

func (*TestClock) Now

func (c *TestClock) Now() time.Time

Now returns the current time as maintained by the TestClock. Unlike RealClock, this time is controlled programmatically rather than being tied to the system clock.

func (*TestClock) Set

func (c *TestClock) Set(t time.Time) time.Time

Set changes the clock to the given time and returns the new time. Forward jumps fire pending Tickers in the same way as TestClock.Tick; backward jumps only adjust the clock value.

Example:

clock := clock.NewTestClock()

// Set to a specific date and time
newYear := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
clock.Set(newYear)

func (*TestClock) Tick

func (c *TestClock) Tick(d time.Duration) time.Time

Tick advances the clock by the given duration and returns the new time. Forward ticks fire any pending Ticker registrations whose period elapsed during the advance, in order. Each fire blocks until the consumer receives the tick from the channel, so by the time Tick returns every due tick has been delivered. The consumer's post-receive work runs asynchronously, so tests asserting on its effects should use a polling helper or a done channel rather than treating Tick's return as a barrier.

Negative or zero durations only update the clock value and do not fire tickers.

Example:

clock := clock.NewTestClock()

// Fast-forward 1 hour
newTime := clock.Tick(time.Hour)

// Fast-forward 30 days
newTime = clock.Tick(30 * 24 * time.Hour)

type Ticker

type Ticker interface {
	// C returns the channel on which the ticks are delivered. Real
	// tickers buffer one tick and drop further ticks while the channel
	// is full; test tickers send synchronously, so a slow consumer
	// holds back simulated time advance until it catches up.
	C() <-chan time.Time

	// Stop turns off the ticker. After Stop, no further ticks are
	// delivered, but the channel is not closed, matching time.Ticker.
	Stop()
}

Ticker delivers ticks on a channel at a regular cadence. It mirrors the surface of time.Ticker needed by callers that work against the Clock interface, so production code can stay agnostic of whether the clock is real or simulated.

Jump to

Keyboard shortcuts

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