package types import ( "database/sql/driver" "encoding/json" "fmt" "strconv" "strings" "time" ) var numOfDigitsOfUnixTimestamp = len(strconv.FormatInt(time.Now().Unix(), 10)) var numOfDigitsOfMilliSecondUnixTimestamp = len(strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) var numOfDigitsOfNanoSecondsUnixTimestamp = len(strconv.FormatInt(time.Now().UnixNano(), 10)) type NanosecondTimestamp time.Time func (t NanosecondTimestamp) Time() time.Time { return time.Time(t) } func (t *NanosecondTimestamp) UnmarshalJSON(data []byte) error { var v int64 var err = json.Unmarshal(data, &v) if err != nil { return err } *t = NanosecondTimestamp(time.Unix(0, v)) return nil } type MillisecondTimestamp time.Time func NewMillisecondTimestampFromInt(i int64) MillisecondTimestamp { return MillisecondTimestamp(time.Unix(0, i*int64(time.Millisecond))) } func MustParseMillisecondTimestamp(a string) MillisecondTimestamp { m, err := strconv.ParseInt(a, 10, 64) // startTime if err != nil { panic(fmt.Errorf("millisecond timestamp parse error %v", err)) } return NewMillisecondTimestampFromInt(m) } func MustParseUnixTimestamp(a string) time.Time { m, err := strconv.ParseInt(a, 10, 64) // startTime if err != nil { panic(fmt.Errorf("millisecond timestamp parse error %v", err)) } return time.Unix(m, 0) } func (t MillisecondTimestamp) String() string { return time.Time(t).String() } func (t MillisecondTimestamp) Time() time.Time { return time.Time(t) } func (t *MillisecondTimestamp) UnmarshalJSON(data []byte) error { var v interface{} var err = json.Unmarshal(data, &v) if err != nil { return err } switch vt := v.(type) { case string: if vt == "" { // treat empty string as 0 *t = MillisecondTimestamp(time.Time{}) return nil } f, err := strconv.ParseFloat(vt, 64) if err == nil { tt, err := convertFloat64ToTime(vt, f) if err != nil { return err } *t = MillisecondTimestamp(tt) return nil } tt, err := time.Parse(time.RFC3339Nano, vt) if err == nil { *t = MillisecondTimestamp(tt) return nil } return err case float64: str := strconv.FormatFloat(vt, 'f', -1, 64) tt, err := convertFloat64ToTime(str, vt) if err != nil { return err } *t = MillisecondTimestamp(tt) return nil default: return fmt.Errorf("can not parse %T %+v as millisecond timestamp", vt, vt) } // Unreachable } func convertFloat64ToTime(vt string, f float64) (time.Time, error) { idx := strings.Index(vt, ".") if idx > 0 { vt = vt[0 : idx-1] } if len(vt) <= numOfDigitsOfUnixTimestamp { return time.Unix(0, int64(f*float64(time.Second))), nil } else if len(vt) <= numOfDigitsOfMilliSecondUnixTimestamp { return time.Unix(0, int64(f)*int64(time.Millisecond)), nil } else if len(vt) <= numOfDigitsOfNanoSecondsUnixTimestamp { return time.Unix(0, int64(f)), nil } return time.Time{}, fmt.Errorf("the floating point value %f is out of the timestamp range", f) } // Time type implements the driver value for sqlite type Time time.Time // layout defines the time layout without timezone // because sqlite3 and mysql does not output datetime with timezone. // // sqlite3 format (in UTC): 2022-06-01 11:38:45 // mysql format in (in Local): 2022-06-02 20:28:10 or 2022-06-02 20:28:10.000 var layout = "2006-01-02 15:04:05.999Z07:00" func (t *Time) UnmarshalJSON(data []byte) error { // fallback to RFC3339 return (*time.Time)(t).UnmarshalJSON(data) } func (t Time) MarshalJSON() ([]byte, error) { return time.Time(t).MarshalJSON() } func (t Time) String() string { return time.Time(t).String() } func (t Time) Time() time.Time { return time.Time(t) } func (t Time) Unix() int64 { return time.Time(t).Unix() } func (t Time) UnixMilli() int64 { return time.Time(t).UnixMilli() } func (t Time) Equal(time2 time.Time) bool { return time.Time(t).Equal(time2) } func (t Time) After(time2 time.Time) bool { return time.Time(t).After(time2) } func (t Time) Before(time2 time.Time) bool { return time.Time(t).Before(time2) } func NewTimeFromUnix(sec int64, nsec int64) Time { return Time(time.Unix(sec, nsec)) } // Value implements the driver.Valuer interface // see http://jmoiron.net/blog/built-in-interfaces/ func (t Time) Value() (driver.Value, error) { if time.Time(t) == (time.Time{}) { return nil, nil } return time.Time(t), nil } func (t *Time) Scan(src interface{}) error { // skip nil time if src == nil { return nil } switch d := src.(type) { case *time.Time: *t = Time(*d) return nil case time.Time: *t = Time(d) return nil case string: // 2020-12-16 05:17:12.994+08:00 tt, err := time.Parse(layout, d) if err != nil { return err } *t = Time(tt) return nil case []byte: // 2019-10-20 23:01:43.77+08:00 tt, err := time.Parse(layout, string(d)) if err != nil { return err } *t = Time(tt) return nil default: } return fmt.Errorf("datatype.Time scan error, type: %T is not supported, value; %+v", src, src) } var looseTimeFormats = []string{ time.RFC3339, time.RFC822, "2006-01-02T15:04:05", "2006-01-02", } // LooseFormatTime parses date time string with a wide range of formats. type LooseFormatTime time.Time func ParseLooseFormatTime(s string) (LooseFormatTime, error) { var t time.Time switch s { case "now": t = time.Now() return LooseFormatTime(t), nil case "yesterday": t = time.Now().AddDate(0, 0, -1) return LooseFormatTime(t), nil case "last month": t = time.Now().AddDate(0, -1, 0) return LooseFormatTime(t), nil case "last 30 days": t = time.Now().AddDate(0, 0, -30) return LooseFormatTime(t), nil case "last year": t = time.Now().AddDate(-1, 0, 0) return LooseFormatTime(t), nil } tv, err := ParseTimeWithFormats(s, looseTimeFormats) if err != nil { return LooseFormatTime{}, err } return LooseFormatTime(tv), nil } func (t *LooseFormatTime) UnmarshalYAML(unmarshal func(interface{}) error) error { var str string if err := unmarshal(&str); err != nil { return err } lt, err := ParseLooseFormatTime(str) if err != nil { return err } *t = lt return nil } func (t *LooseFormatTime) UnmarshalJSON(data []byte) error { var v string err := json.Unmarshal(data, &v) if err != nil { return err } tv, err := ParseTimeWithFormats(v, looseTimeFormats) if err != nil { return err } *t = LooseFormatTime(tv) return nil } func (t LooseFormatTime) MarshalJSON() ([]byte, error) { return []byte(strconv.Quote(time.Time(t).Format(time.RFC3339))), nil } func (t LooseFormatTime) Time() time.Time { return time.Time(t) } // Timestamp is used for parsing unix timestamp (seconds) type Timestamp time.Time func (t Timestamp) Format(layout string) string { return time.Time(t).Format(layout) } func (t Timestamp) Time() time.Time { return time.Time(t) } func (t Timestamp) String() string { return time.Time(t).String() } func (t Timestamp) MarshalJSON() ([]byte, error) { ts := time.Time(t).Unix() return json.Marshal(ts) } func (t *Timestamp) UnmarshalJSON(o []byte) error { var timestamp int64 if err := json.Unmarshal(o, ×tamp); err != nil { return err } *t = Timestamp(time.Unix(timestamp, 0)) return nil } func ParseTimeWithFormats(strTime string, formats []string) (time.Time, error) { for _, format := range formats { tt, err := time.Parse(format, strTime) if err == nil { return tt, nil } } return time.Time{}, fmt.Errorf("failed to parse time %s, valid formats are %+v", strTime, formats) } func BeginningOfTheDay(t time.Time) time.Time { year, month, day := t.Date() return time.Date(year, month, day, 0, 0, 0, 0, t.Location()) } func Over24Hours(since time.Time) bool { return time.Since(since) >= 24*time.Hour }