1package protocol
2
3import (
4	"bytes"
5	"fmt"
6	"math"
7	"strconv"
8	"time"
9
10	"github.com/aws/aws-sdk-go/internal/sdkmath"
11)
12
13// Names of time formats supported by the SDK
14const (
15	RFC822TimeFormatName  = "rfc822"
16	ISO8601TimeFormatName = "iso8601"
17	UnixTimeFormatName    = "unixTimestamp"
18)
19
20// Time formats supported by the SDK
21// Output time is intended to not contain decimals
22const (
23	// RFC 7231#section-7.1.1.1 timetamp format. e.g Tue, 29 Apr 2014 18:30:38 GMT
24	RFC822TimeFormat                           = "Mon, 2 Jan 2006 15:04:05 GMT"
25	rfc822TimeFormatSingleDigitDay             = "Mon, _2 Jan 2006 15:04:05 GMT"
26	rfc822TimeFormatSingleDigitDayTwoDigitYear = "Mon, _2 Jan 06 15:04:05 GMT"
27
28	// This format is used for output time without seconds precision
29	RFC822OutputTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
30
31	// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
32	ISO8601TimeFormat = "2006-01-02T15:04:05.999999999Z"
33
34	// This format is used for output time with fractional second precision up to milliseconds
35	ISO8601OutputTimeFormat = "2006-01-02T15:04:05.999999999Z"
36)
37
38// IsKnownTimestampFormat returns if the timestamp format name
39// is know to the SDK's protocols.
40func IsKnownTimestampFormat(name string) bool {
41	switch name {
42	case RFC822TimeFormatName:
43		fallthrough
44	case ISO8601TimeFormatName:
45		fallthrough
46	case UnixTimeFormatName:
47		return true
48	default:
49		return false
50	}
51}
52
53// FormatTime returns a string value of the time.
54func FormatTime(name string, t time.Time) string {
55	t = t.UTC().Truncate(time.Millisecond)
56
57	switch name {
58	case RFC822TimeFormatName:
59		return t.Format(RFC822OutputTimeFormat)
60	case ISO8601TimeFormatName:
61		return t.Format(ISO8601OutputTimeFormat)
62	case UnixTimeFormatName:
63		ms := t.UnixNano() / int64(time.Millisecond)
64		return strconv.FormatFloat(float64(ms)/1e3, 'f', -1, 64)
65	default:
66		panic("unknown timestamp format name, " + name)
67	}
68}
69
70// ParseTime attempts to parse the time given the format. Returns
71// the time if it was able to be parsed, and fails otherwise.
72func ParseTime(formatName, value string) (time.Time, error) {
73	switch formatName {
74	case RFC822TimeFormatName: // Smithy HTTPDate format
75		return tryParse(value,
76			RFC822TimeFormat,
77			rfc822TimeFormatSingleDigitDay,
78			rfc822TimeFormatSingleDigitDayTwoDigitYear,
79			time.RFC850,
80			time.ANSIC,
81		)
82	case ISO8601TimeFormatName: // Smithy DateTime format
83		return tryParse(value,
84			ISO8601TimeFormat,
85			time.RFC3339Nano,
86			time.RFC3339,
87		)
88	case UnixTimeFormatName:
89		v, err := strconv.ParseFloat(value, 64)
90		_, dec := math.Modf(v)
91		dec = sdkmath.Round(dec*1e3) / 1e3 //Rounds 0.1229999 to 0.123
92		if err != nil {
93			return time.Time{}, err
94		}
95		return time.Unix(int64(v), int64(dec*(1e9))), nil
96	default:
97		panic("unknown timestamp format name, " + formatName)
98	}
99}
100
101func tryParse(v string, formats ...string) (time.Time, error) {
102	var errs parseErrors
103	for _, f := range formats {
104		t, err := time.Parse(f, v)
105		if err != nil {
106			errs = append(errs, parseError{
107				Format: f,
108				Err:    err,
109			})
110			continue
111		}
112		return t, nil
113	}
114
115	return time.Time{}, fmt.Errorf("unable to parse time string, %v", errs)
116}
117
118type parseErrors []parseError
119
120func (es parseErrors) Error() string {
121	var s bytes.Buffer
122	for _, e := range es {
123		fmt.Fprintf(&s, "\n * %q: %v", e.Format, e.Err)
124	}
125
126	return "parse errors:" + s.String()
127}
128
129type parseError struct {
130	Format string
131	Err    error
132}
133