-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add timepb support library (#60)
* feat: add timepb support library * use pointer for durpb, everywhere * use rapid for fuzzy testing * update IsZero * add docs * remove duplicated test * Update support/timepb/doc.go Co-authored-by: Aaron Craelius <aaron@regen.network> Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com> Co-authored-by: Aaron Craelius <aaron@regen.network>
- Loading branch information
1 parent
3782f42
commit 213b768
Showing
6 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Support Libraries | ||
|
||
This directory provides support libraries for known types. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# timepb | ||
|
||
`timepb` is a Go package that provides functions to do time operations with | ||
[protobuf timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#timestamp) | ||
and [protobuf duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#duration) | ||
structures. | ||
|
||
### Example | ||
|
||
``` go | ||
t1 := &tspb.Timestamp{Seconds: 10, Nanos: 1} | ||
d := &durpb.Duration{Seconds: 1, Nanos: 1e9 - 1} | ||
t2 := Add(t1, d) | ||
|
||
fmt.Println(Compare(&tspb.Timestamp{Seconds: 12, Nanos: 0}, t2) == 0) | ||
fmt.Println(Compare(&tspb.Timestamp{Seconds: 10, Nanos: 1}, t1) == 0) | ||
fmt.Println(Compare(t1, t2)) | ||
// Output: | ||
// true | ||
// true | ||
// -1 | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package timepb | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
|
||
durpb "google.golang.org/protobuf/types/known/durationpb" | ||
tspb "google.golang.org/protobuf/types/known/timestamppb" | ||
) | ||
|
||
// IsZero returns true only when t is nil | ||
func IsZero(t *tspb.Timestamp) bool { | ||
return t == nil | ||
} | ||
|
||
// Commpare t1 and t2 and returns -1 when t1 < t2, 0 when t1 == t2 and 1 otherwise. | ||
// Returns false if t1 or t2 is nil | ||
func Compare(t1, t2 *tspb.Timestamp) int { | ||
if t1 == nil || t2 == nil { | ||
panic(fmt.Sprint("Can't compare nil time, t1=", t1, "t2=", t2)) | ||
} | ||
if t1.Seconds == t2.Seconds && t1.Nanos == t2.Nanos { | ||
return 0 | ||
} | ||
if t1.Seconds < t2.Seconds || t1.Seconds == t2.Seconds && t1.Nanos < t2.Nanos { | ||
return -1 | ||
} | ||
return 1 | ||
} | ||
|
||
// DurationIsNegative returns true if the duration is negative. It assumes that d is valid | ||
// (d..CheckValid() is nil). | ||
func DurationIsNegative(d *durpb.Duration) bool { | ||
return d.Seconds < 0 || d.Seconds == 0 && d.Nanos < 0 | ||
} | ||
|
||
// AddStd returns a new timestamp with value t + d, where d is stdlib Duration. | ||
// If t is nil then nil is returned. | ||
// Panics on overflow. | ||
func AddStd(t *tspb.Timestamp, d time.Duration) *tspb.Timestamp { | ||
if t == nil { | ||
return nil | ||
} | ||
if d == 0 { | ||
t2 := *t | ||
return &t2 | ||
} | ||
t2 := tspb.New(t.AsTime().Add(d)) | ||
overflowPanic(t, t2, d < 0) | ||
return t2 | ||
} | ||
|
||
func overflowPanic(t1, t2 *tspb.Timestamp, negative bool) { | ||
cmp := Compare(t1, t2) | ||
if negative { | ||
if cmp < 0 { | ||
panic("time overflow") | ||
} | ||
} else { | ||
if cmp > 0 { | ||
panic("time overflow") | ||
} | ||
} | ||
} | ||
|
||
const second = int32(time.Second) | ||
|
||
// Add returns a new timestamp with value t + d, where d is protobuf Duration | ||
// If t is nil then nil is returned. Panics on overflow. | ||
// Note: d must be a valid PB Duration (d..CheckValid() is nil). | ||
func Add(t *tspb.Timestamp, d *durpb.Duration) *tspb.Timestamp { | ||
if t == nil { | ||
return nil | ||
} | ||
if d.Seconds == 0 && d.Nanos == 0 { | ||
t2 := *t | ||
return &t2 | ||
} | ||
t2 := tspb.Timestamp{ | ||
Seconds: t.Seconds + d.Seconds, | ||
Nanos: t.Nanos + d.Nanos, | ||
} | ||
if t2.Nanos >= second { | ||
t2.Nanos -= second | ||
t2.Seconds++ | ||
} else if t2.Nanos <= -second { | ||
t2.Nanos += second | ||
t2.Seconds-- | ||
} | ||
overflowPanic(t, &t2, DurationIsNegative(d)) | ||
return &t2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package timepb | ||
|
||
import ( | ||
"fmt" | ||
|
||
durpb "google.golang.org/protobuf/types/known/durationpb" | ||
tspb "google.golang.org/protobuf/types/known/timestamppb" | ||
) | ||
|
||
func ExampleAdd() { | ||
t1 := &tspb.Timestamp{Seconds: 10, Nanos: 1} | ||
d := &durpb.Duration{Seconds: 1, Nanos: 1e9 - 1} | ||
t2 := Add(t1, d) | ||
|
||
fmt.Println(Compare(&tspb.Timestamp{Seconds: 12, Nanos: 0}, t2) == 0) | ||
fmt.Println(Compare(&tspb.Timestamp{Seconds: 10, Nanos: 1}, t1) == 0) | ||
fmt.Println(Compare(t1, t2)) | ||
// Output: | ||
// true | ||
// true | ||
// -1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package timepb | ||
|
||
import ( | ||
"math" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
durpb "google.golang.org/protobuf/types/known/durationpb" | ||
tspb "google.golang.org/protobuf/types/known/timestamppb" | ||
"pgregory.net/rapid" | ||
) | ||
|
||
func new(s int64, n int32) *tspb.Timestamp { | ||
return &tspb.Timestamp{Seconds: s, Nanos: n} | ||
} | ||
|
||
func TestIsZero(t *testing.T) { | ||
tcs := []struct { | ||
t *tspb.Timestamp | ||
expected bool | ||
}{ | ||
{nil, true}, | ||
|
||
{&tspb.Timestamp{}, false}, | ||
{new(0, 0), false}, | ||
{new(1, 0), false}, | ||
{new(0, 1), false}, | ||
{tspb.New(time.Time{}), false}, | ||
} | ||
|
||
for i, tc := range tcs { | ||
require.Equal(t, tc.expected, IsZero(tc.t), "test_id %d", i) | ||
} | ||
} | ||
|
||
func TestCompare(t *testing.T) { | ||
tcs := []struct { | ||
t1 *tspb.Timestamp | ||
t2 *tspb.Timestamp | ||
expected int | ||
}{ | ||
{&tspb.Timestamp{}, &tspb.Timestamp{}, 0}, | ||
{new(1, 1), new(1, 1), 0}, | ||
{new(-1, 1), new(-1, 1), 0}, | ||
{new(231, -5), new(231, -5), 0}, | ||
|
||
{new(1, -1), new(1, 0), -1}, | ||
{new(1, -1), new(12, -1), -1}, | ||
{new(-11, -1), new(-1, -1), -1}, | ||
|
||
{new(1, -1), new(0, -1), 1}, | ||
{new(1, -1), new(1, -2), 1}, | ||
} | ||
for i, tc := range tcs { | ||
r := Compare(tc.t1, tc.t2) | ||
require.Equal(t, tc.expected, r, "test %d", i) | ||
} | ||
|
||
// test panics | ||
tcs2 := []struct { | ||
t1 *tspb.Timestamp | ||
t2 *tspb.Timestamp | ||
}{ | ||
{nil, new(1, 1)}, | ||
{new(1, 1), nil}, | ||
{nil, nil}, | ||
} | ||
for i, tc := range tcs2 { | ||
require.Panics(t, func() { | ||
Compare(tc.t1, tc.t2) | ||
}, "test-panics %d", i) | ||
} | ||
} | ||
|
||
func TestAddFuzzy(t *testing.T) { | ||
check := func(t require.TestingT, s, n int64, d time.Duration) { | ||
t_in := time.Unix(s, n) | ||
t_expected := tspb.New(t_in.Add(d)) | ||
tb := tspb.New(t_in) | ||
tbPb := Add(tb, durpb.New(d)) | ||
tbStd := AddStd(tb, d) | ||
require.Equal(t, *t_expected, *tbStd, "checking pb add") | ||
require.Equal(t, *t_expected, *tbPb, "checking stdlib add") | ||
} | ||
gen := rapid.Int64Range(0, 1<<62) | ||
genNano := rapid.Int64Range(0, 1e9-1) | ||
rInt := func(t *rapid.T, label string) int64 { return gen.Draw(t, label).(int64) } | ||
|
||
rapid.Check(t, func(t *rapid.T) { | ||
s, n, d := rInt(t, "sec"), genNano.Draw(t, "nanos").(int64), time.Duration(rInt(t, "dur")) | ||
check(t, s, n, d) | ||
}) | ||
|
||
check(t, 0, 0, 0) | ||
check(t, 1, 2, 0) | ||
check(t, -1, -1, 1) | ||
|
||
require.Nil(t, Add(nil, &durpb.Duration{Seconds: 1}), "Pb works with nil values") | ||
require.Nil(t, AddStd(nil, time.Second), "Std works with nil values") | ||
} | ||
|
||
func TestAddOverflow(t *testing.T) { | ||
require := require.New(t) | ||
tb := tspb.Timestamp{ | ||
Seconds: math.MaxInt64, | ||
Nanos: 1000, | ||
} | ||
require.Panics(func() { | ||
AddStd(&tb, time.Second) | ||
}, "AddStd should panic on overflow") | ||
|
||
require.Panics(func() { | ||
Add(&tb, &durpb.Duration{Nanos: second - 1}) | ||
}, "Add should panic on overflow") | ||
|
||
// should panic on underflow | ||
|
||
tb = tspb.Timestamp{ | ||
Seconds: -math.MaxInt64 - 1, | ||
Nanos: -1000, | ||
} | ||
require.True(tb.Seconds < 0, "sanity check") | ||
require.Panics(func() { | ||
tt := AddStd(&tb, -time.Second) | ||
t.Log(tt) | ||
}, "AddStd should panic on underflow") | ||
|
||
require.Panics(func() { | ||
tt := Add(&tb, &durpb.Duration{Nanos: -second + 1}) | ||
t.Log(tt) | ||
}, "Add should panic on underflow") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/* | ||
Package timepb provides functions to do time operations with protobuf timestamp | ||
and duration structures. | ||
*/ | ||
package timepb |