Skip to content

Commit fc6dde1

Browse files
committed
separate unixtime with sec and nanosec precision binding
1 parent 3a76635 commit fc6dde1

File tree

2 files changed

+147
-31
lines changed

2 files changed

+147
-31
lines changed

binder.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
* duration
5454
* BindUnmarshaler() interface
5555
* UnixTime() - converts unix time (integer) to time.Time
56+
* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
5657
* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
5758
*/
5859

@@ -1158,29 +1159,54 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
11581159
}
11591160

11601161
// UnixTime binds parameter to time.Time variable (in local Time corresponding to the given Unix time).
1161-
// NB: value larger than 2147483647 (max int32, 2038-01-19T03:14:07Z) is interpreted as UnixNano (unix time with nanosecond precision)
1162-
// where value last 9 characters (e.g. `999999999`) are nano seconds and everything before them are seconds part.
11631162
//
1164-
// This means following limitation:
1165-
// 0 - 2147483647 is parsed in range of 1970-01-01T00:00:00.000000000+00:00 to 2038-01-19T03:14:07Z
1166-
// 2147483648 - ... is parsed in range of 1970-01-01T00:00:02.147483648Z to ...
1163+
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
1164+
//
1165+
// Note:
1166+
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
11671167
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
1168-
return b.unixTime(sourceParam, dest, false)
1168+
return b.unixTime(sourceParam, dest, false, false)
11691169
}
11701170

11711171
// MustUnixTime requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
11721172
// to the given Unix time). Returns error when value does not exist.
1173-
// NB: value larger than 2147483647 (max int32, 2038-01-19T03:14:07Z) is interpreted as UnixNano (unix time with nanosecond precision)
1174-
// where value last 9 characters (e.g. `999999999`) are nano seconds and everything before them are seconds part.
11751173
//
1176-
// This means following limitation:
1177-
// 0 - 2147483647 is parsed in range of 1970-01-01T00:00:00.000000000+00:00 to 2038-01-19T03:14:07Z
1178-
// 2147483648 - ... is parsed in range of 1970-01-01T00:00:02.147483648Z to ...
1174+
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
1175+
//
1176+
// Note:
1177+
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
11791178
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
1180-
return b.unixTime(sourceParam, dest, true)
1179+
return b.unixTime(sourceParam, dest, true, false)
1180+
}
1181+
1182+
// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
1183+
//
1184+
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
1185+
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
1186+
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
1187+
//
1188+
// Note:
1189+
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
1190+
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
1191+
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
1192+
return b.unixTime(sourceParam, dest, false, true)
1193+
}
1194+
1195+
// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable (in local Time corresponding
1196+
// to the given Unix time value in nano second precision). Returns error when value does not exist.
1197+
//
1198+
// Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
1199+
// Example: 1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
1200+
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
1201+
//
1202+
// Note:
1203+
// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
1204+
// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
1205+
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
1206+
return b.unixTime(sourceParam, dest, true, true)
11811207
}
11821208

1183-
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool) *ValueBinder {
1209+
func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
11841210
if b.failFast && b.errors != nil {
11851211
return b
11861212
}
@@ -1199,10 +1225,10 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
11991225
return b
12001226
}
12011227

1202-
if n <= 2147483647 {
1203-
*dest = time.Unix(n, 0)
1204-
} else {
1228+
if isNano {
12051229
*dest = time.Unix(0, n)
1230+
} else {
1231+
*dest = time.Unix(n, 0)
12061232
}
12071233
return b
12081234
}

binder_test.go

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,10 +2434,7 @@ func TestBindWithDelimiter_invalidType(t *testing.T) {
24342434
}
24352435

