Skip to content

Commit 3c84672

Browse files
committed
Add Timestamp helper funcs to the ptypes package.
1 parent 1270911 commit 3c84672

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

ptypes/timestamp.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
package ptypes
33+
34+
// This file implements operations on google.protobuf.Timestamp.
35+
36+
import (
37+
"errors"
38+
"fmt"
39+
"time"
40+
41+
tspb "github.com/golang/protobuf/ptypes/timestamp"
42+
)
43+
44+
const (
45+
// Seconds field of the earliest valid Timestamp.
46+
// This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
47+
minValidSeconds = -62135596800
48+
// Seconds field just after the latest valid Timestamp.
49+
// This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
50+
maxValidSeconds = 253402300800
51+
)
52+
53+
// validateTimestamp determines whether a Timestamp is valid.
54+
// A valid timestamp represents a time in the range
55+
// [0001-01-01, 10000-01-01) and has a Nanos field
56+
// in the range [0, 1e9).
57+
//
58+
// If the Timestamp is valid, validateTimestamp returns nil.
59+
// Otherwise, it returns an error that describes
60+
// the problem.
61+
//
62+
// Every valid Timestamp can be represented by a time.Time, but the converse is not true.
63+
func validateTimestamp(ts *tspb.Timestamp) error {
64+
if ts == nil {
65+
return errors.New("timestamp: nil Timestamp")
66+
}
67+
if ts.Seconds < minValidSeconds {
68+
return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
69+
}
70+
if ts.Seconds >= maxValidSeconds {
71+
return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
72+
}
73+
if ts.Nanos < 0 || ts.Nanos >= 1e9 {
74+
return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
75+
}
76+
return nil
77+
}
78+
79+
// Timestamp converts a google.protobuf.Timestamp proto to a time.Time.
80+
// It returns an error if the argument is invalid.
81+
//
82+
// Unlike most Go functions, if Timestamp returns an error, the first return value
83+
// is not the zero time.Time. Instead, it is the value obtained from the
84+
// time.Unix function when passed the contents of the Timestamp, in the UTC
85+
// locale. This may or may not be a meaningful time; many invalid Timestamps
86+
// do map to valid time.Times.
87+
//
88+
// A nil Timestamp returns an error. The first return value in that case is
89+
// undefined.
90+
func Timestamp(ts *tspb.Timestamp) (time.Time, error) {
91+
// Don't return the zero value on error, because corresponds to a valid
92+
// timestamp. Instead return whatever time.Unix gives us.
93+
var t time.Time
94+
if ts == nil {
95+
t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
96+
} else {
97+
t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
98+
}
99+
return t, validateTimestamp(ts)
100+
}
101+
102+
// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
103+
// It returns an error if the resulting Timestamp is invalid.
104+
func TimestampProto(t time.Time) (*tspb.Timestamp, error) {
105+
seconds := t.Unix()
106+
nanos := int32(t.Sub(time.Unix(seconds, 0)))
107+
ts := &tspb.Timestamp{
108+
Seconds: seconds,
109+
Nanos: nanos,
110+
}
111+
if err := validateTimestamp(ts); err != nil {
112+
return nil, err
113+
}
114+
return ts, nil
115+
}
116+
117+
// TimestampString returns the RFC 3339 string for valid Timestamps. For invalid
118+
// Timestamps, it returns an error message in parentheses.
119+
func TimestampString(ts *tspb.Timestamp) string {
120+
t, err := Timestamp(ts)
121+
if err != nil {
122+
return fmt.Sprintf("(%v)", err)
123+
}
124+
return t.Format(time.RFC3339Nano)
125+
}

