github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/util/duration/parse.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // This file is a rough copy of ParseDuration function from go standard library 6 // It adds support for day and year durations 7 8 package duration 9 10 import ( 11 "errors" 12 "time" 13 ) 14 15 var unitMap = map[string]int64{ 16 "ns": int64(time.Nanosecond), 17 "us": int64(time.Microsecond), 18 "µs": int64(time.Microsecond), // U+00B5 = micro symbol 19 "μs": int64(time.Microsecond), // U+03BC = Greek letter mu 20 "ms": int64(time.Millisecond), 21 "s": int64(time.Second), 22 "m": int64(time.Minute), 23 "h": int64(time.Hour), 24 // these are extra units we (pyroscope) added: 25 "d": int64(24) * int64(time.Hour), 26 "M": int64(24*30) * int64(time.Hour), 27 "y": int64(24*365) * int64(time.Hour), 28 } 29 30 var errLeadingInt = errors.New("time: bad [0-9]*") // never printed 31 32 // leadingInt consumes the leading [0-9]* from s. 33 func leadingInt(s string) (x int64, rem string, err error) { 34 i := 0 35 for ; i < len(s); i++ { 36 c := s[i] 37 if c < '0' || c > '9' { 38 break 39 } 40 if x > (1<<63-1)/10 { 41 // overflow 42 return 0, "", errLeadingInt 43 } 44 x = x*10 + int64(c) - '0' 45 if x < 0 { 46 // overflow 47 return 0, "", errLeadingInt 48 } 49 } 50 return x, s[i:], nil 51 } 52 53 // leadingFraction consumes the leading [0-9]* from s. 54 // It is used only for fractions, so does not return an error on overflow, 55 // it just stops accumulating precision. 56 func leadingFraction(s string) (x int64, scale float64, rem string) { 57 i := 0 58 scale = 1 59 overflow := false 60 for ; i < len(s); i++ { 61 c := s[i] 62 if c < '0' || c > '9' { 63 break 64 } 65 if overflow { 66 continue 67 } 68 if x > (1<<63-1)/10 { 69 // It's possible for overflow to give a positive number, so take care. 70 overflow = true 71 continue 72 } 73 y := x*10 + int64(c) - '0' 74 if y < 0 { 75 overflow = true 76 continue 77 } 78 x = y 79 scale *= 10 80 } 81 return x, scale, s[i:] 82 } 83 84 func quote(s string) string { 85 return "\"" + s + "\"" 86 } 87 88 // ParseDuration parses a duration string. 89 // A duration string is a possibly signed sequence of 90 // decimal numbers, each with optional fraction and a unit suffix, 91 // such as "300ms", "-1.5h" or "2h45m". 92 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 93 func ParseDuration(s string) (time.Duration, error) { 94 // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ 95 orig := s 96 var d int64 97 neg := false 98 99 // Consume [-+]? 100 if s != "" { 101 c := s[0] 102 if c == '-' || c == '+' { 103 neg = c == '-' 104 s = s[1:] 105 } 106 } 107 // Special case: if all that is left is "0", this is zero. 108 if s == "0" { 109 return 0, nil 110 } 111 if s == "" { 112 return 0, errors.New("time: invalid duration " + quote(orig)) 113 } 114 for s != "" { 115 var ( 116 v, f int64 // integers before, after decimal point 117 scale float64 = 1 // value = v + f/scale 118 ) 119 120 var err error 121 122 // The next character must be [0-9.] 123 if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { 124 return 0, errors.New("time: invalid duration " + quote(orig)) 125 } 126 // Consume [0-9]* 127 pl := len(s) 128 v, s, err = leadingInt(s) 129 if err != nil { 130 return 0, errors.New("time: invalid duration " + quote(orig)) 131 } 132 pre := pl != len(s) // whether we consumed anything before a period 133 134 // Consume (\.[0-9]*)? 135 post := false 136 if s != "" && s[0] == '.' { 137 s = s[1:] 138 pl := len(s) 139 f, scale, s = leadingFraction(s) 140 post = pl != len(s) 141 } 142 if !pre && !post { 143 // no digits (e.g. ".s" or "-.s") 144 return 0, errors.New("time: invalid duration " + quote(orig)) 145 } 146 147 // Consume unit. 148 i := 0 149 for ; i < len(s); i++ { 150 c := s[i] 151 if c == '.' || '0' <= c && c <= '9' { 152 break 153 } 154 } 155 if i == 0 { 156 return 0, errors.New("time: missing unit in duration " + quote(orig)) 157 } 158 u := s[:i] 159 s = s[i:] 160 unit, ok := unitMap[u] 161 if !ok { 162 return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) 163 } 164 if v > (1<<63-1)/unit { 165 // overflow 166 return 0, errors.New("time: invalid duration " + quote(orig)) 167 } 168 v *= unit 169 if f > 0 { 170 // float64 is needed to be nanosecond accurate for fractions of hours. 171 // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) 172 v += int64(float64(f) * (float64(unit) / scale)) 173 if v < 0 { 174 // overflow 175 return 0, errors.New("time: invalid duration " + quote(orig)) 176 } 177 } 178 d += v 179 if d < 0 { 180 // overflow 181 return 0, errors.New("time: invalid duration " + quote(orig)) 182 } 183 } 184 185 if neg { 186 d = -d 187 } 188 return time.Duration(d), nil 189 }