Skip to content

Commit 75c3814

Browse files
authored
feat: Support negative duration in new function ParseDurationAllowNegative (#793)
Signed-off-by: Dmitry Ponomaryov <[email protected]>
1 parent 7bd5fff commit 75c3814

File tree

2 files changed

+173
-55
lines changed

2 files changed

+173
-55
lines changed

model/time.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ var unitMap = map[string]struct {
201201

202202
// ParseDuration parses a string into a time.Duration, assuming that a year
203203
// always has 365d, a week always has 7d, and a day always has 24h.
204+
// Negative durations are not supported.
204205
func ParseDuration(s string) (Duration, error) {
205206
switch s {
206207
case "0":
@@ -253,18 +254,36 @@ func ParseDuration(s string) (Duration, error) {
253254
return 0, errors.New("duration out of range")
254255
}
255256
}
257+
256258
return Duration(dur), nil
257259
}
258260

261+
// ParseDurationAllowNegative is like ParseDuration but also accepts negative durations.
262+
func ParseDurationAllowNegative(s string) (Duration, error) {
263+
if s == "" || s[0] != '-' {
264+
return ParseDuration(s)
265+
}
266+
267+
d, err := ParseDuration(s[1:])
268+
269+
return -d, err
270+
}
271+
259272
func (d Duration) String() string {
260273
var (
261-
ms = int64(time.Duration(d) / time.Millisecond)
262-
r = ""
274+
ms = int64(time.Duration(d) / time.Millisecond)
275+
r = ""
276+
sign = ""
263277
)
278+
264279
if ms == 0 {
265280
return "0s"
266281
}
267282

283+
if ms < 0 {
284+
sign, ms = "-", -ms
285+
}
286+
268287
f := func(unit string, mult int64, exact bool) {
269288
if exact && ms%mult != 0 {
270289
return
@@ -286,7 +305,7 @@ func (d Duration) String() string {
286305
f("s", 1000, false)
287306
f("ms", 1, false)
288307

289-
return r
308+
return sign + r
290309
}
291310

292311
// MarshalJSON implements the json.Marshaler interface.

model/time_test.go

Lines changed: 151 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -68,70 +68,171 @@ func TestDuration(t *testing.T) {
6868
}
6969

7070
func TestParseDuration(t *testing.T) {
71-
cases := []struct {
72-
in string
73-
out time.Duration
71+
type testCase struct {
72+
in string
73+
out time.Duration
74+
expectedString string
75+
allowedNegative bool
76+
}
7477

75-
expectedString string
76-
}{
78+
baseCases := []testCase{
7779
{
78-
in: "0",
79-
out: 0,
80-
expectedString: "0s",
81-
}, {
82-
in: "0w",
83-
out: 0,
84-
expectedString: "0s",
85-
}, {
86-
in: "0s",
87-
out: 0,
88-
}, {
89-
in: "324ms",
90-
out: 324 * time.Millisecond,
91-
}, {
92-
in: "3s",
93-
out: 3 * time.Second,
94-
}, {
95-
in: "5m",
96-
out: 5 * time.Minute,
97-
}, {
98-
in: "1h",
99-
out: time.Hour,
100-
}, {
101-
in: "4d",
102-
out: 4 * 24 * time.Hour,
103-
}, {
104-
in: "4d1h",
105-
out: 4*24*time.Hour + time.Hour,
106-
}, {
107-
in: "14d",
108-
out: 14 * 24 * time.Hour,
109-
expectedString: "2w",
110-
}, {
111-
in: "3w",
112-
out: 3 * 7 * 24 * time.Hour,
113-
}, {
114-
in: "3w2d1h",
115-
out: 3*7*24*time.Hour + 2*24*time.Hour + time.Hour,
116-
expectedString: "23d1h",
117-
}, {
118-
in: "10y",
119-
out: 10 * 365 * 24 * time.Hour,
80+
in: "0",
81+
out: 0,
82+
expectedString: "0s",
83+
allowedNegative: false,
84+
},
85+
{
86+
in: "0w",
87+
out: 0,
88+
expectedString: "0s",
89+
allowedNegative: false,
90+
},
91+
{
92+
in: "0s",
93+
out: 0,
94+
expectedString: "",
95+
allowedNegative: false,
96+
},
97+
{
98+
in: "324ms",
99+
out: 324 * time.Millisecond,
100+
expectedString: "",
101+
allowedNegative: false,
102+
},
103+
{
104+
in: "3s",
105+
out: 3 * time.Second,
106+
expectedString: "",
107+
allowedNegative: false,
108+
},
109+
{
110+
in: "5m",
111+
out: 5 * time.Minute,
112+
expectedString: "",
113+
allowedNegative: false,
114+
},
115+
{
116+
in: "1h",
117+
out: time.Hour,
118+
expectedString: "",
119+
allowedNegative: false,
120+
},
121+
{
122+
in: "4d",
123+
out: 4 * 24 * time.Hour,
124+
expectedString: "",
125+
allowedNegative: false,
126+
},
127+
{
128+
in: "4d1h",
129+
out: 4*24*time.Hour + time.Hour,
130+
expectedString: "",
131+
allowedNegative: false,
132+
},
133+
{
134+
in: "14d",
135+
out: 14 * 24 * time.Hour,
136+
expectedString: "2w",
137+
allowedNegative: false,
138+
},
139+
{
140+
in: "3w",
141+
out: 3 * 7 * 24 * time.Hour,
142+
expectedString: "",
143+
allowedNegative: false,
144+
},
145+
{
146+
in: "3w2d1h",
147+
out: 3*7*24*time.Hour + 2*24*time.Hour + time.Hour,
148+
expectedString: "23d1h",
149+
allowedNegative: false,
150+
},
151+
{
152+
in: "10y",
153+
out: 10 * 365 * 24 * time.Hour,
154+
expectedString: "",
155+
allowedNegative: false,
120156
},
121157
}
122158

123-
for _, c := range cases {
124-
d, err := ParseDuration(c.in)
159+
negativeCases := []testCase{
160+
{
161+
in: "-3s",
162+
out: -3 * time.Second,
163+
expectedString: "",
164+
allowedNegative: true,
165+
},
166+
{
167+
in: "-5m",
168+
out: -5 * time.Minute,
169+
expectedString: "",
170+
allowedNegative: true,
171+
},
172+
{
173+
in: "-1h",
174+
out: -1 * time.Hour,
175+
expectedString: "",
176+
allowedNegative: true,
177+
},
178+
{
179+
in: "-2d",
180+
out: -2 * 24 * time.Hour,
181+
expectedString: "",
182+
allowedNegative: true,
183+
},
184+
{
185+
in: "-1w",
186+
out: -7 * 24 * time.Hour,
187+
expectedString: "",
188+
allowedNegative: true,
189+
},
190+
{
191+
in: "-3w2d1h",
192+
out: -(3*7*24*time.Hour + 2*24*time.Hour + time.Hour),
193+
expectedString: "-23d1h",
194+
allowedNegative: true,
195+
},
196+
{
197+
in: "-10y",
198+
out: -10 * 365 * 24 * time.Hour,
199+
expectedString: "",
200+
allowedNegative: true,
201+
},
202+
}
203+
204+
for _, c := range baseCases {
205+
c.allowedNegative = true
206+
negativeCases = append(negativeCases, c)
207+
}
208+
209+
allCases := append(baseCases, negativeCases...)
210+
211+
for _, c := range allCases {
212+
var (
213+
d Duration
214+
err error
215+
)
216+
217+
if c.allowedNegative {
218+
d, err = ParseDurationAllowNegative(c.in)
219+
} else {
220+
d, err = ParseDuration(c.in)
221+
}
222+
125223
if err != nil {
126224
t.Errorf("Unexpected error on input %q", c.in)
127225
}
226+
128227
if time.Duration(d) != c.out {
129228
t.Errorf("Expected %v but got %v", c.out, d)
130229
}
230+
131231
expectedString := c.expectedString
132-
if c.expectedString == "" {
232+
if expectedString == "" {
133233
expectedString = c.in
134234
}
235+
135236
if d.String() != expectedString {
136237
t.Errorf("Expected duration string %q but got %q", c.in, d.String())
137238
}
@@ -307,7 +408,6 @@ func TestParseBadDuration(t *testing.T) {
307408
cases := []string{
308409
"1",
309410
"1y1m1d",
310-
"-1w",
311411
"1.5d",
312412
"d",
313413
"294y",
@@ -322,7 +422,6 @@ func TestParseBadDuration(t *testing.T) {
322422
if err == nil {
323423
t.Errorf("Expected error on input %s", c)
324424
}
325-
326425
}
327426
}
328427

0 commit comments

Comments
 (0)