Package Duration:

- add self parsing to allow capabilities parsing days like '4d13h25m41s'
This commit is contained in:
Nicolas JUHEL
2025-03-25 11:06:18 +01:00
parent a39ad5f911
commit b1a8a6dbe9

View File

@@ -26,21 +26,32 @@
package duration
import (
"errors"
"fmt"
"strings"
"time"
)
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
var unitMap = map[string]uint64{
"ns": uint64(time.Nanosecond),
"us": uint64(time.Microsecond),
"µs": uint64(time.Microsecond), // U+00B5 = micro symbol
"μs": uint64(time.Microsecond), // U+03BC = Greek letter mu
"ms": uint64(time.Millisecond),
"s": uint64(time.Second),
"m": uint64(time.Minute),
"h": uint64(time.Hour),
"d": uint64(24 * time.Hour),
}
func parseString(s string) (Duration, error) {
s = strings.Replace(s, "\"", "", -1)
s = strings.Replace(s, "'", "", -1)
s = strings.Replace(s, " ", "", -1)
// err: 99d55h44m33s123ms
if v, e := time.ParseDuration(s); e != nil {
return 0, e
} else {
return Duration(v), nil
}
return parseDuration(s)
}
func (d *Duration) parseString(s string) error {
@@ -60,3 +71,159 @@ func (d *Duration) unmarshall(val []byte) error {
return nil
}
}
// 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", "d".
func parseDuration(s string) (Duration, error) {
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
orig := s
var d uint64
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, fmt.Errorf("time: invalid duration '%s'", orig)
}
for s != "" {
var (
v, f uint64 // 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, fmt.Errorf("time: invalid duration '%s'", orig)
}
// Consume [0-9]*
pl := len(s)
v, s, err = leadingInt(s)
if err != nil {
return 0, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: invalid duration '%s'", 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, fmt.Errorf("time: missing unit in duration '%s'" + orig)
}
u := s[:i]
s = s[i:]
unit, ok := unitMap[u]
if !ok {
return 0, fmt.Errorf("time: unknown unit '%s' in duration '%s'", u, orig)
}
if v > 1<<63/unit {
// overflow
return 0, fmt.Errorf("time: invalid duration '%s'", 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 += uint64(float64(f) * (float64(unit) / scale))
if v > 1<<63 {
// overflow
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
}
}
d += v
if d > 1<<63 {
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
}
}
if neg {
return -Duration(d), nil
}
if d > 1<<63-1 {
return 0, fmt.Errorf("time: invalid duration '%s'", orig)
}
return Duration(d), nil
}
// leadingInt consumes the leading [0-9]* from s.
func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) {
i := 0
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if x > 1<<63/10 {
// overflow
return 0, rem, errLeadingInt
}
x = x*10 + uint64(c) - '0'
if x > 1<<63 {
// overflow
return 0, rem, 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 uint64, 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 + uint64(c) - '0'
if y > 1<<63 {
overflow = true
continue
}
x = y
scale *= 10
}
return x, scale, s[i:]
}