| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | package nowish | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"sync/atomic" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 	"unsafe" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Start returns a new Clock instance initialized and | 
					
						
							|  |  |  | // started with the provided precision, along with the | 
					
						
							|  |  |  | // stop function for it's underlying timer | 
					
						
							|  |  |  | func Start(precision time.Duration) (*Clock, func()) { | 
					
						
							|  |  |  | 	c := Clock{} | 
					
						
							|  |  |  | 	return &c, c.Start(precision) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Clock struct { | 
					
						
							|  |  |  | 	noCopy noCopy //nolint noCopy because a copy will fuck with atomics | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// format stores the time formatting style string | 
					
						
							|  |  |  | 	format string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// valid indicates whether the current value stored in .Format is valid | 
					
						
							|  |  |  | 	valid uint32 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// mutex protects writes to .Format, not because it would be unsafe, but | 
					
						
							|  |  |  | 	// because we want to minimize unnnecessary allocations | 
					
						
							|  |  |  | 	mutex sync.Mutex | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	// nowfmt is an unsafe pointer to the last-updated time format string | 
					
						
							|  |  |  | 	nowfmt unsafe.Pointer | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	// now is an unsafe pointer to the last-updated time.Time object | 
					
						
							|  |  |  | 	now unsafe.Pointer | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Start starts the clock with the provided precision, the | 
					
						
							|  |  |  | // returned function is the stop function for the underlying timer | 
					
						
							|  |  |  | func (c *Clock) Start(precision time.Duration) func() { | 
					
						
							|  |  |  | 	// Create ticker from duration | 
					
						
							|  |  |  | 	tick := time.NewTicker(precision) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set initial time | 
					
						
							|  |  |  | 	t := time.Now() | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	atomic.StorePointer(&c.now, unsafe.Pointer(&t)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Set initial format | 
					
						
							|  |  |  | 	s := "" | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&s)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// If formatting string unset, set default | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 	if c.format == "" { | 
					
						
							|  |  |  | 		c.format = time.RFC822 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start main routine | 
					
						
							|  |  |  | 	go c.run(tick) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return stop fn | 
					
						
							|  |  |  | 	return tick.Stop | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // run is the internal clock ticking loop | 
					
						
							|  |  |  | func (c *Clock) run(tick *time.Ticker) { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		// Wait on tick | 
					
						
							|  |  |  | 		_, ok := <-tick.C | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Channel closed | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Update time | 
					
						
							|  |  |  | 		t := time.Now() | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		atomic.StorePointer(&c.now, unsafe.Pointer(&t)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Invalidate format string | 
					
						
							|  |  |  | 		atomic.StoreUint32(&c.valid, 0) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Now returns a good (ish) estimate of the current 'now' time | 
					
						
							|  |  |  | func (c *Clock) Now() time.Time { | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	return *(*time.Time)(atomic.LoadPointer(&c.now)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NowFormat returns the formatted "now" time, cached until next tick and "now" updates | 
					
						
							|  |  |  | func (c *Clock) NowFormat() string { | 
					
						
							|  |  |  | 	// If format still valid, return this | 
					
						
							|  |  |  | 	if atomic.LoadUint32(&c.valid) == 1 { | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		return *(*string)(atomic.LoadPointer(&c.nowfmt)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get mutex lock | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Double check still invalid | 
					
						
							|  |  |  | 	if atomic.LoadUint32(&c.valid) == 1 { | 
					
						
							|  |  |  | 		c.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		return *(*string)(atomic.LoadPointer(&c.nowfmt)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate time format | 
					
						
							|  |  |  | 	b := c.Now().AppendFormat( | 
					
						
							|  |  |  | 		make([]byte, 0, len(c.format)), | 
					
						
							|  |  |  | 		c.format, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Update the stored value and set valid! | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&b)) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	atomic.StoreUint32(&c.valid, 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Unlock and return | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Note: | 
					
						
							|  |  |  | 	// it's safe to do this conversion here | 
					
						
							|  |  |  | 	// because this byte slice will never change. | 
					
						
							|  |  |  | 	// and we have the direct pointer to it, we're | 
					
						
							|  |  |  | 	// not requesting it atomicly via c.Format | 
					
						
							|  |  |  | 	return *(*string)(unsafe.Pointer(&b)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SetFormat sets the time format string used by .NowFormat() | 
					
						
							|  |  |  | func (c *Clock) SetFormat(format string) { | 
					
						
							|  |  |  | 	// Get mutex lock | 
					
						
							|  |  |  | 	c.mutex.Lock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Update time format | 
					
						
							|  |  |  | 	c.format = format | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Invalidate current format string | 
					
						
							|  |  |  | 	atomic.StoreUint32(&c.valid, 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Unlock | 
					
						
							|  |  |  | 	c.mutex.Unlock() | 
					
						
							|  |  |  | } |