diff --git a/pkg/schema/field/timestamp.go b/pkg/schema/field/timestamp.go new file mode 100644 index 0000000000000000000000000000000000000000..7f89aefc1982e12996f9307129bc396e501cecb9 --- /dev/null +++ b/pkg/schema/field/timestamp.go @@ -0,0 +1,85 @@ +package field + +import ( + "context" + "fmt" + "time" +) + +var ( + zeroTime = time.Time{} + timestampType = &TimestampType{} +) + +type TimestampParameters struct{} + +func (t TimestampParameters) Type() Type { return timestampType } +func (t *TimestampParameters) Clone(reset bool) Parameters { return t } + +type TimestampType struct{} + +func (t TimestampType) Name() string { + return "timestamp" +} + +func (TimestampType) NewParameters() Parameters { + return &TimestampParameters{} +} + +func (TimestampType) IsEmpty(v interface{}) bool { + return v == 0 || v == nil +} + +func toTimestamp(i interface{}) (interface{}, error) { + switch v := i.(type) { + case nil: + return nil, nil + case int64: + return v, nil + case int: + return int64(v), nil + case int8: + return int64(v), nil + case int32: + return int64(v), nil + case uint64: + return int64(v), nil + case uint: + return int64(v), nil + case uint8: + return int64(v), nil + case uint32: + return int64(v), nil + default: + return nil, fmt.Errorf("unsupported value type: \"%T\"", i) + } +} + +func (TimestampType) Decode(_ context.Context, _ *Field, v interface{}) (interface{}, error) { + switch i := v.(type) { + case string: + duration, err := time.ParseDuration(i) + if err == nil { + return duration.Nanoseconds(), nil + } + t, err := time.Parse(time.TimeOnly, i) + + if err == nil { + return t.AddDate(1, 0, 0).Sub(zeroTime).Nanoseconds(), nil + } + return nil, err + default: + return toTimestamp(i) + } +} + +func (TimestampType) Encode(_ context.Context, _ *Field, v interface{}) (interface{}, error) { + switch i := v.(type) { + default: + return toTimestamp(i) + } +} + +func Timestamp(o ...interface{}) *Field { + return NewField(&TimestampParameters{}, o...) +} diff --git a/pkg/schema/field/timestamp_test.go b/pkg/schema/field/timestamp_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0875a69963590806527f104eee43faedb7a5a85c --- /dev/null +++ b/pkg/schema/field/timestamp_test.go @@ -0,0 +1,85 @@ +package field + +import ( + "context" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTimestamp_Decode(t *testing.T) { + tests := []struct { + name string + field *Field + data interface{} + want interface{} + wantErr bool + errMsg string + }{ + {"Correct", Timestamp(), int64(2), int64(2), false, ""}, // #0 + {"Correct", Timestamp(), int32(2), int64(2), false, ""}, // #1 + {"Correct", Timestamp(), 2, int64(2), false, ""}, // #2 + {"Correct", Timestamp(), "13h10m44s", int64(47444000000000), false, ""}, // #3 + {"Correct", Timestamp(), "24h", int64(86400000000000), false, ""}, // #4 + {"Correct", Timestamp(), "2.5h", int64(9000000000000), false, ""}, // #5 + {"Correct", Timestamp(), "-5h", int64(-18000000000000), false, ""}, // #5 + {"Correct", Timestamp(), "13:10:44", int64(47444000000000), false, ""}, // #6 + {"Correct", Timestamp(), "23:59:59", int64(86399000000000), false, ""}, // #7 + {"Correct", Timestamp(), "00:00:00", int64(0), false, ""}, // #8 + {"Correct", Timestamp(), "00:00:01", int64(1000000000), false, ""}, // #8 + {"Correct", Timestamp(), uint64(2), int64(2), false, ""}, + {"Correct", Timestamp(), nil, nil, false, ""}, + + {"Wrong data", Timestamp(), "", nil, true, "decode error: parsing time \"\" as \"15:04:05\": cannot parse \"\" as \"15\""}, // #0 + {"Wrong data", Timestamp(), []byte(""), nil, true, "decode error: unsupported value type: \"[]uint8\""}, // #1 + {"Wrong data", Timestamp(), 2.2, nil, true, "decode error: unsupported value type: \"float64\""}, // #2 + {"Wrong data", Timestamp(), "13:10", nil, true, "decode error: parsing time \"13:10\" as \"15:04:05\": cannot parse \"\" as \":\""}, // #3 + {"Wrong data", Timestamp(), "24:00:00", nil, true, "decode error: parsing time \"24:00:00\": hour out of range"}, // #4 + + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Decode(context.Background(), tt.field, tt.data) + if tt.wantErr { + require.Equal(t, tt.errMsg, err.Error()) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Decode() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTimestamp_Encode(t *testing.T) { + tests := []struct { + name string + field *Field + data interface{} + want interface{} + wantErr bool + errMsg string + }{ + {"Correct", Timestamp(), int64(2), int64(2), false, ""}, // #0 + {"Correct", Timestamp(), 2, int64(2), false, ""}, // #1 + {"Correct", Timestamp(), uint64(2), int64(2), false, ""}, // #2 + + {"Wrong data", Timestamp(), "", nil, true, "encode error: unsupported value type: \"string\""}, // #0 + {"Wrong data", Timestamp(), []byte(""), nil, true, "encode error: unsupported value type: \"[]uint8\""}, // #1 + {"Wrong data", Timestamp(), 2.2, nil, true, "encode error: unsupported value type: \"float64\""}, // #2 + + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Encode(context.Background(), tt.field, tt.data) + if tt.wantErr { + require.Equal(t, tt.errMsg, err.Error()) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Decode() got = %v, want %v", got, tt.want) + } + }) + } +}