github.com/m3db/m3@v1.5.0/src/x/time/unit.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package time 22 23 import ( 24 "errors" 25 "sort" 26 "time" 27 ) 28 29 // Different time units that are supported. 30 const ( 31 // None is a place holder for time units, it doesn't represent an actual time unit. The ordering 32 // here is used for comparisons betweens units and should not be changed. 33 None Unit = iota 34 Second 35 Millisecond 36 Microsecond 37 Nanosecond 38 Minute 39 Hour 40 Day 41 Year 42 ) 43 44 var ( 45 errUnrecognizedTimeUnit = errors.New("unrecognized time unit") 46 errConvertDurationToUnit = errors.New("unable to convert from duration to time unit") 47 errConvertUnitToDuration = errors.New("unable to convert from time unit to duration") 48 errNegativeDuraton = errors.New("duration cannot be negative") 49 ) 50 51 // Unit represents a time unit. 52 type Unit uint16 53 54 // Value is the time duration of the time unit. 55 func (tu Unit) Value() (time.Duration, error) { 56 if tu < 1 || int(tu) >= unitCount { 57 return 0, errUnrecognizedTimeUnit 58 } 59 return unitsToDuration[tu], nil 60 } 61 62 // Count returns the number of units contained within the duration. 63 func (tu Unit) Count(d time.Duration) (int, error) { 64 if d < 0 { 65 return 0, errNegativeDuraton 66 } 67 68 if tu < 1 || int(tu) >= unitCount { 69 return 0, errUnrecognizedTimeUnit 70 } 71 72 dur := unitsToDuration[tu] 73 return int(d / dur), nil 74 } 75 76 // MustCount is like Count but panics if d is negative or if tu is not 77 // a valid Unit. 78 func (tu Unit) MustCount(d time.Duration) int { 79 c, err := tu.Count(d) 80 if err != nil { 81 panic(err) 82 } 83 84 return c 85 } 86 87 // IsValid returns whether the given time unit is valid / supported. 88 func (tu Unit) IsValid() bool { 89 return tu > 0 && int(tu) < unitCount 90 } 91 92 // Validate will validate the time unit. 93 func (tu Unit) Validate() error { 94 if !tu.IsValid() { 95 return errUnrecognizedTimeUnit 96 } 97 return nil 98 } 99 100 // String returns the string representation for the time unit 101 func (tu Unit) String() string { 102 if tu < 1 || int(tu) >= unitCount { 103 return "unknown" 104 } 105 106 return unitStrings[tu] 107 } 108 109 // UnitFromDuration creates a time unit from a time duration. 110 func UnitFromDuration(d time.Duration) (Unit, error) { 111 i := 0 112 // TODO: remove this once we're on go 1.16+, as for loops prevent inlining with older compilers 113 For: 114 if i >= len(unitLookupArray) { 115 return None, errConvertDurationToUnit 116 } 117 118 if unitLookupArray[i].duration == d { 119 return unitLookupArray[i].unit, nil 120 } 121 i++ 122 goto For 123 } 124 125 // DurationFromUnit creates a time duration from a time unit. 126 func DurationFromUnit(u Unit) (time.Duration, error) { 127 if u < 1 || int(u) >= unitCount { 128 return 0, errConvertUnitToDuration 129 } 130 131 return unitsToDuration[u], nil 132 } 133 134 // MaxUnitForDuration determines the maximum unit for which 135 // the input duration is a multiple of. 136 func MaxUnitForDuration(d time.Duration) (int64, Unit) { 137 var ( 138 currMultiple int64 139 currUnit = Nanosecond 140 dUnixNanos = d.Nanoseconds() 141 isNegative bool 142 ) 143 if dUnixNanos < 0 { 144 dUnixNanos = -dUnixNanos 145 isNegative = true 146 } 147 for _, u := range unitsByDurationDesc { 148 // The unit is guaranteed to be valid so it's safe to ignore error here. 149 duration, _ := u.Value() 150 if dUnixNanos < duration.Nanoseconds() { 151 continue 152 } 153 durationUnixNanos := int64(duration) 154 quotient := dUnixNanos / durationUnixNanos 155 remainder := dUnixNanos - quotient*durationUnixNanos 156 if remainder != 0 { 157 continue 158 } 159 currMultiple = quotient 160 currUnit = u 161 break 162 } 163 if isNegative { 164 currMultiple = -currMultiple 165 } 166 return currMultiple, currUnit 167 } 168 169 var ( 170 unitStrings = []string{ 171 "unknown", 172 "s", 173 "ms", 174 "us", 175 "ns", 176 "m", 177 "h", 178 "d", 179 "y", 180 } 181 182 // Using an array here to avoid map access cost. 183 unitsToDuration = []time.Duration{ 184 None: time.Duration(0), 185 Second: time.Second, 186 Millisecond: time.Millisecond, 187 Microsecond: time.Microsecond, 188 Nanosecond: time.Nanosecond, 189 Minute: time.Minute, 190 Hour: time.Hour, 191 Day: time.Hour * 24, 192 Year: time.Hour * 24 * 365, 193 } 194 195 unitCount = len(unitsToDuration) 196 197 unitsByDurationDesc []Unit 198 ) 199 200 type unitLookupEntry struct { 201 duration time.Duration 202 unit Unit 203 } 204 205 var unitLookupArray []unitLookupEntry 206 207 // byDurationDesc sorts time units by their durations in descending order. 208 // The order is undefined if the units are invalid. 209 type byDurationDesc []Unit 210 211 func (b byDurationDesc) Len() int { return len(b) } 212 func (b byDurationDesc) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 213 214 func (b byDurationDesc) Less(i, j int) bool { 215 vi, _ := b[i].Value() 216 vj, _ := b[j].Value() 217 return vi > vj 218 } 219 220 func init() { 221 unitsByDurationDesc = make([]Unit, 0, unitCount) 222 unitLookupArray = make([]unitLookupEntry, 0, unitCount) 223 224 for u, d := range unitsToDuration { 225 unit := Unit(u) 226 if unit == None { 227 continue 228 } 229 230 unitLookupArray = append(unitLookupArray, unitLookupEntry{unit: unit, duration: d}) 231 unitsByDurationDesc = append(unitsByDurationDesc, unit) 232 } 233 sort.Sort(byDurationDesc(unitsByDurationDesc)) 234 } 235 236 // UnitCount returns the total number of unit types. 237 func UnitCount() int { 238 return unitCount 239 }