Skip to content

Commit

Permalink
Improve Time* functions
Browse files Browse the repository at this point in the history
* `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`
  • Loading branch information
shockerli committed Sep 9, 2022
1 parent 253983d commit b09b389
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 47 deletions.
2 changes: 1 addition & 1 deletion bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 11 additions & 4 deletions cvte.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"sort"
"strings"
"time"
)

var errConvFail = errors.New("convert failed")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
42 changes: 40 additions & 2 deletions cvte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,13 +59,16 @@ var (
aliasTypeStringOff AliasTypeString = "off"
aliasTypeStringLosePrecisionInt64 AliasTypeString = "7138826985640367621"
aliasTypeStringLosePrecisionFloat64 AliasTypeString = "7138826985640367621.18"
aliasTypeStringTime1 AliasTypeString = "2016-03-06 15:28:01"

pointerRunes = []rune("中国")
pointerInterNil *AliasTypeInterface
pointerIntNil *AliasTypeInt
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
Expand Down Expand Up @@ -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]
Expand Down
10 changes: 10 additions & 0 deletions doc/content/zh-cn/type/time.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
28 changes: 6 additions & 22 deletions float.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -93,26 +86,17 @@ 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:
return float64(rv.Int()), nil
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
}
Expand Down
18 changes: 10 additions & 8 deletions int.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 28 additions & 4 deletions time.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cvt

import (
"encoding/json"
"fmt"
"time"
)
Expand All @@ -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
}

Expand All @@ -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) {
Expand Down
Loading

0 comments on commit b09b389

Please sign in to comment.