clock.go 4.04 KB
Newer Older
1 2 3
// Package clock provides an abstraction for time to enable testing of functionality that uses time as an input.
package clock

4 5 6 7
import (
	"context"
	"time"
)
8 9

// Clock represents time in a way that can be provided by varying implementations.
10 11
// Methods are designed to be direct replacements for methods in the time package,
// with some new additions to make common patterns simple.
12 13 14 15
type Clock interface {
	// Now provides the current local time. Equivalent to time.Now
	Now() time.Time

16 17 18
	// Since returns the time elapsed since t. It is shorthand for time.Now().Sub(t).
	Since(time.Time) time.Duration

19 20 21 22
	// After waits for the duration to elapse and then sends the current time on the returned channel.
	// It is equivalent to time.After
	After(d time.Duration) <-chan time.Time

23 24
	AfterFunc(d time.Duration, f func()) Timer

25 26 27 28 29 30 31
	// NewTicker returns a new Ticker containing a channel that will send
	// the current time on the channel after each tick. The period of the
	// ticks is specified by the duration argument. The ticker will adjust
	// the time interval or drop ticks to make up for slow receivers.
	// The duration d must be greater than zero; if not, NewTicker will
	// panic. Stop the ticker to release associated resources.
	NewTicker(d time.Duration) Ticker
32 33 34 35

	// NewTimer creates a new Timer that will send
	// the current time on its channel after at least duration d.
	NewTimer(d time.Duration) Timer
36 37 38 39

	// SleepCtx sleeps until either ctx is done or the specified duration has elapsed.
	// Returns the ctx.Err if it returns because the context is done.
	SleepCtx(ctx context.Context, d time.Duration) error
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
}

// A Ticker holds a channel that delivers "ticks" of a clock at intervals
type Ticker interface {
	// Ch returns the channel for the ticker. Equivalent to time.Ticker.C
	Ch() <-chan time.Time

	// Stop turns off a ticker. After Stop, no more ticks will be sent.
	// Stop does not close the channel, to prevent a concurrent goroutine
	// reading from the channel from seeing an erroneous "tick".
	Stop()

	// Reset stops a ticker and resets its period to the specified duration.
	// The next tick will arrive after the new period elapses. The duration d
	// must be greater than zero; if not, Reset will panic.
	Reset(d time.Duration)
}

58 59
// Timer represents a single event.
type Timer interface {
60 61 62
	// Ch returns the channel for the ticker. Equivalent to time.Timer.C
	Ch() <-chan time.Time

63 64 65
	// Stop prevents the Timer from firing.
	// It returns true if the call stops the timer, false if the timer has already
	// expired or been stopped.
66 67
	// Stop does not close the channel, to prevent a read from the channel succeeding
	// incorrectly.
68 69 70 71 72 73 74 75 76
	//
	// For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer
	// has already expired and the function f has been started in its own goroutine;
	// Stop does not wait for f to complete before returning.
	// If the caller needs to know whether f is completed, it must coordinate
	// with f explicitly.
	Stop() bool
}

77 78 79 80 81 82 83 84 85 86
// SystemClock provides an instance of Clock that uses the system clock via methods in the time package.
var SystemClock Clock = systemClock{}

type systemClock struct {
}

func (s systemClock) Now() time.Time {
	return time.Now()
}

87 88 89 90
func (s systemClock) Since(t time.Time) time.Duration {
	return time.Since(t)
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
func (s systemClock) After(d time.Duration) <-chan time.Time {
	return time.After(d)
}

type SystemTicker struct {
	*time.Ticker
}

func (t *SystemTicker) Ch() <-chan time.Time {
	return t.C
}

func (s systemClock) NewTicker(d time.Duration) Ticker {
	return &SystemTicker{time.NewTicker(d)}
}
106

107 108 109 110
func (s systemClock) NewTimer(d time.Duration) Timer {
	return &SystemTimer{time.NewTimer(d)}
}

111 112 113 114
type SystemTimer struct {
	*time.Timer
}

115 116 117 118
func (t *SystemTimer) Ch() <-chan time.Time {
	return t.C
}

119 120 121
func (s systemClock) AfterFunc(d time.Duration, f func()) Timer {
	return &SystemTimer{time.AfterFunc(d, f)}
}
122 123 124 125 126 127 128 129 130 131 132

func (s systemClock) SleepCtx(ctx context.Context, d time.Duration) error {
	timer := s.NewTimer(d)
	defer timer.Stop()
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-timer.Ch():
		return nil
	}
}