-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
261 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
// This package contains the parseDuration function from the time package in the standard library | ||
|
||
package time | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
) | ||
|
||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed | ||
|
||
// leadingInt consumes the leading [0-9]* from s. | ||
func leadingInt(s string) (x int64, rem string, err error) { | ||
i := 0 | ||
for ; i < len(s); i++ { | ||
c := s[i] | ||
if c < '0' || c > '9' { | ||
break | ||
} | ||
if x > (1<<63-1)/10 { | ||
// overflow | ||
return 0, "", errLeadingInt | ||
} | ||
x = x*10 + int64(c) - '0' | ||
if x < 0 { | ||
// overflow | ||
return 0, "", errLeadingInt | ||
} | ||
} | ||
return x, s[i:], nil | ||
} | ||
|
||
// leadingFraction consumes the leading [0-9]* from s. | ||
// It is used only for fractions, so does not return an error on overflow, | ||
// it just stops accumulating precision. | ||
func leadingFraction(s string) (x int64, scale float64, rem string) { | ||
i := 0 | ||
scale = 1 | ||
overflow := false | ||
for ; i < len(s); i++ { | ||
c := s[i] | ||
if c < '0' || c > '9' { | ||
break | ||
} | ||
if overflow { | ||
continue | ||
} | ||
if x > (1<<63-1)/10 { | ||
// It's possible for overflow to give a positive number, so take care. | ||
overflow = true | ||
continue | ||
} | ||
y := x*10 + int64(c) - '0' | ||
if y < 0 { | ||
overflow = true | ||
continue | ||
} | ||
x = y | ||
scale *= 10 | ||
} | ||
return x, scale, s[i:] | ||
} | ||
|
||
var unitMap = map[string]int64{ | ||
"s": int64(time.Second), | ||
"m": int64(time.Minute), | ||
"h": int64(time.Hour), | ||
"d": int64(time.Hour * 24), | ||
} | ||
|
||
// ParseDuration parses a duration string. | ||
// A duration string is a possibly signed sequence of | ||
// decimal numbers, each with optional fraction and a unit suffix, | ||
// such as "300ms", "-1.5h" or "2h45m". | ||
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". | ||
func ParseDuration(s string) (time.Duration, error) { | ||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ | ||
orig := s | ||
var d int64 | ||
neg := false | ||
|
||
// Consume [-+]? | ||
if s != "" { | ||
c := s[0] | ||
if c == '-' || c == '+' { | ||
neg = c == '-' | ||
s = s[1:] | ||
} | ||
} | ||
// Special case: if all that is left is "0", this is zero. | ||
if s == "0" { | ||
return 0, nil | ||
} | ||
if s == "" { | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
for s != "" { | ||
var ( | ||
v, f int64 // integers before, after decimal point | ||
scale float64 = 1 // value = v + f/scale | ||
) | ||
|
||
var err error | ||
|
||
// The next character must be [0-9.] | ||
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
// Consume [0-9]* | ||
pl := len(s) | ||
v, s, err = leadingInt(s) | ||
if err != nil { | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
pre := pl != len(s) // whether we consumed anything before a period | ||
|
||
// Consume (\.[0-9]*)? | ||
post := false | ||
if s != "" && s[0] == '.' { | ||
s = s[1:] | ||
pl := len(s) | ||
f, scale, s = leadingFraction(s) | ||
post = pl != len(s) | ||
} | ||
if !pre && !post { | ||
// no digits (e.g. ".s" or "-.s") | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
|
||
// Consume unit. | ||
i := 0 | ||
for ; i < len(s); i++ { | ||
c := s[i] | ||
if c == '.' || '0' <= c && c <= '9' { | ||
break | ||
} | ||
} | ||
if i == 0 { | ||
return 0, errors.New("time: missing unit in duration " + orig) | ||
} | ||
u := s[:i] | ||
s = s[i:] | ||
unit, ok := unitMap[u] | ||
if !ok { | ||
return 0, errors.New("time: unknown unit " + u + " in duration " + orig) | ||
} | ||
if v > (1<<63-1)/unit { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
v *= unit | ||
if f > 0 { | ||
// float64 is needed to be nanosecond accurate for fractions of hours. | ||
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) | ||
v += int64(float64(f) * (float64(unit) / scale)) | ||
if v < 0 { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
} | ||
d += v | ||
if d < 0 { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + orig) | ||
} | ||
} | ||
|
||
if neg { | ||
d = -d | ||
} | ||
return time.Duration(d), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package time | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
var parseDurationTests = []struct { | ||
in string | ||
ok bool | ||
want time.Duration | ||
}{ | ||
// simple | ||
{"0", true, 0}, | ||
{"5s", true, 5 * time.Second}, | ||
{"30s", true, 30 * time.Second}, | ||
{"1478s", true, 1478 * time.Second}, | ||
// sign | ||
{"-5s", true, -5 * time.Second}, | ||
{"+5s", true, 5 * time.Second}, | ||
{"-0", true, 0}, | ||
{"+0", true, 0}, | ||
// decimal | ||
{"5.0s", true, 5 * time.Second}, | ||
{"5.6s", true, 5*time.Second + 600*time.Millisecond}, | ||
{"5.s", true, 5 * time.Second}, | ||
{".5s", true, 500 * time.Millisecond}, | ||
{"1.0s", true, 1 * time.Second}, | ||
{"1.00s", true, 1 * time.Second}, | ||
{"1.004s", true, 1*time.Second + 4*time.Millisecond}, | ||
{"1.0040s", true, 1*time.Second + 4*time.Millisecond}, | ||
{"100.00100s", true, 100*time.Second + 1*time.Millisecond}, | ||
// different units | ||
{"14s", true, 14 * time.Second}, | ||
{"15m", true, 15 * time.Minute}, | ||
{"16h", true, 16 * time.Hour}, | ||
{"17d", true, 24 * 17 * time.Hour}, | ||
// composite durations | ||
{"3h30m", true, 3*time.Hour + 30*time.Minute}, | ||
{"10.5s4m", true, 4*time.Minute + 10*time.Second + 500*time.Millisecond}, | ||
{"-2m3.4s", true, -(2*time.Minute + 3*time.Second + 400*time.Millisecond)}, | ||
{"39h9m14.425s", true, 39*time.Hour + 9*time.Minute + 14*time.Second + 425*time.Millisecond}, | ||
// more than 9 digits after decimal point, see https://golang.org/issue/6617 | ||
{"0.3333333333333333333h", true, 20 * time.Minute}, | ||
// huge string; issue 15011. | ||
{"0.100000000000000000000h", true, 6 * time.Minute}, | ||
// This value tests the first overflow check in leadingFraction. | ||
{"0.830103483285477580700h", true, 49*time.Minute + 48*time.Second + 372539827*time.Nanosecond}, | ||
|
||
// errors | ||
{"", false, 0}, | ||
{"3", false, 0}, | ||
{"-", false, 0}, | ||
{"s", false, 0}, | ||
{".", false, 0}, | ||
{"-.", false, 0}, | ||
{".s", false, 0}, | ||
{"+.s", false, 0}, | ||
} | ||
|
||
func TestParseDuration(t *testing.T) { | ||
for _, tc := range parseDurationTests { | ||
d, err := ParseDuration(tc.in) | ||
if tc.ok && (err != nil || d != tc.want) { | ||
t.Errorf("ParseDuration(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want) | ||
} else if !tc.ok && err == nil { | ||
t.Errorf("ParseDuration(%q) = _, nil, want _, non-nil", tc.in) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters