blob: 026d0d49155dd717927103fbafa490141a40e41c [file] [log] [blame]
Andrea Campanella3614a922021-02-25 12:40:42 +01001// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
khenaidooac637102019-01-14 15:44:34 -05004
5package ptypes
6
khenaidooac637102019-01-14 15:44:34 -05007import (
8 "errors"
9 "fmt"
10 "time"
11
Andrea Campanella3614a922021-02-25 12:40:42 +010012 timestamppb "github.com/golang/protobuf/ptypes/timestamp"
khenaidooac637102019-01-14 15:44:34 -050013)
14
Andrea Campanella3614a922021-02-25 12:40:42 +010015// Range of google.protobuf.Duration as specified in timestamp.proto.
khenaidooac637102019-01-14 15:44:34 -050016const (
17 // Seconds field of the earliest valid Timestamp.
18 // This is time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
19 minValidSeconds = -62135596800
20 // Seconds field just after the latest valid Timestamp.
21 // This is time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Unix().
22 maxValidSeconds = 253402300800
23)
24
Andrea Campanella3614a922021-02-25 12:40:42 +010025// Timestamp converts a timestamppb.Timestamp to a time.Time.
26// It returns an error if the argument is invalid.
27//
28// Unlike most Go functions, if Timestamp returns an error, the first return
29// value is not the zero time.Time. Instead, it is the value obtained from the
30// time.Unix function when passed the contents of the Timestamp, in the UTC
31// locale. This may or may not be a meaningful time; many invalid Timestamps
32// do map to valid time.Times.
33//
34// A nil Timestamp returns an error. The first return value in that case is
35// undefined.
36func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
37 // Don't return the zero value on error, because corresponds to a valid
38 // timestamp. Instead return whatever time.Unix gives us.
39 var t time.Time
40 if ts == nil {
41 t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
42 } else {
43 t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
44 }
45 return t, validateTimestamp(ts)
46}
47
48// TimestampNow returns a google.protobuf.Timestamp for the current time.
49func TimestampNow() *timestamppb.Timestamp {
50 ts, err := TimestampProto(time.Now())
51 if err != nil {
52 panic("ptypes: time.Now() out of Timestamp range")
53 }
54 return ts
55}
56
57// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
58// It returns an error if the resulting Timestamp is invalid.
59func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
60 ts := &timestamppb.Timestamp{
61 Seconds: t.Unix(),
62 Nanos: int32(t.Nanosecond()),
63 }
64 if err := validateTimestamp(ts); err != nil {
65 return nil, err
66 }
67 return ts, nil
68}
69
70// TimestampString returns the RFC 3339 string for valid Timestamps.
71// For invalid Timestamps, it returns an error message in parentheses.
72func TimestampString(ts *timestamppb.Timestamp) string {
73 t, err := Timestamp(ts)
74 if err != nil {
75 return fmt.Sprintf("(%v)", err)
76 }
77 return t.Format(time.RFC3339Nano)
78}
79
khenaidooac637102019-01-14 15:44:34 -050080// validateTimestamp determines whether a Timestamp is valid.
Andrea Campanella3614a922021-02-25 12:40:42 +010081// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
82// and has a Nanos field in the range [0, 1e9).
khenaidooac637102019-01-14 15:44:34 -050083//
84// If the Timestamp is valid, validateTimestamp returns nil.
Andrea Campanella3614a922021-02-25 12:40:42 +010085// Otherwise, it returns an error that describes the problem.
khenaidooac637102019-01-14 15:44:34 -050086//
Andrea Campanella3614a922021-02-25 12:40:42 +010087// Every valid Timestamp can be represented by a time.Time,
88// but the converse is not true.
89func validateTimestamp(ts *timestamppb.Timestamp) error {
khenaidooac637102019-01-14 15:44:34 -050090 if ts == nil {
91 return errors.New("timestamp: nil Timestamp")
92 }
93 if ts.Seconds < minValidSeconds {
94 return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
95 }
96 if ts.Seconds >= maxValidSeconds {
97 return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
98 }
99 if ts.Nanos < 0 || ts.Nanos >= 1e9 {
100 return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
101 }
102 return nil
103}