package prng import ( "bytes" "encoding/binary" "time" ) // TimeSourceFunc is a function which gets an understanding of time in seconds resolution back. type TimeSourceFunc func() int64 // NewUnixTimestampPRNG creates a new Unix timestamp based pseudo random number generator // using the given resolution. The resolution defines at which second interval numbers are generated. func NewUnixTimestampPRNG(resolution int64, timeSourceFunc ...TimeSourceFunc) *UnixTimestampPrng { utrng := &UnixTimestampPrng{ c: make(chan float64), exit: make(chan struct{}), resolution: resolution, timeSourceFunc: func() int64 { return time.Now().Unix() }, } if len(timeSourceFunc) > 0 { utrng.timeSourceFunc = timeSourceFunc[0] } return utrng } // UnixTimestampPrng is a pseudo random number generator using the Unix time in seconds to derive // a random number from. type UnixTimestampPrng struct { c chan float64 exit chan struct{} resolution int64 timeSourceFunc TimeSourceFunc } // Start starts the Unix timestamp pseudo random number generator by examining the // interval and then starting production of numbers after at least interval seconds // plus delta of the next resolution time have elapsed. func (utrng *UnixTimestampPrng) Start() { nowSec := utrng.timeSourceFunc() nextTimePoint := ResolveNextTimePoint(nowSec, utrng.resolution) time.AfterFunc(time.Duration(nextTimePoint-nowSec)*time.Second, func() { // send for the first time right after the timer is executed utrng.send() t := time.NewTicker(time.Duration(utrng.resolution) * time.Second) defer t.Stop() out: for { select { case <-t.C: utrng.send() case <-utrng.exit: break out } } }) } // sends the next pseudo random number to the consumer channel. func (utrng *UnixTimestampPrng) send() { now := utrng.timeSourceFunc() // reduce to last resolution timePoint := now - (now % utrng.resolution) // convert to float64 buf := bytes.NewBuffer(make([]byte, 0, 8)) if err := binary.Write(buf, binary.LittleEndian, timePoint); err != nil { panic(err) } pseudoR := float64(binary.BigEndian.Uint64(buf.Bytes()[:8])>>11) / (1 << 53) // skip slow consumers select { case utrng.c <- pseudoR: default: } } // C returns the channel from which random generated numbers can be consumed from. func (utrng *UnixTimestampPrng) C() <-chan float64 { return utrng.c } // Stop stops the Unix timestamp pseudo random number generator. func (utrng *UnixTimestampPrng) Stop() { utrng.exit <- struct{}{} } // ResolveNextTimePoint returns the next time point. func ResolveNextTimePoint(nowSec int64, resolution int64) int64 { return nowSec + (resolution - nowSec%resolution) }