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.
36//
37// Deprecated: Call the ts.AsTime and ts.CheckValid methods instead.
38func Timestamp(ts *timestamppb.Timestamp) (time.Time, error) {
39	// Don't return the zero value on error, because corresponds to a valid
40	// timestamp. Instead return whatever time.Unix gives us.
41	var t time.Time
42	if ts == nil {
43		t = time.Unix(0, 0).UTC() // treat nil like the empty Timestamp
44	} else {
45		t = time.Unix(ts.Seconds, int64(ts.Nanos)).UTC()
46	}
47	return t, validateTimestamp(ts)
48}
49
50// TimestampNow returns a google.protobuf.Timestamp for the current time.
51//
52// Deprecated: Call the timestamppb.Now function instead.
53func TimestampNow() *timestamppb.Timestamp {
54	ts, err := TimestampProto(time.Now())
55	if err != nil {
56		panic("ptypes: time.Now() out of Timestamp range")
57	}
58	return ts
59}
60
61// TimestampProto converts the time.Time to a google.protobuf.Timestamp proto.
62// It returns an error if the resulting Timestamp is invalid.
63//
64// Deprecated: Call the timestamppb.New function instead.
65func TimestampProto(t time.Time) (*timestamppb.Timestamp, error) {
66	ts := &timestamppb.Timestamp{
67		Seconds: t.Unix(),
68		Nanos:   int32(t.Nanosecond()),
69	}
70	if err := validateTimestamp(ts); err != nil {
71		return nil, err
72	}
73	return ts, nil
74}
75
76// TimestampString returns the RFC 3339 string for valid Timestamps.
77// For invalid Timestamps, it returns an error message in parentheses.
78//
79// Deprecated: Call the ts.AsTime method instead,
80// followed by a call to the Format method on the time.Time value.
81func TimestampString(ts *timestamppb.Timestamp) string {
82	t, err := Timestamp(ts)
83	if err != nil {
84		return fmt.Sprintf("(%v)", err)
85	}
86	return t.Format(time.RFC3339Nano)
87}
88
89// validateTimestamp determines whether a Timestamp is valid.
90// A valid timestamp represents a time in the range [0001-01-01, 10000-01-01)
91// and has a Nanos field in the range [0, 1e9).
92//
93// If the Timestamp is valid, validateTimestamp returns nil.
94// Otherwise, it returns an error that describes the problem.
95//
96// Every valid Timestamp can be represented by a time.Time,
97// but the converse is not true.
98func validateTimestamp(ts *timestamppb.Timestamp) error {
99	if ts == nil {
100		return errors.New("timestamp: nil Timestamp")
101	}
102	if ts.Seconds < minValidSeconds {
103		return fmt.Errorf("timestamp: %v before 0001-01-01", ts)
104	}
105	if ts.Seconds >= maxValidSeconds {
106		return fmt.Errorf("timestamp: %v after 10000-01-01", ts)
107	}
108	if ts.Nanos < 0 || ts.Nanos >= 1e9 {
109		return fmt.Errorf("timestamp: %v: nanos not in range [0, 1e9)", ts)
110	}
111	return nil
112}
113