Skip to content

Commit e52e795

Browse files
benjihtommysitu
authored andcommitted
Copied and extended golang ParseDuration to support days and years
1 parent 15955f2 commit e52e795

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

core/templating/parse_duration.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package templating
2+
3+
import (
4+
"errors"
5+
"time"
6+
)
7+
8+
var unitMap = map[string]int64{
9+
"ns": int64(time.Nanosecond),
10+
"us": int64(time.Microsecond),
11+
"µs": int64(time.Microsecond), // U+00B5 = micro symbol
12+
"μs": int64(time.Microsecond), // U+03BC = Greek letter mu
13+
"ms": int64(time.Millisecond),
14+
"s": int64(time.Second),
15+
"m": int64(time.Minute),
16+
"h": int64(time.Hour),
17+
"d": int64(time.Hour * 24),
18+
"y": int64(time.Hour * 24 * 365),
19+
}
20+
21+
// ParseDuration parses a duration string.
22+
// A duration string is a possibly signed sequence of
23+
// decimal numbers, each with optional fraction and a unit suffix,
24+
// such as "300ms", "-1.5h" or "2h45m".
25+
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
26+
func ParseDuration(s string) (time.Duration, error) {
27+
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
28+
orig := s
29+
var d int64
30+
neg := false
31+
32+
// Consume [-+]?
33+
if s != "" {
34+
c := s[0]
35+
if c == '-' || c == '+' {
36+
neg = c == '-'
37+
s = s[1:]
38+
}
39+
}
40+
// Special case: if all that is left is "0", this is zero.
41+
if s == "0" {
42+
return 0, nil
43+
}
44+
if s == "" {
45+
return 0, errors.New("time: invalid duration " + orig)
46+
}
47+
for s != "" {
48+
var (
49+
v, f int64 // integers before, after decimal point
50+
scale float64 = 1 // value = v + f/scale
51+
)
52+
53+
var err error
54+
55+
// The next character must be [0-9.]
56+
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
57+
return 0, errors.New("time: invalid duration " + orig)
58+
}
59+
// Consume [0-9]*
60+
pl := len(s)
61+
v, s, err = leadingInt(s)
62+
if err != nil {
63+
return 0, errors.New("time: invalid duration " + orig)
64+
}
65+
pre := pl != len(s) // whether we consumed anything before a period
66+
67+
// Consume (\.[0-9]*)?
68+
post := false
69+
if s != "" && s[0] == '.' {
70+
s = s[1:]
71+
pl := len(s)
72+
f, scale, s = leadingFraction(s)
73+
post = pl != len(s)
74+
}
75+
if !pre && !post {
76+
// no digits (e.g. ".s" or "-.s")
77+
return 0, errors.New("time: invalid duration " + orig)
78+
}
79+
80+
// Consume unit.
81+
i := 0
82+
for ; i < len(s); i++ {
83+
c := s[i]
84+
if c == '.' || '0' <= c && c <= '9' {
85+
break
86+
}
87+
}
88+
if i == 0 {
89+
return 0, errors.New("time: missing unit in duration " + orig)
90+
}
91+
u := s[:i]
92+
s = s[i:]
93+
unit, ok := unitMap[u]
94+
if !ok {
95+
return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
96+
}
97+
if v > (1<<63-1)/unit {
98+
// overflow
99+
return 0, errors.New("time: invalid duration " + orig)
100+
}
101+
v *= unit
102+
if f > 0 {
103+
// float64 is needed to be nanosecond accurate for fractions of hours.
104+
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
105+
v += int64(float64(f) * (float64(unit) / scale))
106+
if v < 0 {
107+
// overflow
108+
return 0, errors.New("time: invalid duration " + orig)
109+
}
110+
}
111+
d += v
112+
if d < 0 {
113+
// overflow
114+
return 0, errors.New("time: invalid duration " + orig)
115+
}
116+
}
117+
118+
if neg {
119+
d = -d
120+
}
121+
return time.Duration(d), nil
122+
}
123+
124+
// leadingInt consumes the leading [0-9]* from s.
125+
func leadingInt(s string) (x int64, rem string, err error) {
126+
i := 0
127+
for ; i < len(s); i++ {
128+
c := s[i]
129+
if c < '0' || c > '9' {
130+
break
131+
}
132+
if x > (1<<63-1)/10 {
133+
// overflow
134+
return 0, "", errLeadingInt
135+
}
136+
x = x*10 + int64(c) - '0'
137+
if x < 0 {
138+
// overflow
139+
return 0, "", errLeadingInt
140+
}
141+
}
142+
return x, s[i:], nil
143+
}
144+
145+
// leadingFraction consumes the leading [0-9]* from s.
146+
// It is used only for fractions, so does not return an error on overflow,
147+
// it just stops accumulating precision.
148+
func leadingFraction(s string) (x int64, scale float64, rem string) {
149+
i := 0
150+
scale = 1
151+
overflow := false
152+
for ; i < len(s); i++ {
153+
c := s[i]
154+
if c < '0' || c > '9' {
155+
break
156+
}
157+
if overflow {
158+
continue
159+
}
160+
if x > (1<<63-1)/10 {
161+
// It's possible for overflow to give a positive number, so take care.
162+
overflow = true
163+
continue
164+
}
165+
y := x*10 + int64(c) - '0'
166+
if y < 0 {
167+
overflow = true
168+
continue
169+
}
170+
x = y
171+
scale *= 10
172+
}
173+
return x, scale, s[i:]
174+
}
175+
176+
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed

0 commit comments

Comments
 (0)