github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/args/fmt_duration.go (about) 1 // Copyright 2020 Insolar Network Ltd. 2 // All rights reserved. 3 // This material is licensed under the Insolar License version 1.0, 4 // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md. 5 6 package args 7 8 import ( 9 "fmt" 10 "math" 11 "strings" 12 "time" 13 ) 14 15 /* 16 NB! "µs" require 3 bytes, not 2 bytes, hence the difference with "ms" on a number of decimal positions 17 */ 18 func DurationFixedLen(d time.Duration, expectedLen int) string { 19 return durationFixedLen(d, d, expectedLen) 20 } 21 22 func durationFixedLen(d, base time.Duration, expectedLen int) string { 23 24 base, baseUnit := metricDurationBase(base) 25 if base < time.Minute { 26 return fmtMetric(d, base, baseUnit, expectedLen) 27 } 28 29 return fmtAboveSeconds(d, expectedLen) 30 } 31 32 func fmtMetric(d time.Duration, base time.Duration, baseUnit string, expectedLen int) string { 33 w := expectedLen - len(baseUnit) 34 v := float64(d) / float64(base) 35 36 if w < 1 { 37 w = 1 38 } 39 vRounded := math.Round(v) 40 if vRounded >= math.Pow10(w) { 41 return durationFixedLen(d, base*1000, expectedLen) 42 } 43 44 if w < 3 || vRounded >= math.Pow10(w-2) { 45 return fmt.Sprintf("%.0f%s", v, baseUnit) 46 } 47 48 b := strings.Builder{} 49 b.Grow(w + len(baseUnit)) 50 51 vInt, vFrac := math.Modf(v) 52 53 b.WriteString(fmt.Sprintf("%d.", uint64(vInt))) 54 decimalCount := w - b.Len() 55 decimals := uint64(vFrac * math.Pow10(decimalCount)) 56 b.WriteString(fmt.Sprintf("%0*d", decimalCount, decimals)) 57 b.WriteString(baseUnit) 58 return b.String() 59 } 60 61 func metricDurationBase(d time.Duration) (time.Duration, string) { 62 if d < 10*time.Minute { 63 switch { 64 case d > 500*time.Millisecond: 65 return time.Second, "s" 66 case d > 500*time.Microsecond: 67 return time.Millisecond, "ms" 68 default: 69 return time.Microsecond, "µs" 70 } 71 } 72 return time.Minute, "m" 73 } 74 75 func fmtAboveSeconds(d time.Duration, expectedLen int) string { 76 minutes := d / time.Minute 77 seconds := (d - minutes*time.Minute) / time.Second 78 79 if minutes < 600 { 80 return fmtPortions(uint64(seconds), "s", uint64(minutes), "m", expectedLen) 81 } 82 83 hours := minutes / 60 84 minutes -= hours * 60 85 return fmtPortions(uint64(minutes), "m", uint64(hours), "h", expectedLen) 86 } 87 88 func fmtPortions(valueLo uint64, unitLo string, valueHi uint64, unitHi string, expectedLen int) string { 89 if valueLo > 59 { 90 panic("illegal value") 91 } 92 93 switch { 94 case valueHi > 0: 95 break 96 case expectedLen-len(unitLo) < 2: 97 if valueLo > 9 { 98 if valueLo > 30 { 99 valueLo = 0 100 valueHi++ 101 } 102 break 103 } 104 fallthrough 105 default: 106 return fmt.Sprintf("%d%s", valueLo, unitLo) 107 } 108 109 vHi := fmt.Sprintf("%d%s", valueHi, unitHi) 110 if len(vHi)+2+len(unitLo) > expectedLen { 111 if valueLo > 30 { 112 return fmt.Sprintf("%d%s", valueHi+1, unitHi) 113 } 114 return vHi 115 } 116 return vHi + fmt.Sprintf("%02d%s", valueLo, unitLo) 117 } 118 119 func BitCountToMaxDecimalCount(bitLen int) int { 120 121 if bitLen == 0 { 122 return 0 123 } 124 125 const k = 3321928 // 3.3219280948873623478703194294894 = Ln(10) / Ln(2) 126 return int(1 + (uint64(bitLen-1)*1000000)/k) 127 }