time.go

  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}