@@ -18,7 +18,6 @@ import (
1818 "errors"
1919 "fmt"
2020 "math"
21- "regexp"
2221 "strconv"
2322 "strings"
2423 "time"
@@ -183,54 +182,78 @@ func (d *Duration) Type() string {
183182 return "duration"
184183}
185184
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+ }
187201
188202// ParseDuration parses a string into a time.Duration, assuming that a year
189203// 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 {
192206 case "0" :
193207 // Allow 0 without a unit.
194208 return 0 , nil
195209 case "" :
196210 return 0 , errors .New ("empty duration string" )
197211 }
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
203212
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 )
210228 }
211- n , _ := strconv . Atoi ( matches [ pos ])
229+ s = s [ i :]
212230
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
213247 // 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" )
216250 }
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" )
222254 }
223255 }
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
234257}
235258
236259func (d Duration ) String () string {
0 commit comments