github.com/haraldrudell/parl@v0.4.176/ptime/period.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package ptime
     7  
     8  import (
     9  	"time"
    10  
    11  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    12  	"github.com/haraldrudell/parl/perrors"
    13  )
    14  
    15  const (
    16  	FractionScale = uint64(1) << 63
    17  )
    18  
    19  // tPeriodEpoch is epoch for all time periods
    20  var tPeriodEpoch = time.Now()
    21  
    22  type PeriodIndex uint64
    23  
    24  // Period provides real-time based fixed-length interval perioding ordered using a uint64 zero-based index.
    25  //   - Period provides first period index and fractional usage of the first period
    26  type Period struct {
    27  	// interval is ns duration of  period
    28  	interval time.Duration
    29  	// period0 is the numeric value for the first period
    30  	//	- the exact number is relative to a process-wide timestamp
    31  	period0 PeriodIndex
    32  	// this Period was instantiated sometime during the period period0
    33  	//	- fraction0 is how much the first period0 is prior to instantiation
    34  	//	- fraction0 0 means the none of period0 was active
    35  	//	- fraction0 2^63 means all of period0 was active
    36  	fraction0 uint64
    37  }
    38  
    39  // NewPeriod returns a new numbered-interval sequence.
    40  func NewPeriod(interval time.Duration) (period *Period) {
    41  	t := time.Now()
    42  	p := Period{interval: interval}
    43  	p.period0 = p.Index(t)
    44  
    45  	// calculate what fraction of the first period is active
    46  	// uint64 valid decimal digits is : 64 * log10(2) ≈ 19
    47  	// use scale factor that is power of 2: 2^63
    48  	t0 := t.Truncate(interval)
    49  	inactiveDuration := t.Sub(t0)
    50  	p.fraction0 = FractionScale - uint64(float64(inactiveDuration)/float64(interval)*float64(FractionScale))
    51  
    52  	return &p
    53  }
    54  
    55  // Index returns the index number for the current period or the period at time t
    56  //   - index is a number p.period0 or larger
    57  func (p *Period) Index(t ...time.Time) (index PeriodIndex) {
    58  	if p.interval <= 0 {
    59  		panic(perrors.ErrorfPF("period must be positive: %s", Duration(p.interval)))
    60  	}
    61  
    62  	// get the time for which to get index
    63  	var t0 time.Time
    64  	if len(t) > 0 {
    65  		t0 = t[0]
    66  	}
    67  	if t0.IsZero() {
    68  		t0 = time.Now()
    69  	}
    70  	if t0.Before(tPeriodEpoch) {
    71  		panic(perrors.ErrorfPF("time before epoch: %s %s", t0.Format(cyclebreaker.Rfc3339ns), tPeriodEpoch.Format(cyclebreaker.Rfc3339ns)))
    72  	}
    73  
    74  	// get index
    75  	if index = PeriodIndex(t0.Sub(tPeriodEpoch) / p.interval); index < p.period0 {
    76  		panic(perrors.ErrorfPF("time before period0: index: %d period0: %d %s epoch: %s",
    77  			index, p.period0,
    78  			t0.Format(cyclebreaker.Rfc3339ns), tPeriodEpoch.Format(cyclebreaker.Rfc3339ns)),
    79  		)
    80  	}
    81  
    82  	return
    83  }
    84  
    85  // Since returns the number of periods difference that now is greater than before
    86  //   - periods is >= 0
    87  //   - now and before must be at least p.period0
    88  //   - before cannot be greater than now
    89  func (p *Period) Since(now, before PeriodIndex) (periods int) {
    90  	if before < p.period0 {
    91  		panic(perrors.ErrorfPF("Period.Sub with before less than period0: %d now: %d period0: %d",
    92  			before, now, p.period0))
    93  	} else if now < before {
    94  		panic(perrors.ErrorfPF("Period.Sub with before greater than now: %d now: %d period0: %d",
    95  			before, now, p.period0))
    96  	}
    97  
    98  	periods = int(now - before)
    99  
   100  	return
   101  }
   102  
   103  // Sub returns a past period index by n intervals
   104  //   - periodIndex will not be less than p.period0
   105  //   - now cannot be less than p.period0
   106  func (p *Period) Sub(now PeriodIndex, n int) (periodIndex PeriodIndex) {
   107  	if now < p.period0 {
   108  		panic(perrors.ErrorfPF("Period.Sub with now less than period0: %d %d", now, p.period0))
   109  	} else if n == 0 {
   110  		return // nothing to do return
   111  	} else if n < 0 {
   112  		panic(perrors.ErrorfPF("Period.Sub with n negative: %d", n))
   113  	} else if maxN := int(now - p.period0); n > maxN {
   114  		n = maxN
   115  	}
   116  
   117  	periodIndex = now - PeriodIndex(n)
   118  
   119  	return
   120  }
   121  
   122  // Available returns the correct number of slice entries to incldue now
   123  //   - after the inital few periods, returns cap
   124  //   - possible values 1… but no greater than cap
   125  //   - now cannot be less than period0
   126  func (p *Period) Available(now PeriodIndex, cap int) (periods int) {
   127  	if now < p.period0 {
   128  		panic(perrors.ErrorfPF("Period.Sub with now less than period0: %d %d", now, p.period0))
   129  	}
   130  	if periods = int(now-p.period0) + 1; periods > cap {
   131  		periods = cap
   132  	}
   133  	return
   134  }