github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/duration.go (about) 1 // Copyright 2009 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 https://go.dev/LICENSE file. 4 // 5 // Code is borrowed directly from pkg/time.ParseDuration under 6 // Go's BSD-style license, extended with changes to support 7 // days, weeks and year as time Duration units. 8 9 // Package cmd provides extended time.Duration implementation 10 package cmd 11 12 import ( 13 "errors" 14 "strings" 15 "time" 16 ) 17 18 // Duration is a standard unit of time. 19 type Duration time.Duration 20 21 // Days returns the duration as a floating point number of days. 22 func (d Duration) Days() float64 { 23 hour := d / Hour 24 nsec := d % Hour 25 return float64(hour) + float64(nsec)*(1e-9/60/60/24) 26 } 27 28 // Standard unit of time. 29 var ( 30 Nanosecond = Duration(time.Nanosecond) 31 Microsecond = Duration(time.Microsecond) 32 Millisecond = Duration(time.Millisecond) 33 Second = Duration(time.Second) 34 Minute = Duration(time.Minute) 35 Hour = Duration(time.Hour) 36 Day = Hour * 24 37 Week = Day * 7 38 Fortnight = Week * 2 39 Month = Day * 30 // Approximation 40 Year = Day * 365 // Approximation 41 Decade = Year * 10 // Approximation 42 Century = Year * 100 // Approximation 43 Millennium = Year * 1000 // Approximation 44 ) 45 46 // leadingInt consumes the leading [0-9]* from s. 47 // this function is directly copied from 48 // https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/time/format.go;drc=55590f3a2b89f001bcadf0df6eb2dde62618302b;l=1455 49 // only changes the "error string that's being returned" 50 func leadingInt(s string) (x int64, rem string, err error) { 51 errLeadingInt := errors.New("bad [0-9]*") 52 53 i := 0 54 for ; i < len(s); i++ { 55 c := s[i] 56 if c < '0' || c > '9' { 57 break 58 } 59 if x > (1<<63-1)/10 { 60 // overflow 61 return 0, "", errLeadingInt 62 } 63 x = x*10 + int64(c) - '0' 64 if x < 0 { 65 // overflow 66 return 0, "", errLeadingInt 67 } 68 } 69 return x, s[i:], nil 70 } 71 72 // unitMap defines 'unit' to 'actual-value' 73 // translates for calculating duration. 74 var unitMap = map[string]int64{ 75 "ns": int64(Nanosecond), 76 "us": int64(Microsecond), 77 "µs": int64(Microsecond), // U+00B5 = micro symbol 78 "μs": int64(Microsecond), // U+03BC = Greek letter mu 79 "ms": int64(Millisecond), 80 "s": int64(Second), 81 "m": int64(Minute), 82 "h": int64(Hour), 83 "d": int64(Day), 84 "w": int64(Week), 85 "y": int64(Year), // Approximation 86 } 87 88 // ParseDuration parses a duration string. 89 // This implementation is similar to Go's 90 // https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/time/format.go;l=1546;bpv=1;bpt=1 91 // extends it to parse days, weeks and years.. 92 // 93 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "y". 94 func ParseDuration(s string) (Duration, error) { 95 if strings.TrimSpace(s) == "" { 96 return 0, errors.New("invalid empty duration") 97 } 98 99 // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ 100 orig := s 101 var d int64 102 neg := false 103 104 // Consume [-+]? 105 if s != "" { 106 c := s[0] 107 if c == '-' || c == '+' { 108 neg = c == '-' 109 s = s[1:] 110 } 111 } 112 // Special case: if all that is left is "0", this is zero. 113 if s == "0" { 114 return 0, nil 115 } 116 if s == "" { 117 return 0, errors.New("invalid duration " + orig) 118 } 119 for s != "" { 120 var ( 121 v, f int64 // integers before, after decimal point 122 scale float64 = 1 // value = v + f/scale 123 ) 124 125 var err error 126 127 // The next character must be [0-9.] 128 if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { 129 return 0, errors.New("invalid duration " + orig) 130 } 131 // Consume [0-9]* 132 pl := len(s) 133 v, s, err = leadingInt(s) 134 if err != nil { 135 return 0, errors.New("invalid duration " + orig) 136 } 137 pre := pl != len(s) // whether we consumed anything before a period 138 139 // Consume (\.[0-9]*)? 140 post := false 141 if s != "" && s[0] == '.' { 142 s = s[1:] 143 pl := len(s) 144 f, s, err = leadingInt(s) 145 if err != nil { 146 return 0, errors.New("invalid duration " + orig) 147 } 148 for n := pl - len(s); n > 0; n-- { 149 scale *= 10 150 } 151 post = pl != len(s) 152 } 153 if !pre && !post { 154 // no digits (e.g. ".s" or "-.s") 155 return 0, errors.New("invalid duration " + orig) 156 } 157 158 // Consume unit. 159 i := 0 160 for ; i < len(s); i++ { 161 c := s[i] 162 if c == '.' || '0' <= c && c <= '9' { 163 break 164 } 165 } 166 if i == 0 { 167 return 0, errors.New("missing unit in duration " + orig) 168 } 169 u := s[:i] 170 s = s[i:] 171 unit, ok := unitMap[u] 172 if !ok { 173 return 0, errors.New("unknown unit " + u + " in duration " + orig) 174 } 175 if v > (1<<63-1)/unit { 176 // overflow 177 return 0, errors.New("invalid duration " + orig) 178 } 179 v *= unit 180 if f > 0 { 181 // float64 is needed to be nanosecond accurate for fractions of hours. 182 // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) 183 v += int64(float64(f) * (float64(unit) / scale)) 184 if v < 0 { 185 // overflow 186 return 0, errors.New("invalid duration " + orig) 187 } 188 } 189 d += v 190 if d < 0 { 191 // overflow 192 return 0, errors.New("invalid duration " + orig) 193 } 194 } 195 196 if neg { 197 d = -d 198 } 199 return Duration(d), nil 200 }