diff --git a/decode.go b/decode.go index 6d900ed..6d0d9fe 100644 --- a/decode.go +++ b/decode.go @@ -205,6 +205,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { } t.Nanosecond = frac * nspow[digits] + t.Precision = digits return t, b[9+digits:], nil } diff --git a/internal/imported_tests/unmarshal_imported_test.go b/internal/imported_tests/unmarshal_imported_test.go index bac07ac..630f05b 100644 --- a/internal/imported_tests/unmarshal_imported_test.go +++ b/internal/imported_tests/unmarshal_imported_test.go @@ -1514,6 +1514,7 @@ func TestUnmarshalLocalDateTime(t *testing.T) { Minute: 32, Second: 0, Nanosecond: 999999000, + Precision: 6, }, }, }, @@ -1600,6 +1601,7 @@ func TestUnmarshalLocalTime(t *testing.T) { Minute: 32, Second: 0, Nanosecond: 999999000, + Precision: 6, }, }, } diff --git a/localtime.go b/localtime.go index 4e32ecc..5a91bd6 100644 --- a/localtime.go +++ b/localtime.go @@ -2,6 +2,7 @@ package toml import ( "fmt" + "strings" "time" ) @@ -40,19 +41,29 @@ func (d *LocalDate) UnmarshalText(b []byte) error { // LocalTime represents a time of day of no specific day in no specific // timezone. type LocalTime struct { - Hour int - Minute int - Second int - Nanosecond int + Hour int // Hour of the day: [0; 24[ + Minute int // Minute of the hour: [0; 60[ + Second int // Second of the minute: [0; 60[ + Nanosecond int // Nanoseconds within the second: [0, 1000000000[ + Precision int // Number of digits to display for Nanosecond. } // String returns RFC 3339 representation of d. +// If d.Nanosecond and d.Precision are zero, the time won't have a nanosecond +// component. If d.Nanosecond > 0 but d.Precision = 0, then the minimum number +// of digits for nanoseconds is provided. func (d LocalTime) String() string { s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second) - if d.Nanosecond == 0 { - return s + + if d.Precision > 0 { + s += fmt.Sprintf(".%09d", d.Nanosecond)[:d.Precision+1] + } else if d.Nanosecond > 0 { + // Nanoseconds are specified, but precision is not provided. Use the + // minimum. + s += strings.Trim(fmt.Sprintf(".%09d", d.Nanosecond), "0") } - return s + fmt.Sprintf(".%09d", d.Nanosecond) + + return s } // MarshalText returns RFC 3339 representation of d. diff --git a/localtime_test.go b/localtime_test.go index 6ad9f0a..d972e5d 100644 --- a/localtime_test.go +++ b/localtime_test.go @@ -37,14 +37,18 @@ func TestLocalDate_UnmarshalMarshalText(t *testing.T) { } func TestLocalTime_String(t *testing.T) { - d := toml.LocalTime{20, 12, 1, 2} + d := toml.LocalTime{20, 12, 1, 2, 9} require.Equal(t, "20:12:01.000000002", d.String()) - d = toml.LocalTime{20, 12, 1, 0} + d = toml.LocalTime{20, 12, 1, 0, 0} require.Equal(t, "20:12:01", d.String()) + d = toml.LocalTime{20, 12, 1, 0, 9} + require.Equal(t, "20:12:01.000000000", d.String()) + d = toml.LocalTime{20, 12, 1, 100, 0} + require.Equal(t, "20:12:01.0000001", d.String()) } func TestLocalTime_MarshalText(t *testing.T) { - d := toml.LocalTime{20, 12, 1, 2} + d := toml.LocalTime{20, 12, 1, 2, 9} b, err := d.MarshalText() require.NoError(t, err) require.Equal(t, []byte("20:12:01.000000002"), b) @@ -54,7 +58,7 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) { d := toml.LocalTime{} err := d.UnmarshalText([]byte("20:12:01.000000002")) require.NoError(t, err) - require.Equal(t, toml.LocalTime{20, 12, 1, 2}, d) + require.Equal(t, toml.LocalTime{20, 12, 1, 2, 9}, d) err = d.UnmarshalText([]byte("what")) require.Error(t, err) @@ -63,10 +67,17 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) { require.Error(t, err) } +func TestLocalTime_RoundTrip(t *testing.T) { + var d struct{ A toml.LocalTime } + err := toml.Unmarshal([]byte("a=20:12:01.500"), &d) + require.NoError(t, err) + require.Equal(t, "20:12:01.500", d.A.String()) +} + func TestLocalDateTime_AsTime(t *testing.T) { d := toml.LocalDateTime{ toml.LocalDate{2021, 6, 8}, - toml.LocalTime{20, 12, 1, 2}, + toml.LocalTime{20, 12, 1, 2, 9}, } cast := d.AsTime(time.UTC) require.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast) @@ -75,7 +86,7 @@ func TestLocalDateTime_AsTime(t *testing.T) { func TestLocalDateTime_String(t *testing.T) { d := toml.LocalDateTime{ toml.LocalDate{2021, 6, 8}, - toml.LocalTime{20, 12, 1, 2}, + toml.LocalTime{20, 12, 1, 2, 9}, } require.Equal(t, "2021-06-08 20:12:01.000000002", d.String()) } @@ -83,7 +94,7 @@ func TestLocalDateTime_String(t *testing.T) { func TestLocalDateTime_MarshalText(t *testing.T) { d := toml.LocalDateTime{ toml.LocalDate{2021, 6, 8}, - toml.LocalTime{20, 12, 1, 2}, + toml.LocalTime{20, 12, 1, 2, 9}, } b, err := d.MarshalText() require.NoError(t, err) @@ -96,7 +107,7 @@ func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) { require.NoError(t, err) require.Equal(t, toml.LocalDateTime{ toml.LocalDate{2021, 6, 8}, - toml.LocalTime{20, 12, 1, 2}, + toml.LocalTime{20, 12, 1, 2, 9}, }, d) err = d.UnmarshalText([]byte("what")) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 491f554..2f59d55 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -331,7 +331,7 @@ func TestUnmarshal(t *testing.T) { expected: &doc{ A: toml.LocalDateTime{ toml.LocalDate{1979, 5, 27}, - toml.LocalTime{0, 32, 0, 0}, + toml.LocalTime{0, 32, 0, 0, 0}, }, }, } @@ -378,7 +378,7 @@ func TestUnmarshal(t *testing.T) { return test{ target: &v, expected: &map[string]interface{}{ - "a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5, Nanosecond: 666666666}, + "a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5, Nanosecond: 666666666, Precision: 9}, }, } }, @@ -2204,42 +2204,57 @@ func TestLocalDateTime(t *testing.T) { examples := []struct { desc string input string + prec int }{ + { + desc: "9 digits zero nanoseconds", + input: "2006-01-02T15:04:05.000000000", + prec: 9, + }, { desc: "9 digits", input: "2006-01-02T15:04:05.123456789", + prec: 9, }, { desc: "8 digits", input: "2006-01-02T15:04:05.12345678", + prec: 8, }, { desc: "7 digits", input: "2006-01-02T15:04:05.1234567", + prec: 7, }, { desc: "6 digits", input: "2006-01-02T15:04:05.123456", + prec: 6, }, { desc: "5 digits", input: "2006-01-02T15:04:05.12345", + prec: 5, }, { desc: "4 digits", input: "2006-01-02T15:04:05.1234", + prec: 4, }, { desc: "3 digits", input: "2006-01-02T15:04:05.123", + prec: 3, }, { desc: "2 digits", input: "2006-01-02T15:04:05.12", + prec: 2, }, { desc: "1 digit", input: "2006-01-02T15:04:05.1", + prec: 1, }, { desc: "0 digit", @@ -2260,7 +2275,7 @@ func TestLocalDateTime(t *testing.T) { require.NoError(t, err) expected := toml.LocalDateTime{ toml.LocalDate{golang.Year(), int(golang.Month()), golang.Day()}, - toml.LocalTime{golang.Hour(), golang.Minute(), golang.Second(), golang.Nanosecond()}, + toml.LocalTime{golang.Hour(), golang.Minute(), golang.Second(), golang.Nanosecond(), e.prec}, } require.Equal(t, expected, actual) })