code.dwrz.net

Go monorepo.
Log | Files | Refs

time.go (3408B)


      1 package time
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 	"math/big"
      7 	"strings"
      8 	"time"
      9 )
     10 
     11 const (
     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 
     25 var 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
     30 func 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
     37 func 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
     49 func 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
     56 func 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
     69 func 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
     77 func 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 
     86 func 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 
    103 type parseErrors []parseError
    104 
    105 func (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 
    114 type 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.
    122 func 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 }