ptypes/timestamp_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Go support for Protocol Buffers - Google's data interchange format
2+
//
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// https://github.com/golang/protobuf
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are
8+
// met:
9+
//
10+
// * Redistributions of source code must retain the above copyright
11+
// notice, this list of conditions and the following disclaimer.
12+
// * Redistributions in binary form must reproduce the above
13+
// copyright notice, this list of conditions and the following disclaimer
14+
// in the documentation and/or other materials provided with the
15+
// distribution.
16+
// * Neither the name of Google Inc. nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
package ptypes
33+
34+
import (
35+
"math"
36+
"testing"
37+
"time"
38+
39+
"github.com/golang/protobuf/proto"
40+
tspb "github.com/golang/protobuf/ptypes/timestamp"
41+
)
42+
43+
var tests = []struct {
44+
ts *tspb.Timestamp
45+
valid bool
46+
t time.Time
47+
}{
48+
// The timestamp representing the Unix epoch date.
49+
{&tspb.Timestamp{0, 0}, true, utcDate(1970, 1, 1)},
50+
// The smallest representable timestamp.
51+
{&tspb.Timestamp{math.MinInt64, math.MinInt32}, false,
52+
time.Unix(math.MinInt64, math.MinInt32).UTC()},
53+
// The smallest representable timestamp with non-negative nanos.
54+
{&tspb.Timestamp{math.MinInt64, 0}, false, time.Unix(math.MinInt64, 0).UTC()},
55+
// The earliest valid timestamp.
56+
{&tspb.Timestamp{minValidSeconds, 0}, true, utcDate(1, 1, 1)},
57+
//"0001-01-01T00:00:00Z"},
58+
// The largest representable timestamp.
59+
{&tspb.Timestamp{math.MaxInt64, math.MaxInt32}, false,
60+
time.Unix(math.MaxInt64, math.MaxInt32).UTC()},
61+
// The largest representable timestamp with nanos in range.
62+
{&tspb.Timestamp{math.MaxInt64, 1e9 - 1}, false,
63+
time.Unix(math.MaxInt64, 1e9-1).UTC()},
64+
// The largest valid timestamp.
65+
{&tspb.Timestamp{maxValidSeconds - 1, 1e9 - 1}, true,
66+
time.Date(9999, 12, 31, 23, 59, 59, 1e9-1, time.UTC)},
67+
// The smallest invalid timestamp that is larger than the valid range.
68+
{&tspb.Timestamp{maxValidSeconds, 0}, false, time.Unix(maxValidSeconds, 0).UTC()},
69+
// A date before the epoch.
70+
{&tspb.Timestamp{-281836800, 0}, true, utcDate(1961, 1, 26)},
71+
// A date after the epoch.
72+
{&tspb.Timestamp{1296000000, 0}, true, utcDate(2011, 1, 26)},
73+
// A date after the epoch, in the middle of the day.
74+
{&tspb.Timestamp{1296012345, 940483}, true,
75+
time.Date(2011, 1, 26, 3, 25, 45, 940483, time.UTC)},
76+
}
77+
78+
func TestValidateTimestamp(t *testing.T) {
79+
for _, s := range tests {
80+
got := validateTimestamp(s.ts)
81+
if (got == nil) != s.valid {
82+
t.Errorf("validateTimestamp(%v) = %v, want %v", s.ts, got, s.valid)
83+
}
84+
}
85+
}
86+
87+
func TestTimestamp(t *testing.T) {
88+
for _, s := range tests {
89+
got, err := Timestamp(s.ts)
90+
if (err == nil) != s.valid {
91+
t.Errorf("Timestamp(%v) error = %v, but valid = %t", s.ts, err, s.valid)
92+
} else if s.valid && got != s.t {
93+
t.Errorf("Timestamp(%v) = %v, want %v", s.ts, got, s.t)
94+
}
95+
}
96+
// Special case: a nil Timestamp is an error, but returns the 0 Unix time.
97+
got, err := Timestamp(nil)
98+
want := time.Unix(0, 0).UTC()
99+
if got != want {
100+
t.Errorf("Timestamp(nil) = %v, want %v", got, want)
101+
}
102+
if err == nil {
103+
t.Errorf("Timestamp(nil) error = nil, expected error")
104+
}
105+
}
106+
107+
func TestTimestampProto(t *testing.T) {
108+
for _, s := range tests {
109+
got, err := TimestampProto(s.t)
110+
if (err == nil) != s.valid {
111+
t.Errorf("TimestampProto(%v) error = %v, but valid = %t", s.t, err, s.valid)
112+
} else if s.valid && !proto.Equal(got, s.ts) {
113+
t.Errorf("TimestampProto(%v) = %v, want %v", s.t, got, s.ts)
114+
}
115+
}
116+
// No corresponding special case here: no time.Time results in a nil Timestamp.
117+
}
118+
119+
func TestTimestampString(t *testing.T) {
120+
for _, test := range []struct {
121+
ts *tspb.Timestamp
122+
want string
123+
}{
124+
// Not much testing needed because presumably time.Format is
125+
// well-tested.
126+
{&tspb.Timestamp{0, 0}, "1970-01-01T00:00:00Z"},
127+
{&tspb.Timestamp{minValidSeconds - 1, 0}, "(timestamp: seconds:-62135596801 before 0001-01-01)"},
128+
} {
129+
got := TimestampString(test.ts)
130+
if got != test.want {
131+
t.Errorf("TimestampString(%v) = %q, want %q", test.ts, got, test.want)
132+
}
133+
}
134+
}
135+
136+
func utcDate(year, month, day int) time.Time {
137+
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
138+
}

0 commit comments

Comments
 (0)