-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtime_parser_test.go
More file actions
345 lines (306 loc) · 9.79 KB
/
time_parser_test.go
File metadata and controls
345 lines (306 loc) · 9.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseTime(t *testing.T) {
t.Run("relative time strings should return future times", func(t *testing.T) {
testCases := []struct {
name string
input string
minDuration time.Duration
maxDuration time.Duration
description string
}{
{
name: "1 minute",
input: "1 min",
minDuration: 59 * time.Second,
maxDuration: 61 * time.Second,
description: "should be approximately 1 minute in the future",
},
{
name: "5 minutes",
input: "5 min",
minDuration: 4*time.Minute + 59*time.Second,
maxDuration: 5*time.Minute + 1*time.Second,
description: "should be approximately 5 minutes in the future",
},
{
name: "1 hour",
input: "1 hour",
minDuration: 59 * time.Minute,
maxDuration: 61 * time.Minute,
description: "should be approximately 1 hour in the future",
},
{
name: "2 hours",
input: "2 hours",
minDuration: 119 * time.Minute,
maxDuration: 121 * time.Minute,
description: "should be approximately 2 hours in the future",
},
{
name: "1 day",
input: "1 day",
minDuration: 23*time.Hour + 59*time.Minute,
maxDuration: 24*time.Hour + 1*time.Minute,
description: "should be approximately 1 day in the future",
},
{
name: "7 days",
input: "7 days",
minDuration: 7*24*time.Hour - 1*time.Minute,
maxDuration: 7*24*time.Hour + 1*time.Minute,
description: "should be approximately 7 days in the future",
},
{
name: "1 week",
input: "1 week",
minDuration: 7*24*time.Hour - 1*time.Minute,
maxDuration: 7*24*time.Hour + 1*time.Minute,
description: "should be approximately 1 week in the future",
},
{
name: "30 days",
input: "30 days",
minDuration: 30*24*time.Hour - 1*time.Minute,
maxDuration: 30*24*time.Hour + 1*time.Minute,
description: "should be approximately 30 days in the future",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
now := time.Now()
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime should not return an error for %q", tc.input)
// Ensure the result is in the future
assert.True(t, result.After(now),
"parsed time should be in the future for %q, got %v which is %v relative to now",
tc.input, result, result.Sub(now))
// Check it's within the expected range
duration := result.Sub(now)
assert.GreaterOrEqual(t, duration, tc.minDuration,
"%s: duration should be at least %v, got %v", tc.description, tc.minDuration, duration)
assert.LessOrEqual(t, duration, tc.maxDuration,
"%s: duration should be at most %v, got %v", tc.description, tc.maxDuration, duration)
})
}
})
t.Run("explicit future times with 'in' prefix", func(t *testing.T) {
testCases := []struct {
name string
input string
minDuration time.Duration
maxDuration time.Duration
}{
{
name: "in 1 min",
input: "in 1 min",
minDuration: 59 * time.Second,
maxDuration: 61 * time.Second,
},
{
name: "in 5 minutes",
input: "in 5 minutes",
minDuration: 4*time.Minute + 59*time.Second,
maxDuration: 5*time.Minute + 1*time.Second,
},
{
name: "in 2 hours",
input: "in 2 hours",
minDuration: 119 * time.Minute,
maxDuration: 121 * time.Minute,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
now := time.Now()
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime should not return an error for %q", tc.input)
assert.True(t, result.After(now), "parsed time should be in the future")
duration := result.Sub(now)
assert.GreaterOrEqual(t, duration, tc.minDuration)
assert.LessOrEqual(t, duration, tc.maxDuration)
})
}
})
t.Run("absolute future dates", func(t *testing.T) {
testCases := []struct {
name string
input string
}{
{
name: "tomorrow",
input: "tomorrow",
},
{
name: "next week",
input: "next week",
},
{
name: "next month",
input: "next month",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
now := time.Now()
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime should not return an error for %q", tc.input)
assert.True(t, result.After(now),
"parsed time should be in the future for %q, got %v", tc.input, result)
})
}
})
t.Run("specific future dates", func(t *testing.T) {
now := time.Now()
futureDate := now.AddDate(0, 0, 10)
// Test with ISO format
input := futureDate.Format("2006-01-02 15:04:05")
result, err := parseTime(input)
require.NoError(t, err)
assert.True(t, result.After(now), "specific future date should be parsed as future")
// Compare with a larger tolerance due to potential timezone differences
// The parsed time might be in UTC while futureDate is in local time
assert.WithinDuration(t, futureDate, result, 24*time.Hour)
// Also verify the date components match
assert.Equal(t, futureDate.Year(), result.Year(), "year should match")
assert.Equal(t, futureDate.Month(), result.Month(), "month should match")
assert.Equal(t, futureDate.Day(), result.Day(), "day should match")
})
t.Run("edge cases - ensure past times are handled", func(t *testing.T) {
// These should be interpreted as future times due to the retry logic
testCases := []struct {
name string
input string
}{
{
name: "monday (could be past or future)",
input: "monday",
},
{
name: "friday",
input: "friday",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
now := time.Now()
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime should not return an error for %q", tc.input)
// The retry logic with "on" prefix should ensure this is in the future
assert.True(t, result.After(now) || result.Equal(now),
"parsed time should be in the future or now for %q, got %v which is %v relative to now",
tc.input, result, result.Sub(now))
})
}
})
t.Run("various time formats", func(t *testing.T) {
testCases := []struct {
name string
input string
}{
{name: "10 seconds", input: "10 seconds"},
{name: "30 sec", input: "30 sec"},
{name: "15 minutes", input: "15 minutes"},
{name: "3 hours", input: "3 hours"},
{name: "2 weeks", input: "2 weeks"},
{name: "1 month", input: "1 month"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
now := time.Now()
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime should not return an error for %q", tc.input)
assert.True(t, result.After(now),
"parsed time should be in the future for %q, got %v which is %v after now",
tc.input, result, result.Sub(now))
})
}
})
t.Run("invalid input should return error", func(t *testing.T) {
testCases := []string{
"not a date",
"invalid time string",
"xyz123",
"",
}
for _, input := range testCases {
t.Run(input, func(t *testing.T) {
_, err := parseTime(input)
assert.Error(t, err, "parseTime should return an error for invalid input %q", input)
})
}
})
}
func TestParseTimeAlwaysReturnsFuture(t *testing.T) {
// This test specifically ensures that parseTime ALWAYS returns a future time
// for common relative time strings
relativeTimeStrings := []string{
"1 min",
"2 min",
"5 min",
"10 min",
"30 min",
"1 hour",
"2 hours",
"1 day",
"2 days",
"7 days",
"1 week",
"2 weeks",
"1 month",
}
for _, input := range relativeTimeStrings {
t.Run(input, func(t *testing.T) {
now := time.Now()
result, err := parseTime(input)
require.NoError(t, err, "parseTime(%q) should not return an error", input)
require.True(t, result.After(now),
"parseTime(%q) MUST return a future time. Got %v (now: %v, diff: %v)",
input, result, now, result.Sub(now))
// Additional check: the difference should be positive
diff := result.Sub(now)
assert.Positive(t, diff.Seconds(),
"time difference should be positive for %q, got %v seconds", input, diff.Seconds())
})
}
}
func TestParseTimeReturnsLocalTimezone(t *testing.T) {
// This test ensures that parseTime ALWAYS returns times in the local timezone
testCases := []struct {
name string
input string
}{
{name: "relative time - 1 min", input: "1 min"},
{name: "relative time - 1 hour", input: "1 hour"},
{name: "relative time - 1 day", input: "1 day"},
{name: "absolute time - tomorrow", input: "tomorrow"},
{name: "weekday - monday", input: "monday"},
{name: "explicit time - in 2 hours", input: "in 2 hours"},
{name: "ISO format", input: "2026-01-01 12:00:00"},
}
localZone := time.Now().Location()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := parseTime(tc.input)
require.NoError(t, err, "parseTime(%q) should not return an error", tc.input)
// Check that the timezone matches the local timezone
resultZone, resultOffset := result.Zone()
localZoneName, localOffset := time.Now().Zone()
assert.Equal(t, localZoneName, resultZone,
"parseTime(%q) should return time in local timezone. Got %s, expected %s",
tc.input, resultZone, localZoneName)
assert.Equal(t, localOffset, resultOffset,
"parseTime(%q) should return time with local timezone offset. Got %d, expected %d",
tc.input, resultOffset, localOffset)
// Verify the location is local
assert.Equal(t, localZone.String(), result.Location().String(),
"parseTime(%q) should return time in local location. Got %s, expected %s",
tc.input, result.Location().String(), localZone.String())
})
}
}