@@ -18,7 +18,6 @@ import (
18
18
"errors"
19
19
"fmt"
20
20
"math"
21
- "regexp"
22
21
"strconv"
23
22
"strings"
24
23
"time"
@@ -183,54 +182,78 @@ func (d *Duration) Type() string {
183
182
return "duration"
184
183
}
185
184
186
- var durationRE = regexp .MustCompile ("^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$" )
185
+ func isdigit (c byte ) bool { return c >= '0' && c <= '9' }
186
+
187
+ // Units are required to go in order from biggest to smallest.
188
+ // This guards against confusion from "1m1d" being 1 minute + 1 day, not 1 month + 1 day.
189
+ var unitMap = map [string ]struct {
190
+ pos int
191
+ mult uint64
192
+ }{
193
+ "ms" : {7 , uint64 (time .Millisecond )},
194
+ "s" : {6 , uint64 (time .Second )},
195
+ "m" : {5 , uint64 (time .Minute )},
196
+ "h" : {4 , uint64 (time .Hour )},
197
+ "d" : {3 , uint64 (24 * time .Hour )},
198
+ "w" : {2 , uint64 (7 * 24 * time .Hour )},
199
+ "y" : {1 , uint64 (365 * 24 * time .Hour )},
200
+ }
187
201
188
202
// ParseDuration parses a string into a time.Duration, assuming that a year
189
203
// always has 365d, a week always has 7d, and a day always has 24h.
190
- func ParseDuration (durationStr string ) (Duration , error ) {
191
- switch durationStr {
204
+ func ParseDuration (s string ) (Duration , error ) {
205
+ switch s {
192
206
case "0" :
193
207
// Allow 0 without a unit.
194
208
return 0 , nil
195
209
case "" :
196
210
return 0 , errors .New ("empty duration string" )
197
211
}
198
- matches := durationRE .FindStringSubmatch (durationStr )
199
- if matches == nil {
200
- return 0 , fmt .Errorf ("not a valid duration string: %q" , durationStr )
201
- }
202
- var dur time.Duration
203
212
204
- // Parse the match at pos `pos` in the regex and use `mult` to turn that
205
- // into ms, then add that value to the total parsed duration.
206
- var overflowErr error
207
- m := func (pos int , mult time.Duration ) {
208
- if matches [pos ] == "" {
209
- return
213
+ orig := s
214
+ var dur uint64
215
+ lastUnitPos := 0
216
+
217
+ for s != "" {
218
+ if ! isdigit (s [0 ]) {
219
+ return 0 , fmt .Errorf ("not a valid duration string: %q" , orig )
220
+ }
221
+ // Consume [0-9]*
222
+ i := 0
223
+ for ; i < len (s ) && isdigit (s [i ]); i ++ {
224
+ }
225
+ v , err := strconv .ParseUint (s [:i ], 10 , 0 )
226
+ if err != nil {
227
+ return 0 , fmt .Errorf ("not a valid duration string: %q" , orig )
210
228
}
211
- n , _ := strconv . Atoi ( matches [ pos ])
229
+ s = s [ i :]
212
230
231
+ // Consume unit.
232
+ for i = 0 ; i < len (s ) && ! isdigit (s [i ]); i ++ {
233
+ }
234
+ if i == 0 {
235
+ return 0 , fmt .Errorf ("not a valid duration string: %q" , orig )
236
+ }
237
+ u := s [:i ]
238
+ s = s [i :]
239
+ unit , ok := unitMap [u ]
240
+ if ! ok {
241
+ return 0 , fmt .Errorf ("unknown unit %q in duration %q" , u , orig )
242
+ }
243
+ if unit .pos <= lastUnitPos { // Units must go in order from biggest to smallest.
244
+ return 0 , fmt .Errorf ("not a valid duration string: %q" , orig )
245
+ }
246
+ lastUnitPos = unit .pos
213
247
// Check if the provided duration overflows time.Duration (> ~ 290years).
214
- if n > int (( 1 << 63 - 1 ) / mult / time . Millisecond ) {
215
- overflowErr = errors .New ("duration out of range" )
248
+ if v > 1 << 63 / unit . mult {
249
+ return 0 , errors .New ("duration out of range" )
216
250
}
217
- d := time .Duration (n ) * time .Millisecond
218
- dur += d * mult
219
-
220
- if dur < 0 {
221
- overflowErr = errors .New ("duration out of range" )
251
+ dur += v * unit .mult
252
+ if dur > 1 << 63 - 1 {
253
+ return 0 , errors .New ("duration out of range" )
222
254
}
223
255
}
224
-
225
- m (2 , 1000 * 60 * 60 * 24 * 365 ) // y
226
- m (4 , 1000 * 60 * 60 * 24 * 7 ) // w
227
- m (6 , 1000 * 60 * 60 * 24 ) // d
228
- m (8 , 1000 * 60 * 60 ) // h
229
- m (10 , 1000 * 60 ) // m
230
- m (12 , 1000 ) // s
231
- m (14 , 1 ) // ms
232
-
233
- return Duration (dur ), overflowErr
256
+ return Duration (dur ), nil
234
257
}
235
258
236
259
func (d Duration ) String () string {
0 commit comments