github.com/sagernet/sing-box@v1.9.0-rc.20/option/time_unit.go (about) 1 package option 2 3 import ( 4 "errors" 5 "time" 6 ) 7 8 // Copyright 2010 The Go Authors. All rights reserved. 9 // Use of this source code is governed by a BSD-style 10 // license that can be found in the LICENSE file. 11 12 const durationDay = 24 * time.Hour 13 14 var unitMap = map[string]uint64{ 15 "ns": uint64(time.Nanosecond), 16 "us": uint64(time.Microsecond), 17 "µs": uint64(time.Microsecond), // U+00B5 = micro symbol 18 "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu 19 "ms": uint64(time.Millisecond), 20 "s": uint64(time.Second), 21 "m": uint64(time.Minute), 22 "h": uint64(time.Hour), 23 "d": uint64(durationDay), 24 } 25 26 // ParseDuration parses a duration string. 27 // A duration string is a possibly signed sequence of 28 // decimal numbers, each with optional fraction and a unit suffix, 29 // such as "300ms", "-1.5h" or "2h45m". 30 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 31 func ParseDuration(s string) (Duration, error) { 32 // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ 33 orig := s 34 var d uint64 35 neg := false 36 37 // Consume [-+]? 38 if s != "" { 39 c := s[0] 40 if c == '-' || c == '+' { 41 neg = c == '-' 42 s = s[1:] 43 } 44 } 45 // Special case: if all that is left is "0", this is zero. 46 if s == "0" { 47 return 0, nil 48 } 49 if s == "" { 50 return 0, errors.New("time: invalid duration " + quote(orig)) 51 } 52 for s != "" { 53 var ( 54 v, f uint64 // integers before, after decimal point 55 scale float64 = 1 // value = v + f/scale 56 ) 57 58 var err error 59 60 // The next character must be [0-9.] 61 if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { 62 return 0, errors.New("time: invalid duration " + quote(orig)) 63 } 64 // Consume [0-9]* 65 pl := len(s) 66 v, s, err = leadingInt(s) 67 if err != nil { 68 return 0, errors.New("time: invalid duration " + quote(orig)) 69 } 70 pre := pl != len(s) // whether we consumed anything before a period 71 72 // Consume (\.[0-9]*)? 73 post := false 74 if s != "" && s[0] == '.' { 75 s = s[1:] 76 pl := len(s) 77 f, scale, s = leadingFraction(s) 78 post = pl != len(s) 79 } 80 if !pre && !post { 81 // no digits (e.g. ".s" or "-.s") 82 return 0, errors.New("time: invalid duration " + quote(orig)) 83 } 84 85 // Consume unit. 86 i := 0 87 for ; i < len(s); i++ { 88 c := s[i] 89 if c == '.' || '0' <= c && c <= '9' { 90 break 91 } 92 } 93 if i == 0 { 94 return 0, errors.New("time: missing unit in duration " + quote(orig)) 95 } 96 u := s[:i] 97 s = s[i:] 98 unit, ok := unitMap[u] 99 if !ok { 100 return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) 101 } 102 if v > 1<<63/unit { 103 // overflow 104 return 0, errors.New("time: invalid duration " + quote(orig)) 105 } 106 v *= unit 107 if f > 0 { 108 // float64 is needed to be nanosecond accurate for fractions of hours. 109 // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) 110 v += uint64(float64(f) * (float64(unit) / scale)) 111 if v > 1<<63 { 112 // overflow 113 return 0, errors.New("time: invalid duration " + quote(orig)) 114 } 115 } 116 d += v 117 if d > 1<<63 { 118 return 0, errors.New("time: invalid duration " + quote(orig)) 119 } 120 } 121 if neg { 122 return -Duration(d), nil 123 } 124 if d > 1<<63-1 { 125 return 0, errors.New("time: invalid duration " + quote(orig)) 126 } 127 return Duration(d), nil 128 } 129 130 var errLeadingInt = errors.New("time: bad [0-9]*") // never printed 131 132 // leadingInt consumes the leading [0-9]* from s. 133 func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) { 134 i := 0 135 for ; i < len(s); i++ { 136 c := s[i] 137 if c < '0' || c > '9' { 138 break 139 } 140 if x > 1<<63/10 { 141 // overflow 142 return 0, rem, errLeadingInt 143 } 144 x = x*10 + uint64(c) - '0' 145 if x > 1<<63 { 146 // overflow 147 return 0, rem, errLeadingInt 148 } 149 } 150 return x, s[i:], nil 151 } 152 153 // leadingFraction consumes the leading [0-9]* from s. 154 // It is used only for fractions, so does not return an error on overflow, 155 // it just stops accumulating precision. 156 func leadingFraction(s string) (x uint64, scale float64, rem string) { 157 i := 0 158 scale = 1 159 overflow := false 160 for ; i < len(s); i++ { 161 c := s[i] 162 if c < '0' || c > '9' { 163 break 164 } 165 if overflow { 166 continue 167 } 168 if x > (1<<63-1)/10 { 169 // It's possible for overflow to give a positive number, so take care. 170 overflow = true 171 continue 172 } 173 y := x*10 + uint64(c) - '0' 174 if y > 1<<63 { 175 overflow = true 176 continue 177 } 178 x = y 179 scale *= 10 180 } 181 return x, scale, s[i:] 182 } 183 184 // These are borrowed from unicode/utf8 and strconv and replicate behavior in 185 // that package, since we can't take a dependency on either. 186 const ( 187 lowerhex = "0123456789abcdef" 188 runeSelf = 0x80 189 runeError = '\uFFFD' 190 ) 191 192 func quote(s string) string { 193 buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes 194 buf[0] = '"' 195 for i, c := range s { 196 if c >= runeSelf || c < ' ' { 197 // This means you are asking us to parse a time.Duration or 198 // time.Location with unprintable or non-ASCII characters in it. 199 // We don't expect to hit this case very often. We could try to 200 // reproduce strconv.Quote's behavior with full fidelity but 201 // given how rarely we expect to hit these edge cases, speed and 202 // conciseness are better. 203 var width int 204 if c == runeError { 205 width = 1 206 if i+2 < len(s) && s[i:i+3] == string(runeError) { 207 width = 3 208 } 209 } else { 210 width = len(string(c)) 211 } 212 for j := 0; j < width; j++ { 213 buf = append(buf, `\x`...) 214 buf = append(buf, lowerhex[s[i+j]>>4]) 215 buf = append(buf, lowerhex[s[i+j]&0xF]) 216 } 217 } else { 218 if c == '"' || c == '\\' { 219 buf = append(buf, '\\') 220 } 221 buf = append(buf, string(c)...) 222 } 223 } 224 buf = append(buf, '"') 225 return string(buf) 226 }