From b09b389a58634685857831d39024ec9581872618 Mon Sep 17 00:00:00 2001 From: shockerli Date: Fri, 9 Sep 2022 22:41:08 +0800 Subject: [PATCH] Improve Time* functions * `Time*()` improve performance for direct type * `Time*()` support `time.Duration` * `Time*()` support `json.Number` * `Time*()` support indirect of `time.Time` * Make function `Indirect()` exported * `Indirect()` support indirect type `time.Time` --- bool.go | 2 +- cvte.go | 15 ++++++++---- cvte_test.go | 42 ++++++++++++++++++++++++++++++++-- doc/content/zh-cn/type/time.md | 10 ++++++++ float.go | 28 +++++------------------ int.go | 18 ++++++++------- map.go | 4 ++-- slice.go | 6 ++--- string.go | 2 +- time.go | 32 ++++++++++++++++++++++---- time_test.go | 11 +++++++++ 11 files changed, 123 insertions(+), 47 deletions(-) diff --git a/bool.go b/bool.go index f5221df..6fa1015 100644 --- a/bool.go +++ b/bool.go @@ -57,7 +57,7 @@ func BoolE(val interface{}) (bool, error) { } // indirect type - v, rv := indirect(val) + v, rv := Indirect(val) switch vv := v.(type) { case nil: diff --git a/cvte.go b/cvte.go index 996dd1c..53387f8 100644 --- a/cvte.go +++ b/cvte.go @@ -6,6 +6,7 @@ import ( "reflect" "sort" "strings" + "time" ) var errConvFail = errors.New("convert failed") @@ -35,7 +36,7 @@ func FieldE(val interface{}, field interface{}) (interface{}, error) { } sf := String(field) // match with the String of field, so field can be any type - _, rv := indirect(val) + _, rv := Indirect(val) switch rv.Kind() { case reflect.Map: // key of map @@ -131,8 +132,8 @@ func ptrValue(rv reflect.Value) reflect.Value { return rv } -// returns the value with base type -func indirect(a interface{}) (val interface{}, rv reflect.Value) { +// Indirect returns the value with base type +func Indirect(a interface{}) (val interface{}, rv reflect.Value) { if a == nil { return } @@ -150,7 +151,7 @@ func indirect(a interface{}) (val interface{}, rv reflect.Value) { } rv = rv.Elem() } - return indirect(rv.Interface()) + return Indirect(rv.Interface()) case reflect.Bool: val = rv.Bool() case reflect.Int: @@ -184,6 +185,12 @@ func indirect(a interface{}) (val interface{}, rv reflect.Value) { if rv.Type().Elem().Kind() == reflect.Uint8 { val = rv.Bytes() } + default: + // time.Time + if ct := reflect.TypeOf(time.Time{}); rv.CanConvert(ct) { + cv := rv.Convert(ct) + return cv.Interface(), cv + } } return diff --git a/cvte_test.go b/cvte_test.go index 2a0a705..1025bf5 100644 --- a/cvte_test.go +++ b/cvte_test.go @@ -33,14 +33,16 @@ type ( AliasTypeString string AliasTypeBytes []byte AliasTypeInterface interface{} + AliasTypeTime time.Time ) var ( aliasTypeBool4True AliasTypeBool = true aliasTypeBool4False AliasTypeBool = false - aliasTypeInt0 AliasTypeInt = 0 - aliasTypeInt1 AliasTypeInt = 1 + aliasTypeInt0 AliasTypeInt = 0 + aliasTypeInt1 AliasTypeInt = 1 + aliasTypeIntTime1 AliasTypeInt = 1234567890 aliasTypeUint0 AliasTypeUint = 0 aliasTypeUint1 AliasTypeUint = 1 @@ -57,6 +59,7 @@ var ( aliasTypeStringOff AliasTypeString = "off" aliasTypeStringLosePrecisionInt64 AliasTypeString = "7138826985640367621" aliasTypeStringLosePrecisionFloat64 AliasTypeString = "7138826985640367621.18" + aliasTypeStringTime1 AliasTypeString = "2016-03-06 15:28:01" pointerRunes = []rune("中国") pointerInterNil *AliasTypeInterface @@ -64,6 +67,8 @@ var ( aliasTypeBytesNil AliasTypeBytes aliasTypeBytesTrue AliasTypeBytes = []byte("true") aliasTypeBytes8d15 AliasTypeBytes = []byte("8.15") + + aliasTypeTime1 = AliasTypeTime(time.Date(2009, 2, 13, 23, 31, 30, 0, time.UTC)) ) // custom type @@ -234,6 +239,39 @@ func TestFieldE(t *testing.T) { } } +func TestIndirect(t *testing.T) { + tests := []struct { + input interface{} + expect interface{} + }{ + {nil, nil}, + {12, 12}, + {12.01, 12.01}, + {"hello", "hello"}, + {aliasTypeInt1, 1}, + {&aliasTypeInt1, 1}, + {aliasTypeStringOff, "off"}, + {&aliasTypeStringOff, "off"}, + {pointerIntNil, nil}, + {&pointerIntNil, nil}, + {pointerRunes, []rune("中国")}, + {&pointerRunes, []rune("中国")}, + {aliasTypeTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, time.UTC)}, + {&aliasTypeTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, time.UTC)}, + } + + for i, tt := range tests { + msg := fmt.Sprintf( + "i = %d, input[%+v], expect[%+v]", + i, tt.input, tt.expect, + ) + + val, _ := cvt.Indirect(tt.input) + + assertEqual(t, tt.expect, val, "[WithE] "+msg) + } +} + /* ------------------------------------------------------------------------------ */ // [testing assert functions] diff --git a/doc/content/zh-cn/type/time.md b/doc/content/zh-cn/type/time.md index 31a5e70..aec8f34 100644 --- a/doc/content/zh-cn/type/time.md +++ b/doc/content/zh-cn/type/time.md @@ -19,9 +19,19 @@ cvt.Time("10 Nov 09 23:00 UTC") cvt.Time("2009-11-10T23:00:00Z") cvt.Time("11:00PM") cvt.Time("2006-01-02") +cvt.Time("2006.01.02") +cvt.Time("2006/01/02") cvt.Time("2016-03-06 15:28:01") +cvt.Time("2016-03-06 15:28:01.332") +cvt.Time("2016-03-06 15:28:01.332901") +cvt.Time("2016-03-06 15:28:01.332901345") +cvt.Time("2006年01月02日 15时04分05秒") +cvt.Time("Mon Jan 2 15:04:05 2006 -0700") cvt.Time(1482597504) +cvt.Time(time.Duration(1482597504)) cvt.Time(time.Date(2009, 2, 13, 23, 31, 30, 0, time.Local)) +cvt.Time(json.Number("1234567890")) +cvt.Time(json.Number("2016-03-06 15:28:01")) ``` > 更多示例请看单元测试:`time_test.go` diff --git a/float.go b/float.go index 8545587..ad7082c 100644 --- a/float.go +++ b/float.go @@ -65,24 +65,17 @@ func convFloat64E(val interface{}) (float64, error) { case float32: // use fmt to fix float32 -> float64 precision loss // eg: cvt.Float64E(float32(8.31)) - vvv, err := strconv.ParseFloat(fmt.Sprintf("%f", vv), 64) - if err == nil { - return vvv, nil - } + return strconv.ParseFloat(fmt.Sprintf("%f", vv), 64) case float64: return vv, nil case json.Number: - vvv, err := vv.Float64() - if err == nil { - return vvv, nil - } - return 0, errConvFail + return vv.Float64() case time.Duration: return float64(vv), nil } // indirect type - v, rv := indirect(val) + v, rv := Indirect(val) switch vv := v.(type) { case nil: @@ -93,15 +86,9 @@ func convFloat64E(val interface{}) (float64, error) { } return 0, nil case string: - vvv, err := strconv.ParseFloat(vv, 64) - if err == nil { - return vvv, nil - } + return strconv.ParseFloat(vv, 64) case []byte: - vvv, err := strconv.ParseFloat(string(vv), 64) - if err == nil { - return vvv, nil - } + return strconv.ParseFloat(string(vv), 64) case uint, uint8, uint16, uint32, uint64, uintptr: return float64(rv.Uint()), nil case int, int8, int16, int32, int64: @@ -109,10 +96,7 @@ func convFloat64E(val interface{}) (float64, error) { case float32: // use fmt to fix float32 -> float64 precision loss // eg: cvt.Float64E(float32(8.31)) - vvv, err := strconv.ParseFloat(fmt.Sprintf("%f", vv), 64) - if err == nil { - return vvv, nil - } + return strconv.ParseFloat(fmt.Sprintf("%f", vv), 64) case float64: return vv, nil } diff --git a/int.go b/int.go index e121c34..6880f67 100644 --- a/int.go +++ b/int.go @@ -385,7 +385,7 @@ func convUint64(val interface{}) (uint64, error) { } // indirect type - v, rv := indirect(val) + v, rv := Indirect(val) switch vv := v.(type) { case nil: return 0, nil @@ -464,7 +464,7 @@ func convInt64(val interface{}) (int64, error) { } // indirect type - v, rv := indirect(val) + v, rv := Indirect(val) switch vv := v.(type) { case nil: return 0, nil @@ -493,10 +493,11 @@ func convInt64(val interface{}) (int64, error) { } // convert an int or float string to int64 -// "12" => 12 -// "12.01" => 12 -// "-12" => -12 -// "-12.01" => -12 +// +// "12" => 12 +// "12.01" => 12 +// "-12" => -12 +// "-12.01" => -12 func str2int64(s string) (i int64, err error) { // ensure can be converted to float _, err = strconv.ParseFloat(s, 64) @@ -513,8 +514,9 @@ func str2int64(s string) (i int64, err error) { } // convert an int or float string to uint64 -// "12" => 12 -// "12.01" => 12 +// +// "12" => 12 +// "12.01" => 12 func str2uint64(s string) (i uint64, err error) { // ensure can be converted to float _, err = strconv.ParseFloat(s, 64) diff --git a/map.go b/map.go index 2e647b2..b660eb8 100644 --- a/map.go +++ b/map.go @@ -28,7 +28,7 @@ func StringMapE(val interface{}) (m map[string]interface{}, err error) { } // indirect type - _, rv := indirect(val) + _, rv := Indirect(val) switch rv.Kind() { case reflect.Map: for _, key := range rv.MapKeys() { @@ -61,7 +61,7 @@ func struct2map(rv reflect.Value) map[string]interface{} { vv := ptrValue(rv.Field(j)) if f.Anonymous && t.Kind() == reflect.Struct { for k, v := range struct2map(vv) { - // anonymous sub-field has a low priority + // anonymous subfield has a low priority if _, ok := m[k]; !ok { m[k] = v } diff --git a/slice.go b/slice.go index 506ded1..02dd9c5 100644 --- a/slice.go +++ b/slice.go @@ -24,7 +24,7 @@ func SliceE(val interface{}) (sl []interface{}, err error) { return sl, errUnsupportedTypeNil } - _, rv := indirect(val) + _, rv := Indirect(val) switch rv.Kind() { case reflect.String: @@ -184,7 +184,7 @@ func ColumnsE(val interface{}, field interface{}) (sl []interface{}, err error) return nil, errUnsupportedTypeNil } - _, rv := indirect(val) + _, rv := Indirect(val) switch rv.Kind() { case reflect.Slice, reflect.Array: @@ -217,7 +217,7 @@ func KeysE(val interface{}) (sl []interface{}, err error) { return nil, errUnsupportedTypeNil } - _, rv := indirect(val) + _, rv := Indirect(val) switch rv.Kind() { case reflect.Map: diff --git a/string.go b/string.go index 7b2af98..d9aee36 100644 --- a/string.go +++ b/string.go @@ -63,7 +63,7 @@ func StringE(val interface{}) (string, error) { } // indirect type - v, rv := indirect(val) + v, rv := Indirect(val) switch vv := v.(type) { case nil: return "", nil diff --git a/time.go b/time.go index 52623e6..c0d399d 100644 --- a/time.go +++ b/time.go @@ -1,6 +1,7 @@ package cvt import ( + "encoding/json" "fmt" "time" ) @@ -20,15 +21,38 @@ func Time(v interface{}, def ...time.Time) time.Time { // TimeE convert an interface to a time.Time type func TimeE(val interface{}) (t time.Time, err error) { - v, _ := indirect(val) + // direct type(for improve performance) + switch vv := val.(type) { + case nil: + return + case time.Time: + return vv, nil + case string: + return parseDate(vv) + case time.Duration, + int, int32, int64, uint, uint32, uint64: + return time.Unix(Int64(vv), 0), nil + case json.Number: + // timestamp + vvv, err := vv.Int64() + if err == nil { + return time.Unix(Int64(vvv), 0), nil + } + // time string + return parseDate(vv.String()) + } - // source type + // indirect type + v, _ := Indirect(val) switch vv := v.(type) { + case nil: + return case time.Time: return vv, nil case string: return parseDate(vv) - case int, int32, int64, uint, uint32, uint64: + case time.Duration, + int, int32, int64, uint, uint32, uint64: return time.Unix(Int64(vv), 0), nil } @@ -38,7 +62,7 @@ func TimeE(val interface{}) (t time.Time, err error) { return parseDate(vv.String()) } - return time.Time{}, newErr(val, "time.Time") + return t, newErr(val, "time.Time") } func parseDate(s string) (t time.Time, err error) { diff --git a/time_test.go b/time_test.go index 4abdcdb..e3c7d92 100644 --- a/time_test.go +++ b/time_test.go @@ -1,6 +1,7 @@ package cvt_test import ( + "encoding/json" "fmt" "testing" "time" @@ -55,6 +56,7 @@ func TestTimeE(t *testing.T) { expect time.Time isErr bool }{ + {nil, time.Time{}, false}, {"2009-11-10 23:00:00 +0000 UTC", time.Date(2009, 11, 10, 23, 0, 0, 0, loc), false}, // Time.String() {"Tue Nov 10 23:00:00 2009", time.Date(2009, 11, 10, 23, 0, 0, 0, loc), false}, // ANSIC {"Tue Nov 10 23:00:00 UTC 2009", time.Date(2009, 11, 10, 23, 0, 0, 0, loc), false}, // UnixDate @@ -108,6 +110,15 @@ func TestTimeE(t *testing.T) { {uint32(1234567890), time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, {time.Date(2009, 2, 13, 23, 31, 30, 0, loc), time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, {TestTimeStringer{time.Date(2010, 3, 7, 0, 0, 0, 0, loc)}, time.Date(2010, 3, 7, 0, 0, 0, 0, loc), false}, + {pointerIntNil, time.Time{}, false}, + {aliasTypeStringTime1, time.Date(2016, 3, 6, 15, 28, 1, 0, loc), false}, + {&aliasTypeStringTime1, time.Date(2016, 3, 6, 15, 28, 1, 0, loc), false}, + {aliasTypeIntTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, + {&aliasTypeIntTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, + {aliasTypeTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, + {&aliasTypeTime1, time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, + {json.Number("1234567890"), time.Date(2009, 2, 13, 23, 31, 30, 0, loc), false}, + {json.Number(aliasTypeStringTime1), time.Date(2016, 3, 6, 15, 28, 1, 0, loc), false}, // errors {"2006", time.Time{}, true},