util/time.go (208 lines of code) (raw):
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"sync"
"sync/atomic"
"time"
)
const (
TimeFormat = "2006-01-02 15:04:05"
DateFormat = "2006-01-02"
UnixTimeUnitOffset = uint64(time.Millisecond / time.Nanosecond)
)
var (
_ Clock = &RealClock{}
_ Clock = &MockClock{}
_ Ticker = &RealTicker{}
_ Ticker = &MockTicker{}
_ TickerCreator = &RealTickerCreator{}
_ TickerCreator = &MockTickerCreator{}
)
var (
currentClock *atomic.Value
currentTickerCreator *atomic.Value
)
func init() {
realClock := NewRealClock()
currentClock = new(atomic.Value)
SetClock(realClock)
realTickerCreator := NewRealTickerCreator()
currentTickerCreator = new(atomic.Value)
SetTickerCreator(realTickerCreator)
}
type Clock interface {
Now() time.Time
Sleep(d time.Duration)
CurrentTimeMillis() uint64
CurrentTimeNano() uint64
}
// clockWrapper is used for atomic operation.
type clockWrapper struct {
clock Clock
}
// RealClock wraps some APIs of time package.
type RealClock struct{}
func NewRealClock() *RealClock {
return &RealClock{}
}
func (t *RealClock) Now() time.Time {
return time.Now()
}
func (t *RealClock) Sleep(d time.Duration) {
time.Sleep(d)
}
func (t *RealClock) CurrentTimeMillis() uint64 {
tickerNow := CurrentTimeMillsWithTicker()
if tickerNow > uint64(0) {
return tickerNow
}
return uint64(time.Now().UnixNano()) / UnixTimeUnitOffset
}
func (t *RealClock) CurrentTimeNano() uint64 {
return uint64(t.Now().UnixNano())
}
// MockClock is used for testing.
type MockClock struct {
lock sync.RWMutex
now time.Time
}
func NewMockClock() *MockClock {
return &MockClock{
now: time.Now(),
}
}
func (t *MockClock) Now() time.Time {
t.lock.RLock()
defer t.lock.RUnlock()
return t.now
}
func (t *MockClock) Sleep(d time.Duration) {
if d <= 0 {
return
}
t.lock.Lock()
t.now = t.now.Add(d)
t.lock.Unlock()
time.Sleep(time.Millisecond)
}
func (t *MockClock) CurrentTimeMillis() uint64 {
return uint64(t.Now().UnixNano()) / UnixTimeUnitOffset
}
func (t *MockClock) CurrentTimeNano() uint64 {
return uint64(t.Now().UnixNano())
}
// Ticker interface encapsulates operations like time.Ticker.
type Ticker interface {
C() <-chan time.Time
Stop()
}
// RealTicker wraps time.Ticker.
type RealTicker struct {
t *time.Ticker
}
func NewRealTicker(d time.Duration) *RealTicker {
return &RealTicker{
t: time.NewTicker(d),
}
}
func (t *RealTicker) C() <-chan time.Time {
return t.t.C
}
func (t *RealTicker) Stop() {
t.t.Stop()
}
// MockTicker is usually used for testing.
// MockTicker and MockClock are usually used together.
type MockTicker struct {
lock sync.Mutex
period time.Duration
c chan time.Time
last time.Time
stop chan struct{}
}
func NewMockTicker(d time.Duration) *MockTicker {
t := &MockTicker{
period: d,
c: make(chan time.Time, 1),
last: Now(),
stop: make(chan struct{}),
}
go t.checkLoop()
return t
}
func (t *MockTicker) C() <-chan time.Time {
return t.c
}
func (t *MockTicker) Stop() {
close(t.stop)
}
func (t *MockTicker) check() {
t.lock.Lock()
defer t.lock.Unlock()
now := Now()
for next := t.last.Add(t.period); !next.After(now); next = next.Add(t.period) {
t.last = next
select {
case <-t.stop:
return
case t.c <- t.last:
default:
}
}
}
func (t *MockTicker) checkLoop() {
ticker := time.NewTicker(time.Microsecond)
for {
select {
case <-t.stop:
return
case <-ticker.C:
}
t.check()
}
}
// TickerCreator is used to create Ticker.
type TickerCreator interface {
NewTicker(d time.Duration) Ticker
}
// tickerCreatorWrapper is used for atomic operation.
type tickerCreatorWrapper struct {
tickerCreator TickerCreator
}
// RealTickerCreator is used to creates RealTicker which wraps time.Ticker.
type RealTickerCreator struct{}
func NewRealTickerCreator() *RealTickerCreator {
return &RealTickerCreator{}
}
func (tc *RealTickerCreator) NewTicker(d time.Duration) Ticker {
return NewRealTicker(d)
}
// MockTickerCreator is used create MockTicker which is usually used for testing.
// MockTickerCreator and MockClock are usually used together.
type MockTickerCreator struct{}
func NewMockTickerCreator() *MockTickerCreator {
return &MockTickerCreator{}
}
func (tc *MockTickerCreator) NewTicker(d time.Duration) Ticker {
return NewMockTicker(d)
}
// SetClock sets the clock used by util package.
// In general, no need to set it. It is usually used for testing.
func SetClock(c Clock) {
currentClock.Store(&clockWrapper{c})
}
// CurrentClock returns the current clock used by util package.
func CurrentClock() Clock {
return currentClock.Load().(*clockWrapper).clock
}
// SetTickerCreator sets the ticker creator used by util package.
// In general, no need to set it. It is usually used for testing.
func SetTickerCreator(tc TickerCreator) {
currentTickerCreator.Store(&tickerCreatorWrapper{tc})
}
// CurrentTickerCreator returns the current ticker creator used by util package.
func CurrentTickerCreator() TickerCreator {
return currentTickerCreator.Load().(*tickerCreatorWrapper).tickerCreator
}
func NewTicker(d time.Duration) Ticker {
return CurrentTickerCreator().NewTicker(d)
}
// FormatTimeMillis formats Unix timestamp (ms) to time string.
func FormatTimeMillis(tsMillis uint64) string {
return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(TimeFormat)
}
// FormatDate formats Unix timestamp (ms) to date string
func FormatDate(tsMillis uint64) string {
return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(DateFormat)
}
// Returns the current Unix timestamp in milliseconds.
func CurrentTimeMillis() uint64 {
return CurrentClock().CurrentTimeMillis()
}
// Returns the current Unix timestamp in nanoseconds.
func CurrentTimeNano() uint64 {
return CurrentClock().CurrentTimeNano()
}
// Now returns the current local time.
func Now() time.Time {
return CurrentClock().Now()
}
// Sleep pauses the current goroutine for at least the duration d.
// A negative or zero duration causes Sleep to return immediately.
func Sleep(d time.Duration) {
CurrentClock().Sleep(d)
}