1package time
2
3import (
4 "context"
5 "fmt"
6 "math/big"
7 "strings"
8 "time"
9)
10
11const (
12 // dateTimeFormat is a IMF-fixdate formatted RFC3339 section 5.6
13 dateTimeFormatInput = "2006-01-02T15:04:05.999999999Z"
14 dateTimeFormatInputNoZ = "2006-01-02T15:04:05.999999999"
15 dateTimeFormatOutput = "2006-01-02T15:04:05.999Z"
16
17 // httpDateFormat is a date time defined by RFC 7231#section-7.1.1.1
18 // IMF-fixdate with no UTC offset.
19 httpDateFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
20 // Additional formats needed for compatibility.
21 httpDateFormatSingleDigitDay = "Mon, _2 Jan 2006 15:04:05 GMT"
22 httpDateFormatSingleDigitDayTwoDigitYear = "Mon, _2 Jan 06 15:04:05 GMT"
23)
24
25var millisecondFloat = big.NewFloat(1e3)
26
27// FormatDateTime formats value as a date-time, (RFC3339 section 5.6)
28//
29// Example: 1985-04-12T23:20:50.52Z
30func FormatDateTime(value time.Time) string {
31 return value.UTC().Format(dateTimeFormatOutput)
32}
33
34// ParseDateTime parses a string as a date-time, (RFC3339 section 5.6)
35//
36// Example: 1985-04-12T23:20:50.52Z
37func ParseDateTime(value string) (time.Time, error) {
38 return tryParse(value,
39 dateTimeFormatInput,
40 dateTimeFormatInputNoZ,
41 time.RFC3339Nano,
42 time.RFC3339,
43 )
44}
45
46// FormatHTTPDate formats value as a http-date, (RFC 7231#section-7.1.1.1 IMF-fixdate)
47//
48// Example: Tue, 29 Apr 2014 18:30:38 GMT
49func FormatHTTPDate(value time.Time) string {
50 return value.UTC().Format(httpDateFormat)
51}
52
53// ParseHTTPDate parses a string as a http-date, (RFC 7231#section-7.1.1.1 IMF-fixdate)
54//
55// Example: Tue, 29 Apr 2014 18:30:38 GMT
56func ParseHTTPDate(value string) (time.Time, error) {
57 return tryParse(value,
58 httpDateFormat,
59 httpDateFormatSingleDigitDay,
60 httpDateFormatSingleDigitDayTwoDigitYear,
61 time.RFC850,
62 time.ANSIC,
63 )
64}
65
66// FormatEpochSeconds returns value as a Unix time in seconds with with decimal precision
67//
68// Example: 1515531081.123
69func FormatEpochSeconds(value time.Time) float64 {
70 ms := value.UnixNano() / int64(time.Millisecond)
71 return float64(ms) / 1e3
72}
73
74// ParseEpochSeconds returns value as a Unix time in seconds with with decimal precision
75//
76// Example: 1515531081.123
77func ParseEpochSeconds(value float64) time.Time {
78 f := big.NewFloat(value)
79 f = f.Mul(f, millisecondFloat)
80 i, _ := f.Int64()
81 // Offset to `UTC` because time.Unix returns the time value based on system
82 // local setting.
83 return time.Unix(0, i*1e6).UTC()
84}
85
86func tryParse(v string, formats ...string) (time.Time, error) {
87 var errs parseErrors
88 for _, f := range formats {
89 t, err := time.Parse(f, v)
90 if err != nil {
91 errs = append(errs, parseError{
92 Format: f,
93 Err: err,
94 })
95 continue
96 }
97 return t, nil
98 }
99
100 return time.Time{}, fmt.Errorf("unable to parse time string, %w", errs)
101}
102
103type parseErrors []parseError
104
105func (es parseErrors) Error() string {
106 var s strings.Builder
107 for _, e := range es {
108 fmt.Fprintf(&s, "\n * %q: %v", e.Format, e.Err)
109 }
110
111 return "parse errors:" + s.String()
112}
113
114type parseError struct {
115 Format string
116 Err error
117}
118
119// SleepWithContext will wait for the timer duration to expire, or until the context
120// is canceled. Whichever happens first. If the context is canceled the
121// Context's error will be returned.
122func SleepWithContext(ctx context.Context, dur time.Duration) error {
123 t := time.NewTimer(dur)
124 defer t.Stop()
125
126 select {
127 case <-t.C:
128 break
129 case <-ctx.Done():
130 return ctx.Err()
131 }
132
133 return nil
134}