gateio: fix and optimise time parsing (#1647)

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2024-09-12 13:21:31 +10:00
committed by GitHub
parent a82f0bff59
commit 46f3080737
2 changed files with 102 additions and 30 deletions

View File

@@ -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
}

View File

@@ -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)
}
}
}