-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add timepb support library #60
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
88f47f4
feat: add timepb support library
robert-zaremba a27e822
Merge branch 'main' into robert/timepb
robert-zaremba 51f2e81
use pointer for durpb, everywhere
robert-zaremba b46e949
use rapid for fuzzy testing
robert-zaremba dd8a281
update IsZero
robert-zaremba 562aca7
add docs
robert-zaremba 17708c0
remove duplicated test
robert-zaremba 2ce84e6
Update support/timepb/doc.go
technicallyty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 | ||
robert-zaremba marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// 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 { | ||
robert-zaremba marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a README or docs.go? Not sure what is best practice. I think the advantage of docs.go is that it shows up in pkg.go.dev like this for sub-packages:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README shows up in pkg.go.dev as well when you go to the package page. I think we need both. Will add doc.go