24362436
func TestValueBinder_UnixTime(t *testing.T) {
2437-
exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43+00:00") // => 1609180603
2438-
exampleTimeNano, _ := time.Parse(time.RFC3339Nano, "2020-12-28T18:36:43.123456789+00:00") // => 1609180603123456789
2439-
// 61123456789 = 61 seconds + 123456789 nano seconds
2440-
exampleTimeNanoShort, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:01:01.123456789+00:00")
2437+
exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43+00:00") // => 1609180603
24412438
var testCases = []struct {
24422439
name string
24432440
givenFailFast bool
@@ -2452,20 +2449,113 @@ func TestValueBinder_UnixTime(t *testing.T) {
24522449
whenURL: "/search?param=1609180603&param=1609180604",
24532450
expectValue: exampleTime,
24542451
},
2452+
{
2453+
name: "ok, binds value, unix time over int32 value",
2454+
whenURL: "/search?param=2147483648&param=1609180604",
2455+
expectValue: time.Unix(2147483648, 0),
2456+
},
2457+
{
2458+
name: "ok, params values empty, value is not changed",
2459+
whenURL: "/search?nope=1",
2460+
expectValue: time.Time{},
2461+
},
2462+
{
2463+
name: "nok, previous errors fail fast without binding value",
2464+
givenFailFast: true,
2465+
whenURL: "/search?param=1&param=100",
2466+
expectValue: time.Time{},
2467+
expectError: "previous error",
2468+
},
2469+
{
2470+
name: "nok, conversion fails, value is not changed",
2471+
whenURL: "/search?param=nope&param=100",
2472+
expectValue: time.Time{},
2473+
expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param",
2474+
},
2475+
{
2476+
name: "ok (must), binds value",
2477+
whenMust: true,
2478+
whenURL: "/search?param=1609180603&param=1609180604",
2479+
expectValue: exampleTime,
2480+
},
2481+
{
2482+
name: "ok (must), params values empty, returns error, value is not changed",
2483+
whenMust: true,
2484+
whenURL: "/search?nope=1",
2485+
expectValue: time.Time{},
2486+
expectError: "code=400, message=required field value is empty, field=param",
2487+
},
2488+
{
2489+
name: "nok (must), previous errors fail fast without binding value",
2490+
givenFailFast: true,
2491+
whenMust: true,
2492+
whenURL: "/search?param=1&param=100",
2493+
expectValue: time.Time{},
2494+
expectError: "previous error",
2495+
},
2496+
{
2497+
name: "nok (must), conversion fails, value is not changed",
2498+
whenMust: true,
2499+
whenURL: "/search?param=nope&param=100",
2500+
expectValue: time.Time{},
2501+
expectError: "code=400, message=failed to bind field value to Time, internal=strconv.ParseInt: parsing \"nope\": invalid syntax, field=param",
2502+
},
2503+
}
2504+
2505+
for _, tc := range testCases {
2506+
t.Run(tc.name, func(t *testing.T) {
2507+
c := createTestContext(tc.whenURL, nil, nil)
2508+
b := QueryParamsBinder(c).FailFast(tc.givenFailFast)
2509+
if tc.givenFailFast {
2510+
b.errors = []error{errors.New("previous error")}
2511+
}
2512+
2513+
dest := time.Time{}
2514+
var err error
2515+
if tc.whenMust {
2516+
err = b.MustUnixTime("param", &dest).BindError()
2517+
} else {
2518+
err = b.UnixTime("param", &dest).BindError()
2519+
}
2520+
2521+
assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano())
2522+
assert.Equal(t, tc.expectValue.In(time.UTC), dest.In(time.UTC))
2523+
if tc.expectError != "" {
2524+
assert.EqualError(t, err, tc.expectError)
2525+
} else {
2526+
assert.NoError(t, err)
2527+
}
2528+
})
2529+
}
2530+
}
2531+
2532+
func TestValueBinder_UnixTimeNano(t *testing.T) {
2533+
exampleTime, _ := time.Parse(time.RFC3339, "2020-12-28T18:36:43.000000000+00:00") // => 1609180603
2534+
exampleTimeNano, _ := time.Parse(time.RFC3339Nano, "2020-12-28T18:36:43.123456789+00:00") // => 1609180603123456789
2535+
exampleTimeNanoBelowSec, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00.999999999+00:00")
2536+
var testCases = []struct {
2537+
name string
2538+
givenFailFast bool
2539+
givenBindErrors []error
2540+
whenURL string
2541+
whenMust bool
2542+
expectValue time.Time
2543+
expectError string
2544+
}{
2545+
{
2546+
name: "ok, binds value, unix time in nano seconds (sec precision)",
2547+
whenURL: "/search?param=1609180603000000000&param=1609180604",
2548+
expectValue: exampleTime,
2549+
},
24552550
{
24562551
name: "ok, binds value, unix time in nano seconds",
24572552
whenURL: "/search?param=1609180603123456789&param=1609180604",
24582553
expectValue: exampleTimeNano,
24592554
},
24602555
{
2461-
name: "ok, binds value, unix time in nano seconds (short)",
2462-
whenURL: "/search?param=61123456789&param=1609180604",
2463-
expectValue: exampleTimeNanoShort,
2464-
},
2465-
{
2466-
name: "ok, binds value, unix time in MAX seconds (2147483647)",
2467-
whenURL: "/search?param=2147483647&param=1609180604",
2468-
expectValue: time.Unix(2147483647, 0),
2556+
name: "ok, binds value, unix time in nano seconds (below 1 sec)",
2557+
whenURL: "/search?param=999999999&param=1609180604",
2558+
expectValue: exampleTimeNanoBelowSec,
24692559
},
24702560
{
24712561
name: "ok, params values empty, value is not changed",
@@ -2488,7 +2578,7 @@ func TestValueBinder_UnixTime(t *testing.T) {
24882578
{
24892579
name: "ok (must), binds value",
24902580
whenMust: true,
2491-
whenURL: "/search?param=1609180603&param=1609180604",
2581+
whenURL: "/search?param=1609180603000000000&param=1609180604",
24922582
expectValue: exampleTime,
24932583
},
24942584
{
@@ -2526,9 +2616,9 @@ func TestValueBinder_UnixTime(t *testing.T) {
25262616
dest := time.Time{}
25272617
var err error
25282618
if tc.whenMust {
2529-
err = b.MustUnixTime("param", &dest).BindError()
2619+
err = b.MustUnixTimeNano("param", &dest).BindError()
25302620
} else {
2531-
err = b.UnixTime("param", &dest).BindError()
2621+
err = b.UnixTimeNano("param", &dest).BindError()
25322622
}
25332623

25342624
assert.Equal(t, tc.expectValue.UnixNano(), dest.UnixNano())

0 commit comments

Comments
 (0)