diff --git a/cast.go b/cast.go index ec1ba30..26640b2 100644 --- a/cast.go +++ b/cast.go @@ -1,15 +1,14 @@ package cast import ( - "fmt" "reflect" "strconv" "time" ) var ( - // DateFormats are our valid date formats - DateFormats = []string{ + // dateFormats are our default date formats + dateFormats = []string{ "2006-01-02", "2006-01-02 15:04:05", time.RFC3339, @@ -20,34 +19,59 @@ var ( } ) -func valueOf(v interface{}) reflect.Value { - if v == nil { - return reflect.Value{} +// From html/template/content.go +// Copyright 2011 The Go Authors. All rights reserved. +// indirect returns the value, after dereferencing as many times +// as necessary to reach the base type (or nil). +func indirect(a interface{}) interface{} { + if a == nil { + return nil } - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr { - return rv + if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { + // Avoid creating a reflect.Value if it's not a pointer. + return a } - return rv.Elem() + v := reflect.ValueOf(a) + for v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + return v.Interface() } // AsBool to convert as bool func AsBool(v interface{}) (bool, bool) { - rv := valueOf(v) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int() > 0, true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rv.Uint() > 0, true - case reflect.Float32, reflect.Float64: - return rv.Float() > 0, true - case reflect.String: - if b, err := strconv.ParseBool(rv.String()); err == nil { + switch d := indirect(v).(type) { + case bool: + return d, true + case int: + return d > 0, true + case int64: + return d > 0, true + case int32: + return d > 0, true + case int16: + return d > 0, true + case int8: + return d > 0, true + case uint: + return d > 0, true + case uint64: + return d > 0, true + case uint32: + return d > 0, true + case uint16: + return d > 0, true + case uint8: + return d > 0, true + case float64: + return d > 0, true + case float32: + return d > 0, true + case string: + if b, err := strconv.ParseBool(d); err == nil { return b, true } return false, false - case reflect.Bool: - return rv.Bool(), true default: return false, false } @@ -69,22 +93,39 @@ func AsBoolArray(values ...interface{}) ([]bool, bool) { // AsString to convert as string func AsString(v interface{}) (string, bool) { - rv := valueOf(v) - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(rv.Int(), 10), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return strconv.FormatUint(rv.Uint(), 10), true - case reflect.Float32, reflect.Float64: - return strconv.FormatFloat(rv.Float(), 'f', -1, 64), true - case reflect.String: - return rv.String(), true - case reflect.Bool: - return strconv.FormatBool(rv.Bool()), true - case reflect.Invalid: - return "", false + switch d := indirect(v).(type) { + case string: + return d, true + case bool: + return strconv.FormatBool(d), true + case int: + return strconv.FormatInt(int64(d), 10), true + case int64: + return strconv.FormatInt(d, 10), true + case int32: + return strconv.FormatInt(int64(d), 10), true + case int16: + return strconv.FormatInt(int64(d), 10), true + case int8: + return strconv.FormatInt(int64(d), 10), true + case uint: + return strconv.FormatUint(uint64(d), 10), true + case uint64: + return strconv.FormatUint(d, 10), true + case uint32: + return strconv.FormatUint(uint64(d), 10), true + case uint16: + return strconv.FormatUint(uint64(d), 10), true + case uint8: + return strconv.FormatUint(uint64(d), 10), true + case float64: + return strconv.FormatFloat(d, 'f', -1, 64), true + case float32: + return strconv.FormatFloat(float64(d), 'f', -1, 64), true + case []byte: + return string(d), true default: - return fmt.Sprintf("%v", v), false + return "", false } } @@ -102,27 +143,44 @@ func AsStringArray(values ...interface{}) ([]string, bool) { return arr, b } -// AsInt to convert as a int +// AsInt to convert as a int64 +// for now, no overflow detection func AsInt(v interface{}) (int64, bool) { - rv := valueOf(v) - - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rv.Int(), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return int64(rv.Uint()), true - case reflect.Float32, reflect.Float64: - return int64(rv.Float()), true - case reflect.String: - if n, err := strconv.ParseInt(rv.String(), 10, 64); err == nil { - return n, true - } - return 0, false - case reflect.Bool: - if n := rv.Bool(); n { + switch d := indirect(v).(type) { + case bool: + if d { return 1, true } return 0, true + case int: + return int64(d), true + case int64: + return d, true + case int32: + return int64(d), true + case int16: + return int64(d), true + case int8: + return int64(d), true + case uint: + return int64(d), true + case uint64: + return int64(d), true + case uint32: + return int64(d), true + case uint16: + return int64(d), true + case uint8: + return int64(d), true + case float64: + return int64(d), true + case float32: + return int64(d), true + case string: + if n, err := strconv.ParseInt(d, 10, 64); err == nil { + return n, true + } + return 0, false default: return 0, false } @@ -143,40 +201,64 @@ func AsIntArray(values ...interface{}) ([]int64, bool) { } // AsUint to convert as a uint64 +// for now, no overflow detection func AsUint(v interface{}) (uint64, bool) { - rv := valueOf(v) - - switch kind := rv.Kind(); kind { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := rv.Int() - if n < 0 { - return 0, false + switch d := indirect(v).(type) { + case bool: + if d { + return 1, true } - return uint64(n), true - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return uint64(rv.Uint()), true - - case reflect.Float32, reflect.Float64: - n := reflect.ValueOf(v).Float() - if n < 0 { - return 0, false + return 0, true + case int: + if d >= 0 { + return uint64(d), true } - return uint64(n), true - - case reflect.String: - if n, err := strconv.ParseUint(rv.String(), 10, 64); err == nil { - return n, true + return 0, false + case int64: + if d >= 0 { + return uint64(d), true } return 0, false - - case reflect.Bool: - n := rv.Bool() - if n { - return 1, true + case int32: + if d >= 0 { + return uint64(d), true } - return 0, true - + return 0, false + case int16: + if d >= 0 { + return uint64(d), true + } + return 0, false + case int8: + if d >= 0 { + return uint64(d), true + } + return 0, false + case uint: + return uint64(d), true + case uint64: + return d, true + case uint32: + return uint64(d), true + case uint16: + return uint64(d), true + case uint8: + return uint64(d), true + case float64: + if d >= 0 { + return uint64(d), true + } + return 0, false + case float32: + if d >= 0 { + return uint64(d), true + } + return 0, false + case string: + if n, err := strconv.ParseUint(d, 10, 64); err == nil { + return n, true + } + return 0, false default: return 0, false } @@ -198,25 +280,41 @@ func AsUintArray(values ...interface{}) ([]uint64, bool) { // AsFloat to convert as a float64 func AsFloat(v interface{}) (float64, bool) { - rv := valueOf(v) - - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(rv.Int()), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return float64(rv.Uint()), true - case reflect.Float32, reflect.Float64: - return rv.Float(), true - case reflect.String: - if n, err := strconv.ParseFloat(rv.String(), 64); err == nil { - return n, true - } - return 0, false - case reflect.Bool: - if n := rv.Bool(); n { + switch d := indirect(v).(type) { + case bool: + if d { return 1, true } return 0, true + case int: + return float64(d), true + case int64: + return float64(d), true + case int32: + return float64(d), true + case int16: + return float64(d), true + case int8: + return float64(d), true + case uint: + return float64(d), true + case uint64: + return float64(d), true + case uint32: + return float64(d), true + case uint16: + return float64(d), true + case uint8: + return float64(d), true + case float64: + return float64(d), true + case float32: + return float64(d), true + case string: + if n, err := strconv.ParseFloat(d, 64); err == nil { + return n, true + } + return 0, false default: return 0, false } @@ -237,37 +335,36 @@ func AsFloatArray(values ...interface{}) ([]float64, bool) { } // AsDatetime to convert as datetime (time.Time) -func AsDatetime(v interface{}) (time.Time, bool) { - - rv := valueOf(v) - - switch rv.Kind() { - case reflect.Int, reflect.Int32, reflect.Int64: - // timestamp compliant with javascript - return time.Unix(0, rv.Int()*int64(time.Millisecond)).UTC(), true - - case reflect.String: - str := rv.String() +func AsDatetime(v interface{}, format ...string) (time.Time, bool) { + switch d := indirect(v).(type) { + case time.Time: + return d.UTC(), true + + // timestamp compliant with javascript + case int: + return time.Unix(0, int64(d)*int64(time.Millisecond)).UTC(), true + case int64: + return time.Unix(0, d*int64(time.Millisecond)).UTC(), true + case int32: + return time.Unix(0, int64(d)*int64(time.Millisecond)).UTC(), true + case float64: + return time.Unix(0, int64(d)*int64(time.Millisecond)).UTC(), true + + case string: // Try formats - for _, format := range DateFormats { - if dt, err := time.Parse(format, str); err == nil { + format = append(format, dateFormats...) + for _, f := range format { + if dt, err := time.Parse(f, d); err == nil { return dt.UTC(), true } } // Try to convert to int64 - if ts, err := strconv.ParseInt(str, 10, 64); err == nil { + if ts, err := strconv.ParseInt(d, 10, 64); err == nil { return time.Unix(0, ts*int64(time.Millisecond)).UTC(), true } return time.Time{}, false - - case reflect.Struct: - if c, ok := v.(time.Time); ok { - return c.UTC(), true - } - return time.Time{}, false - default: return time.Time{}, false } @@ -289,23 +386,35 @@ func AsDatetimeArray(values ...interface{}) ([]time.Time, bool) { // AsDuration to convert as a duration func AsDuration(v interface{}) (time.Duration, bool) { - rv := valueOf(v) - - switch rv.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return time.Duration(rv.Int()), true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return time.Duration(int64(rv.Uint())), true - case reflect.Float32, reflect.Float64: - return time.Duration(int64(rv.Float())), true - - case reflect.String: - if du, err := time.ParseDuration(rv.String()); err == nil { - return du, true - } - return time.Duration(0), false - case reflect.Struct: - if du, ok := v.(time.Duration); ok { + switch d := indirect(v).(type) { + case time.Duration: + return d, true + case int: + return time.Duration(int64(d)), true + case int64: + return time.Duration(d), true + case int32: + return time.Duration(int64(d)), true + case int16: + return time.Duration(int64(d)), true + case int8: + return time.Duration(int64(d)), true + case uint: + return time.Duration(int64(d)), true + case uint64: + return time.Duration(int64(d)), true + case uint32: + return time.Duration(int64(d)), true + case uint16: + return time.Duration(int64(d)), true + case uint8: + return time.Duration(int64(d)), true + case float64: + return time.Duration(int64(d)), true + case float32: + return time.Duration(int64(d)), true + case string: + if du, err := time.ParseDuration(d); err == nil { return du, true } return time.Duration(0), false diff --git a/cast_test.go b/cast_test.go index 9eda75a..de748a4 100644 --- a/cast_test.go +++ b/cast_test.go @@ -67,6 +67,7 @@ func TestAsStringArray(t *testing.T) { } func TestAsInt(t *testing.T) { + testInt(t, nil, 0, false) testInt(t, "123", 123, true) testInt(t, "wrong", 0, false) testInt(t, true, 1, true) @@ -85,17 +86,18 @@ func TestAsInt(t *testing.T) { testInt(t, float64(123), 123, true) } -func TestAsUintArray(t *testing.T) { - arr, ok := cast.AsUintArray(1, 2, true) +func TestAsIntArray(t *testing.T) { + arr, ok := cast.AsIntArray(1, 2, true) assert.True(t, ok) - assert.Equal(t, []uint64{1, 2, 1}, arr) + assert.Equal(t, []int64{1, 2, 1}, arr) - arr, ok = cast.AsUintArray(1, 2, true, "no", -2) + arr, ok = cast.AsIntArray(1, 2, true, "no") assert.False(t, ok) - assert.Equal(t, []uint64{1, 2, 1, 0, 0}, arr) + assert.Equal(t, []int64{1, 2, 1, 0}, arr) } func TestAsUint(t *testing.T) { + testUint(t, nil, 0, false) testUint(t, "123", 123, true) testUint(t, "-123", 0, false) testUint(t, "wrong", 0, false) @@ -121,17 +123,18 @@ func TestAsUint(t *testing.T) { testUint(t, float64(-123), 0, false) } -func TestAsIntArray(t *testing.T) { - arr, ok := cast.AsIntArray(1, 2, true) +func TestAsUintArray(t *testing.T) { + arr, ok := cast.AsUintArray(1, 2, true) assert.True(t, ok) - assert.Equal(t, []int64{1, 2, 1}, arr) + assert.Equal(t, []uint64{1, 2, 1}, arr) - arr, ok = cast.AsIntArray(1, 2, true, "no") + arr, ok = cast.AsUintArray(1, 2, true, "no", -2) assert.False(t, ok) - assert.Equal(t, []int64{1, 2, 1, 0}, arr) + assert.Equal(t, []uint64{1, 2, 1, 0, 0}, arr) } func TestAsFloat(t *testing.T) { + testFloat(t, nil, 0, false) testFloat(t, "123", 123, true) testFloat(t, "wrong", 0, false) testFloat(t, true, 1, true)