From 3a4d7af89ea2acbac4f78e7073fb0f8e6ee52c46 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Fri, 25 Oct 2019 14:07:46 -0400 Subject: [PATCH] Local DateTime support (#317) --- marshal.go | 20 +++++- marshal_test.go | 165 ++++++++++++++++++++++++++++++++++++++++++++++ tomltree_write.go | 2 + 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/marshal.go b/marshal.go index 03ff054..1c181f3 100644 --- a/marshal.go +++ b/marshal.go @@ -69,6 +69,7 @@ const ( var timeType = reflect.TypeOf(time.Time{}) var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() var localDateType = reflect.TypeOf(LocalDate{}) +var localDateTimeType = reflect.TypeOf(LocalDateTime{}) // Check if the given marshal type maps to a Tree primitive func isPrimitive(mtype reflect.Type) bool { @@ -86,7 +87,7 @@ func isPrimitive(mtype reflect.Type) bool { case reflect.String: return true case reflect.Struct: - return mtype == timeType || mtype == localDateType || isCustomMarshaler(mtype) + return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || isCustomMarshaler(mtype) default: return false } @@ -706,12 +707,27 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref case reflect.Bool, reflect.Struct: val := reflect.ValueOf(tval) - if val.Type() == localDateType { + switch val.Type() { + case localDateType: localDate := val.Interface().(LocalDate) switch mtype { case timeType: return reflect.ValueOf(time.Date(localDate.Year, localDate.Month, localDate.Day, 0, 0, 0, 0, time.Local)), nil } + case localDateTimeType: + localDateTime := val.Interface().(LocalDateTime) + switch mtype { + case timeType: + return reflect.ValueOf(time.Date( + localDateTime.Date.Year, + localDateTime.Date.Month, + localDateTime.Date.Day, + localDateTime.Time.Hour, + localDateTime.Time.Minute, + localDateTime.Time.Second, + localDateTime.Time.Nanosecond, + time.Local)), nil + } } // if this passes for when mtype is reflect.Struct, tval is a time.LocalTime diff --git a/marshal_test.go b/marshal_test.go index 9e24b09..dae6fb7 100644 --- a/marshal_test.go +++ b/marshal_test.go @@ -1873,3 +1873,168 @@ func TestMarshalLocalDate(t *testing.T) { t.Errorf("expected '%s', got '%s'", expected, got) } } + +func TestUnmarshalLocalDateTime(t *testing.T) { + examples := []struct { + name string + in string + out LocalDateTime + }{ + { + name: "normal", + in: "1979-05-27T07:32:00", + out: LocalDateTime{ + Date: LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }}, + { + name: "with nanoseconds", + in: "1979-05-27T00:32:00.999999", + out: LocalDateTime{ + Date: LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + }, + } + + for i, example := range examples { + toml := fmt.Sprintf(`date = %s`, example.in) + + t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date LocalDateTime + } + + var obj dateStruct + + err := Unmarshal([]byte(toml), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date != example.out { + t.Errorf("expected '%s', got '%s'", example.out, obj.Date) + } + }) + + t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { + type dateStruct struct { + Date time.Time + } + + var obj dateStruct + + err := Unmarshal([]byte(toml), &obj) + + if err != nil { + t.Fatal(err) + } + + if obj.Date.Year() != example.out.Date.Year { + t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) + } + if obj.Date.Month() != example.out.Date.Month { + t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) + } + if obj.Date.Day() != example.out.Date.Day { + t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) + } + if obj.Date.Hour() != example.out.Time.Hour { + t.Errorf("expected day %d, got %d", example.out.Time.Hour, obj.Date.Hour()) + } + if obj.Date.Minute() != example.out.Time.Minute { + t.Errorf("expected day %d, got %d", example.out.Time.Minute, obj.Date.Minute()) + } + if obj.Date.Second() != example.out.Time.Second { + t.Errorf("expected day %d, got %d", example.out.Time.Second, obj.Date.Second()) + } + if obj.Date.Nanosecond() != example.out.Time.Nanosecond { + t.Errorf("expected day %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) + } + }) + } +} + +func TestMarshalLocalDateTime(t *testing.T) { + type dateStruct struct { + DateTime LocalDateTime + } + + examples := []struct { + name string + in LocalDateTime + out string + }{ + { + name: "normal", + out: "DateTime = 1979-05-27T07:32:00\n", + in: LocalDateTime{ + Date: LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: LocalTime{ + Hour: 7, + Minute: 32, + Second: 0, + Nanosecond: 0, + }, + }}, + { + name: "with nanoseconds", + out: "DateTime = 1979-05-27T00:32:00.999999000\n", + in: LocalDateTime{ + Date: LocalDate{ + Year: 1979, + Month: 5, + Day: 27, + }, + Time: LocalTime{ + Hour: 0, + Minute: 32, + Second: 0, + Nanosecond: 999999000, + }, + }, + }, + } + + for i, example := range examples { + t.Run(fmt.Sprintf("%d_%s", i, example.name), func(t *testing.T) { + obj := dateStruct{ + DateTime: example.in, + } + b, err := Marshal(obj) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got := string(b) + + if got != example.out { + t.Errorf("expected '%s', got '%s'", example.out, got) + } + }) + } +} diff --git a/tomltree_write.go b/tomltree_write.go index ee35db4..a065650 100644 --- a/tomltree_write.go +++ b/tomltree_write.go @@ -138,6 +138,8 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen return value.Format(time.RFC3339), nil case LocalDate: return value.String(), nil + case LocalDateTime: + return value.String(), nil case nil: return "", nil }