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