From 46f30807378cd01b215daf56a7ca4169612e025a Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 12 Sep 2024 13:21:31 +1000 Subject: [PATCH] gateio: fix and optimise time parsing (#1647) Co-authored-by: Ryan O'Hara-Reid --- exchanges/gateio/gateio_convert.go | 78 ++++++++++++++++++------------ exchanges/gateio/gateio_test.go | 54 +++++++++++++++++++++ 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/exchanges/gateio/gateio_convert.go b/exchanges/gateio/gateio_convert.go index 4c9aea3d..911a80db 100644 --- a/exchanges/gateio/gateio_convert.go +++ b/exchanges/gateio/gateio_convert.go @@ -1,51 +1,69 @@ package gateio import ( - "encoding/json" + "bytes" "fmt" "math" "strconv" + "strings" "time" ) +var ( + zero = []byte(`0`) + emptyStr = []byte(`""`) + zeroStr = []byte(`"0"`) +) + // Time represents a time.Time object that can be unmarshalled from a float64 or string. type Time time.Time // UnmarshalJSON deserializes json, and timestamp information. func (a *Time) UnmarshalJSON(data []byte) error { - var value interface{} - err := json.Unmarshal(data, &value) + if bytes.Equal(data, zero) || bytes.Equal(data, emptyStr) || bytes.Equal(data, zeroStr) { + *a = Time(time.Time{}) + return nil + } + + s := string(data) + if s[0] == '"' { + s = s[1 : len(s)-1] + } + + target := strings.Index(s, ".") + if target != -1 { + s = s[:target] + s[target+1:] + } + + standard, err := strconv.ParseInt(s, 10, 64) if err != nil { return err } - var standard int64 - switch val := value.(type) { - case float64: - if math.Trunc(val) != val { - standard = int64(val * 1e3) // Account for 1684981731.098 - } else { - standard = int64(val) - } - case string: - if val == "" { - return nil - } - parsedValue, err := strconv.ParseFloat(val, 64) - if err != nil { - return err - } - if math.Trunc(parsedValue) != parsedValue { - *a = Time(time.UnixMicro(int64(parsedValue * 1e3))) // Account for "1691122380942.173000" microseconds - return nil - } - standard = int64(parsedValue) - default: - return fmt.Errorf("cannot unmarshal %T into Time", val) - } - if standard > 9999999999 { - *a = Time(time.UnixMilli(standard)) - } else { + + switch len(s) { + case 10: + // Seconds *a = Time(time.Unix(standard, 0)) + case 11, 12: + // Milliseconds: 1726104395.5 && 1726104395.56 + *a = Time(time.UnixMilli(standard * int64(math.Pow10(13-len(s))))) + case 13: + // Milliseconds + *a = Time(time.UnixMilli(standard)) + case 14: + // MicroSeconds: 1726106210903.0 + *a = Time(time.UnixMicro(standard * 100)) + case 16: + // MicroSeconds + *a = Time(time.UnixMicro(standard)) + case 17: + // NanoSeconds: 1606292218213.4578 + *a = Time(time.Unix(0, standard*100)) + case 19: + // NanoSeconds + *a = Time(time.Unix(0, standard)) + default: + return fmt.Errorf("cannot unmarshal %s into Time", string(data)) } return nil } diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index a69da1a0..088527c6 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3611,3 +3611,57 @@ func TestGenerateWebsocketMessageID(t *testing.T) { t.Parallel() require.NotEmpty(t, g.GenerateWebsocketMessageID(false)) } + +func TestTime(t *testing.T) { + t.Parallel() + var testTime Time + + require.NoError(t, json.Unmarshal([]byte(`0`), &testTime)) + assert.Equal(t, time.Time{}, testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`""`), &testTime)) + assert.Equal(t, time.Time{}, testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`"0"`), &testTime)) + assert.Equal(t, time.Time{}, testTime.Time()) + + // seconds + require.NoError(t, json.Unmarshal([]byte(`"1628736847"`), &testTime)) + assert.Equal(t, time.Unix(1628736847, 0), testTime.Time()) + + // milliseconds + require.NoError(t, json.Unmarshal([]byte(`"1726104395.5"`), &testTime)) + assert.Equal(t, time.UnixMilli(1726104395500), testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`"1726104395.56"`), &testTime)) + assert.Equal(t, time.UnixMilli(1726104395560), testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`"1628736847325"`), &testTime)) + assert.Equal(t, time.UnixMilli(1628736847325), testTime.Time()) + + // microseconds + require.NoError(t, json.Unmarshal([]byte(`"1628736847325123"`), &testTime)) + assert.Equal(t, time.UnixMicro(1628736847325123), testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`"1726106210903.0"`), &testTime)) + assert.Equal(t, time.UnixMicro(1726106210903000), testTime.Time()) + + // nanoseconds + require.NoError(t, json.Unmarshal([]byte(`"1606292218213.4578"`), &testTime)) + assert.Equal(t, time.Unix(0, 1606292218213457800), testTime.Time()) + + require.NoError(t, json.Unmarshal([]byte(`"1606292218213457800"`), &testTime)) + assert.Equal(t, time.Unix(0, 1606292218213457800), testTime.Time()) +} + +// 5046307 216.0 ns/op 168 B/op 2 allocs/op (current) +// 2716176 441.9 ns/op 352 B/op 6 allocs/op (previous) +func BenchmarkTime(b *testing.B) { + var testTime Time + for i := 0; i < b.N; i++ { + err := json.Unmarshal([]byte(`"1691122380942.173000"`), &testTime) + if err != nil { + b.Fatal(err) + } + } +}