diff --git a/datastore/load.go b/datastore/load.go index 086b6b6f31c..5b1102961b8 100644 --- a/datastore/load.go +++ b/datastore/load.go @@ -20,15 +20,19 @@ import ( "strings" "time" + "cloud.google.com/go/civil" "cloud.google.com/go/internal/fields" pb "google.golang.org/genproto/googleapis/datastore/v1" ) var ( - typeOfByteSlice = reflect.TypeOf([]byte(nil)) - typeOfTime = reflect.TypeOf(time.Time{}) - typeOfGeoPoint = reflect.TypeOf(GeoPoint{}) - typeOfKeyPtr = reflect.TypeOf(&Key{}) + typeOfByteSlice = reflect.TypeOf([]byte(nil)) + typeOfTime = reflect.TypeOf(time.Time{}) + typeOfCivilDate = reflect.TypeOf(civil.Date{}) + typeOfCivilDateTime = reflect.TypeOf(civil.DateTime{}) + typeOfCivilTime = reflect.TypeOf(civil.Time{}) + typeOfGeoPoint = reflect.TypeOf(GeoPoint{}) + typeOfKeyPtr = reflect.TypeOf(&Key{}) ) // typeMismatchReason returns a string explaining why the property p could not @@ -379,6 +383,15 @@ func setVal(v reflect.Value, p Property) (s string) { return typeMismatchReason(p, v) } v.Set(reflect.ValueOf(x)) + case typeOfCivilDate: + date := civil.DateOf(pValue.(time.Time)) + v.Set(reflect.ValueOf(date)) + case typeOfCivilDateTime: + dateTime := civil.DateTimeOf(pValue.(time.Time)) + v.Set(reflect.ValueOf(dateTime)) + case typeOfCivilTime: + timeVal := civil.TimeOf(pValue.(time.Time)) + v.Set(reflect.ValueOf(timeVal)) default: ent, ok := pValue.(*Entity) if !ok { diff --git a/datastore/load_test.go b/datastore/load_test.go index 75ccab95d62..518fa61c10b 100644 --- a/datastore/load_test.go +++ b/datastore/load_test.go @@ -20,8 +20,10 @@ import ( "testing" "time" + "cloud.google.com/go/civil" "cloud.google.com/go/internal/testutil" pb "google.golang.org/genproto/googleapis/datastore/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) type Simple struct { @@ -484,6 +486,68 @@ func TestLoadToInterface(t *testing.T) { dst: &withUntypedInterface{Field: 1e9}, want: &withUntypedInterface{Field: "Newly set"}, }, + { + name: "struct with civil.Date", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Date": {ValueType: &pb.Value_TimestampValue{TimestampValue: ×tamppb.Timestamp{Seconds: 1605474000}}}, + }, + }, + dst: &struct{ Date civil.Date }{ + Date: civil.Date{}, + }, + want: &struct{ Date civil.Date }{ + Date: civil.Date{ + Year: 2020, + Month: 11, + Day: 15, + }, + }, + }, + { + name: "struct with civil.DateTime", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "DateTime": {ValueType: &pb.Value_TimestampValue{TimestampValue: ×tamppb.Timestamp{Seconds: 1605504600}}}, + }, + }, + dst: &struct{ DateTime civil.DateTime }{ + DateTime: civil.DateTime{}, + }, + want: &struct{ DateTime civil.DateTime }{ + DateTime: civil.DateTime{ + Date: civil.Date{ + Year: 2020, + Month: 11, + Day: 16, + }, + Time: civil.Time{ + Hour: 5, + Minute: 30, + }, + }, + }, + }, + { + name: "struct with civil.Time", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Time": {ValueType: &pb.Value_TimestampValue{TimestampValue: ×tamppb.Timestamp{Seconds: 1605504600}}}, + }, + }, + dst: &struct{ Time civil.Time }{ + Time: civil.Time{}, + }, + want: &struct{ Time civil.Time }{ + Time: civil.Time{ + Hour: 5, + Minute: 30, + }, + }, + }, } for _, tc := range testCases { diff --git a/datastore/save.go b/datastore/save.go index 08874ad7465..f6945ec6e1c 100644 --- a/datastore/save.go +++ b/datastore/save.go @@ -21,6 +21,7 @@ import ( "time" "unicode/utf8" + "cloud.google.com/go/civil" timepb "github.com/golang/protobuf/ptypes/timestamp" pb "google.golang.org/genproto/googleapis/datastore/v1" llpb "google.golang.org/genproto/googleapis/type/latlng" @@ -53,6 +54,28 @@ func reflectFieldSave(props *[]Property, p Property, name string, opts saveOpts, switch x := v.Interface().(type) { case *Key, time.Time, GeoPoint: p.Value = x + case civil.Date: + p.Value = x.In(time.UTC) + *props = append(*props, p) + return nil + case civil.Time: + var format string + if x.Nanosecond == 0 { + format = "15:04:05" + } else { + format = "15:04:05.000000000" + } + val, err := time.Parse(format, x.String()) + if err != nil { + return fmt.Errorf("datastore: error while parsing civil.Time: %v", err) + } + p.Value = val + *props = append(*props, p) + return nil + case civil.DateTime: + p.Value = x.In(time.UTC) + *props = append(*props, p) + return nil default: switch v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: diff --git a/datastore/save_test.go b/datastore/save_test.go index ac52f7c1724..5071e6054d5 100644 --- a/datastore/save_test.go +++ b/datastore/save_test.go @@ -15,9 +15,11 @@ package datastore import ( + "fmt" "testing" "time" + "cloud.google.com/go/civil" "cloud.google.com/go/internal/testutil" pb "google.golang.org/genproto/googleapis/datastore/v1" ) @@ -321,6 +323,27 @@ func TestSaveFieldsWithInterface(t *testing.T) { N2 interface{} } + civDateVal := civil.Date{ + Year: 2020, + Month: 11, + Day: 10, + } + civTimeValNano := civil.Time{ + Hour: 1, + Minute: 1, + Second: 1, + Nanosecond: 1, + } + civTimeVal := civil.Time{ + Hour: 1, + Minute: 1, + Second: 1, + } + timeValNano, _ := time.Parse("15:04:05.000000000", civTimeValNano.String()) + timeVal, _ := time.Parse("15:04:05", civTimeVal.String()) + dateTimeStr := fmt.Sprintf("%v %v", civDateVal.String(), civTimeVal.String()) + dateTimeVal, _ := time.ParseInLocation("2006-01-02 15:04:05", dateTimeStr, time.UTC) + cases := []struct { name string in interface{} @@ -371,6 +394,65 @@ func TestSaveFieldsWithInterface(t *testing.T) { {Name: "Map", Value: []Property{}}, }, }, + { + name: "civil.Date", + in: &struct { + CivDate civil.Date + }{ + CivDate: civDateVal, + }, + want: []Property{ + { + Name: "CivDate", + Value: civDateVal.In(time.UTC), + }, + }, + }, + { + name: "civil.Time-nano", + in: &struct { + CivTimeNano civil.Time + }{ + CivTimeNano: civTimeValNano, + }, + want: []Property{ + { + Name: "CivTimeNano", + Value: timeValNano, + }, + }, + }, + { + name: "civil.Time", + in: &struct { + CivTime civil.Time + }{ + CivTime: civTimeVal, + }, + want: []Property{ + { + Name: "CivTime", + Value: timeVal, + }, + }, + }, + { + name: "civil.DateTime", + in: &struct { + CivDateTime civil.DateTime + }{ + CivDateTime: civil.DateTime{ + Date: civDateVal, + Time: civTimeVal, + }, + }, + want: []Property{ + { + Name: "CivDateTime", + Value: dateTimeVal, + }, + }, + }, { name: "Nested", in: &n3{