1// 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.
4
5package ptypes
6
7import (
8	"errors"
9	"fmt"
10	"time"
11
12	timestamppb "github.com/golang/protobuf/ptypes/timestamp"
13)
14
15// Range of google.protobuf.Duration as specified in timestamp.proto.
16const (
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
25// 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
80// validateTimestamp determines whether a Timestamp is valid.
81// 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).
83//
84// If the Timestamp is valid, validateTimestamp returns nil.
85// Otherwise, it returns an error that describes the problem.
86//
87// Every valid Timestamp can be represented by a time.Time,
88// but the converse is not true.
89func validateTimestamp(ts *timestamppb.Timestamp) error {
90	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}
104