mirror of
https://github.com/golang/go
synced 2024-11-25 08:57:58 -07:00
time: add ParseDuration.
R=rsc, r, r CC=golang-dev https://golang.org/cl/5489111
This commit is contained in:
parent
e6a322b0b9
commit
f298d0ce29
@ -283,25 +283,16 @@ var atoiError = errors.New("time: invalid number")
|
||||
|
||||
// Duplicates functionality in strconv, but avoids dependency.
|
||||
func atoi(s string) (x int, err error) {
|
||||
i := 0
|
||||
if len(s) > 0 && s[0] == '-' {
|
||||
i++
|
||||
neg := false
|
||||
if s != "" && s[0] == '-' {
|
||||
neg = true
|
||||
s = s[1:]
|
||||
}
|
||||
if i >= len(s) {
|
||||
x, rem, err := leadingInt(s)
|
||||
if err != nil || rem != "" {
|
||||
return 0, atoiError
|
||||
}
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c < '0' || c > '9' {
|
||||
return 0, atoiError
|
||||
}
|
||||
if x >= (1<<31-10)/10 {
|
||||
// will overflow
|
||||
return 0, atoiError
|
||||
}
|
||||
x = x*10 + int(c) - '0'
|
||||
}
|
||||
if s[0] == '-' {
|
||||
if neg {
|
||||
x = -x
|
||||
}
|
||||
return x, nil
|
||||
@ -893,3 +884,126 @@ func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
func leadingInt(s string) (x int, rem string, err error) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
if x >= (1<<31-10)/10 {
|
||||
// overflow
|
||||
return 0, "", errLeadingInt
|
||||
}
|
||||
x = x*10 + int(c) - '0'
|
||||
}
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
var unitMap = map[string]float64{
|
||||
"ns": float64(Nanosecond),
|
||||
"us": float64(Microsecond),
|
||||
"µs": float64(Microsecond), // U+00B5 = micro symbol
|
||||
"μs": float64(Microsecond), // U+03BC = Greek letter mu
|
||||
"ms": float64(Millisecond),
|
||||
"s": float64(Second),
|
||||
"m": float64(Minute),
|
||||
"h": float64(Hour),
|
||||
}
|
||||
|
||||
// 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) (Duration, error) {
|
||||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
||||
orig := s
|
||||
f := float64(0)
|
||||
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 != "" {
|
||||
g := float64(0) // this element of the sequence
|
||||
|
||||
var x int
|
||||
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)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("time: invalid duration " + orig)
|
||||
}
|
||||
g = float64(x)
|
||||
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)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("time: invalid duration " + orig)
|
||||
}
|
||||
scale := 1
|
||||
for n := pl - len(s); n > 0; n-- {
|
||||
scale *= 10
|
||||
}
|
||||
g += float64(x) / float64(scale)
|
||||
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)
|
||||
}
|
||||
|
||||
f += g * unit
|
||||
}
|
||||
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
return Duration(f), nil
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -816,6 +817,82 @@ func TestNotJSONEncodableTime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var parseDurationTests = []struct {
|
||||
in string
|
||||
ok bool
|
||||
want Duration
|
||||
}{
|
||||
// simple
|
||||
{"0", true, 0},
|
||||
{"5s", true, 5 * Second},
|
||||
{"30s", true, 30 * Second},
|
||||
{"1478s", true, 1478 * Second},
|
||||
// sign
|
||||
{"-5s", true, -5 * Second},
|
||||
{"+5s", true, 5 * Second},
|
||||
{"-0", true, 0},
|
||||
{"+0", true, 0},
|
||||
// decimal
|
||||
{"5.0s", true, 5 * Second},
|
||||
{"5.6s", true, 5*Second + 600*Millisecond},
|
||||
{"5.s", true, 5 * Second},
|
||||
{".5s", true, 500 * Millisecond},
|
||||
{"1.0s", true, 1 * Second},
|
||||
{"1.00s", true, 1 * Second},
|
||||
{"1.004s", true, 1*Second + 4*Millisecond},
|
||||
{"1.0040s", true, 1*Second + 4*Millisecond},
|
||||
{"100.00100s", true, 100*Second + 1*Millisecond},
|
||||
// different units
|
||||
{"10ns", true, 10 * Nanosecond},
|
||||
{"11us", true, 11 * Microsecond},
|
||||
{"12µs", true, 12 * Microsecond}, // U+00B5
|
||||
{"12μs", true, 12 * Microsecond}, // U+03BC
|
||||
{"13ms", true, 13 * Millisecond},
|
||||
{"14s", true, 14 * Second},
|
||||
{"15m", true, 15 * Minute},
|
||||
{"16h", true, 16 * Hour},
|
||||
// composite durations
|
||||
{"3h30m", true, 3*Hour + 30*Minute},
|
||||
{"10.5s4m", true, 4*Minute + 10*Second + 500*Millisecond},
|
||||
{"-2m3.4s", true, -(2*Minute + 3*Second + 400*Millisecond)},
|
||||
{"1h2m3s4ms5us6ns", true, 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond},
|
||||
{"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond},
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationRoundTrip(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
// Resolutions finer than milliseconds will result in
|
||||
// imprecise round-trips.
|
||||
d0 := Duration(rand.Int31()) * Millisecond
|
||||
s := d0.String()
|
||||
d1, err := ParseDuration(s)
|
||||
if err != nil || d0 != d1 {
|
||||
t.Errorf("round-trip failed: %d => %q => %d, %v", d0, s, d1, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNow(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Now()
|
||||
|
Loading…
Reference in New Issue
Block a user