github.com/wfusion/gofusion@v1.1.14/common/utils/time.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math/rand"
     7  	"sync"
     8  	"time"
     9  
    10  	"go.uber.org/multierr"
    11  )
    12  
    13  // UnixNano returns t as a Unix time, the number of nanoseconds elapsed
    14  // since January 1, 1970 UTC. The result is undefined if the Unix time
    15  // in nanoseconds cannot be represented by an int64 (a date before the year
    16  // 1678 or after 2262). Note that this means the result of calling UnixNano
    17  // on the zero Time is undefined. The result does not depend on the
    18  // location associated with t.
    19  const (
    20  	minYear = 1678
    21  	maxYear = 2262
    22  )
    23  
    24  // GetTime 将毫秒级的时间戳转换成时间
    25  func GetTime(timestampMs int64) time.Time {
    26  	return time.UnixMilli(timestampMs)
    27  }
    28  
    29  // GetTimeStamp 将时间转换成毫秒级的时间戳
    30  func GetTimeStamp(t time.Time) int64 {
    31  	if year := t.Year(); year >= maxYear || year < minYear {
    32  		return t.Unix() * 1e3
    33  	}
    34  	return t.UnixNano() / 1e6
    35  }
    36  
    37  // IsValidTimestamp 返回 false 表示无法对毫秒时间戳和 time.Time 进行精确转换
    38  func IsValidTimestamp(timeMS int64) bool {
    39  	year := GetTime(timeMS).Year()
    40  	return year >= minYear && year < maxYear
    41  }
    42  
    43  type loopWithIntervalOption struct {
    44  	maxTimes uint
    45  	// jitter time
    46  	base, max  time.Duration
    47  	ratio, exp float64
    48  	symmetric  bool
    49  }
    50  
    51  // LoopJitterInterval Deprecated, try github.com/rican7/retry.Retry instead
    52  func LoopJitterInterval(base, max time.Duration, ratio, exp float64,
    53  	symmetric bool) OptionFunc[loopWithIntervalOption] {
    54  	return func(o *loopWithIntervalOption) {
    55  		o.base = base
    56  		o.max = max
    57  		o.ratio = ratio
    58  		o.exp = exp
    59  		o.symmetric = symmetric
    60  	}
    61  }
    62  
    63  // LoopMaxTimes Deprecated, try github.com/rican7/retry.Retry instead
    64  func LoopMaxTimes(maxTimes uint) OptionFunc[loopWithIntervalOption] {
    65  	return func(o *loopWithIntervalOption) {
    66  		o.maxTimes = maxTimes
    67  	}
    68  }
    69  
    70  // LoopWithInterval Deprecated, try github.com/rican7/retry.Retry instead
    71  func LoopWithInterval(ctx context.Context, interval time.Duration,
    72  	fn func() bool, opts ...OptionExtender) (err error) {
    73  	var (
    74  		maxTimes     uint
    75  		nextInterval func() time.Duration
    76  	)
    77  
    78  	opt := ApplyOptions[loopWithIntervalOption](opts...)
    79  	enableJitter := opt.base > 0
    80  	enableMaxTimes := opt.maxTimes > 0
    81  	if enableJitter {
    82  		nextInterval = NextJitterIntervalFunc(opt.base, opt.max, opt.ratio, opt.exp, opt.symmetric)
    83  		interval = nextInterval()
    84  	}
    85  	if enableMaxTimes {
    86  		maxTimes = opt.maxTimes
    87  	}
    88  
    89  	timer := time.NewTimer(interval)
    90  	defer timer.Stop()
    91  	for {
    92  		if fn() {
    93  			return
    94  		}
    95  
    96  		if enableMaxTimes {
    97  			if maxTimes--; maxTimes == 0 {
    98  				return multierr.Append(err, errors.New("exceed the maximum times"))
    99  			}
   100  		}
   101  
   102  		// time.Sleep
   103  		timer.Reset(interval)
   104  		select {
   105  		case <-ctx.Done():
   106  			return ctx.Err()
   107  		case <-timer.C:
   108  			if enableJitter {
   109  				interval = nextInterval()
   110  			}
   111  		}
   112  	}
   113  }
   114  
   115  // NextJitterIntervalFunc generate a jitter and exponential power duration, inspired by net/http.(*Server).Shutdown
   116  func NextJitterIntervalFunc(base, max time.Duration, ratio, exp float64, symmetric bool) func() time.Duration {
   117  	return func() (interval time.Duration) {
   118  		// add specified ratio jitter
   119  		if !symmetric {
   120  			// if ratio is 0.5
   121  			// then interval = base + random(0.5*base) <=> [base, 1.5*base)
   122  			// if ratio is 0.1
   123  			// then interval = base + random(0.1*base) <=> [base, 1.1*base)
   124  			_range := float64(base) * ratio * rand.Float64()
   125  			interval = base + time.Duration(_range)
   126  		} else {
   127  			// if ratio is 0.5
   128  			// then interval = base + random(0.5*base) - 0.25*base <=> [0.75*base, 1.25*base)
   129  			// if ratio is 0.1
   130  			// then interval = base + random(0.1*base) - 0.05*base <=> [0.95*base, 1.05*base)
   131  			_range := float64(base) * ratio * rand.Float64()
   132  			interval = base + time.Duration(_range) - time.Duration(float64(base)*(ratio/2))
   133  		}
   134  
   135  		// double and clamp for next time
   136  		base = time.Duration(float64(base) * exp)
   137  		if base > max {
   138  			base = max
   139  		}
   140  		return interval
   141  	}
   142  }
   143  
   144  // WaitGroupTimeout adds timeout feature for sync.WaitGroup.Wait().
   145  // It returns true, when timeout.
   146  type timeoutOption struct {
   147  	wg *sync.WaitGroup
   148  }
   149  
   150  func TimeoutWg(wg *sync.WaitGroup) OptionFunc[timeoutOption] {
   151  	return func(o *timeoutOption) {
   152  		o.wg = wg
   153  	}
   154  }
   155  
   156  func Timeout(timeout time.Duration, opts ...OptionExtender) bool {
   157  	opt := ApplyOptions[timeoutOption](opts...)
   158  	wgClosed := make(chan struct{}, 1)
   159  	go func() {
   160  		switch {
   161  		case opt.wg != nil:
   162  			opt.wg.Wait()
   163  		}
   164  		wgClosed <- struct{}{}
   165  	}()
   166  
   167  	timer := time.NewTimer(timeout)
   168  	defer timer.Stop()
   169  	select {
   170  	case <-wgClosed:
   171  		return false
   172  	case <-timer.C:
   173  		return true
   174  	}
   175  }