package testingx import ( "sync" "time" ) // TimeDeterministic implements time.Now in a deterministic fashion // such that every time.Time call returns a moment in time that occurs // one second after the configured zeroTime. // // It's safe to use this struct from multiple goroutine contexts. type TimeDeterministic struct { // counter counts the number of "ticks" passed since the zero time: each // call to Now increments this counter by one second. counter time.Duration // mu protects fields in this structure from concurrent access. mu sync.Mutex // zeroTime is the lazy-initialized zero time. The first call to Now // will initialize this field with the current time. zeroTime time.Time } // NewTimeDeterministic creates a new instance using the given zeroTime value. func NewTimeDeterministic(zeroTime time.Time) *TimeDeterministic { return &TimeDeterministic{ counter: 0, mu: sync.Mutex{}, zeroTime: zeroTime, } } // Now is like time.Now but more deterministic. The first call returns the // configured zeroTime and subsequent calls return moments in time that occur // exactly one second after the time returned by the previous call. func (td *TimeDeterministic) Now() time.Time { td.mu.Lock() if td.zeroTime.IsZero() { td.zeroTime = time.Now() } offset := td.counter td.counter += time.Second res := td.zeroTime.Add(offset) td.mu.Unlock() return res }