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 }