bbgo/pkg/types/time.go

369 lines
7.6 KiB
Go
Raw Normal View History

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, &timestamp); 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
}