Skip to content
Permalink
Browse files
feat(datastore): support civil package types save (#3202)
Adds save/load support for Date, Time and Datetime from the package cloud.google.com/go/civil.
  • Loading branch information
IlyaFaer committed Dec 2, 2020
1 parent fe64496 commit 9cc1a66e22ecd8dcad1235c290f05b92edff5aa0
Showing with 186 additions and 4 deletions.
  1. +17 −4 datastore/load.go
  2. +64 −0 datastore/load_test.go
  3. +23 −0 datastore/save.go
  4. +82 −0 datastore/save_test.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 {
@@ -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: &timestamppb.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: &timestamppb.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: &timestamppb.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 {
@@ -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:
@@ -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{

0 comments on commit 9cc1a66

Please sign in to comment.