decode: preserve nanosecond precision when decoding time (#626)

Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
Cameron Moore
2021-10-17 19:43:29 -05:00
committed by GitHub
parent 76f53c857b
commit a23850f29b
5 changed files with 58 additions and 18 deletions
+1
View File
@@ -205,6 +205,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
} }
t.Nanosecond = frac * nspow[digits] t.Nanosecond = frac * nspow[digits]
t.Precision = digits
return t, b[9+digits:], nil return t, b[9+digits:], nil
} }
@@ -1514,6 +1514,7 @@ func TestUnmarshalLocalDateTime(t *testing.T) {
Minute: 32, Minute: 32,
Second: 0, Second: 0,
Nanosecond: 999999000, Nanosecond: 999999000,
Precision: 6,
}, },
}, },
}, },
@@ -1600,6 +1601,7 @@ func TestUnmarshalLocalTime(t *testing.T) {
Minute: 32, Minute: 32,
Second: 0, Second: 0,
Nanosecond: 999999000, Nanosecond: 999999000,
Precision: 6,
}, },
}, },
} }
+18 -7
View File
@@ -2,6 +2,7 @@ package toml
import ( import (
"fmt" "fmt"
"strings"
"time" "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 // LocalTime represents a time of day of no specific day in no specific
// timezone. // timezone.
type LocalTime struct { type LocalTime struct {
Hour int Hour int // Hour of the day: [0; 24[
Minute int Minute int // Minute of the hour: [0; 60[
Second int Second int // Second of the minute: [0; 60[
Nanosecond int Nanosecond int // Nanoseconds within the second: [0, 1000000000[
Precision int // Number of digits to display for Nanosecond.
} }
// String returns RFC 3339 representation of d. // 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 { func (d LocalTime) String() string {
s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second) 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. // MarshalText returns RFC 3339 representation of d.
+19 -8
View File
@@ -37,14 +37,18 @@ func TestLocalDate_UnmarshalMarshalText(t *testing.T) {
} }
func TestLocalTime_String(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()) 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()) 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) { 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() b, err := d.MarshalText()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, []byte("20:12:01.000000002"), b) require.Equal(t, []byte("20:12:01.000000002"), b)
@@ -54,7 +58,7 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
d := toml.LocalTime{} d := toml.LocalTime{}
err := d.UnmarshalText([]byte("20:12:01.000000002")) err := d.UnmarshalText([]byte("20:12:01.000000002"))
require.NoError(t, err) 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")) err = d.UnmarshalText([]byte("what"))
require.Error(t, err) require.Error(t, err)
@@ -63,10 +67,17 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
require.Error(t, err) 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) { func TestLocalDateTime_AsTime(t *testing.T) {
d := toml.LocalDateTime{ d := toml.LocalDateTime{
toml.LocalDate{2021, 6, 8}, toml.LocalDate{2021, 6, 8},
toml.LocalTime{20, 12, 1, 2}, toml.LocalTime{20, 12, 1, 2, 9},
} }
cast := d.AsTime(time.UTC) cast := d.AsTime(time.UTC)
require.Equal(t, time.Date(2021, time.June, 8, 20, 12, 1, 2, time.UTC), cast) 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) { func TestLocalDateTime_String(t *testing.T) {
d := toml.LocalDateTime{ d := toml.LocalDateTime{
toml.LocalDate{2021, 6, 8}, 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()) 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) { func TestLocalDateTime_MarshalText(t *testing.T) {
d := toml.LocalDateTime{ d := toml.LocalDateTime{
toml.LocalDate{2021, 6, 8}, toml.LocalDate{2021, 6, 8},
toml.LocalTime{20, 12, 1, 2}, toml.LocalTime{20, 12, 1, 2, 9},
} }
b, err := d.MarshalText() b, err := d.MarshalText()
require.NoError(t, err) require.NoError(t, err)
@@ -96,7 +107,7 @@ func TestLocalDateTime_UnmarshalMarshalText(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, toml.LocalDateTime{ require.Equal(t, toml.LocalDateTime{
toml.LocalDate{2021, 6, 8}, toml.LocalDate{2021, 6, 8},
toml.LocalTime{20, 12, 1, 2}, toml.LocalTime{20, 12, 1, 2, 9},
}, d) }, d)
err = d.UnmarshalText([]byte("what")) err = d.UnmarshalText([]byte("what"))
+18 -3
View File
@@ -331,7 +331,7 @@ func TestUnmarshal(t *testing.T) {
expected: &doc{ expected: &doc{
A: toml.LocalDateTime{ A: toml.LocalDateTime{
toml.LocalDate{1979, 5, 27}, 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{ return test{
target: &v, target: &v,
expected: &map[string]interface{}{ 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 { examples := []struct {
desc string desc string
input string input string
prec int
}{ }{
{
desc: "9 digits zero nanoseconds",
input: "2006-01-02T15:04:05.000000000",
prec: 9,
},
{ {
desc: "9 digits", desc: "9 digits",
input: "2006-01-02T15:04:05.123456789", input: "2006-01-02T15:04:05.123456789",
prec: 9,
}, },
{ {
desc: "8 digits", desc: "8 digits",
input: "2006-01-02T15:04:05.12345678", input: "2006-01-02T15:04:05.12345678",
prec: 8,
}, },
{ {
desc: "7 digits", desc: "7 digits",
input: "2006-01-02T15:04:05.1234567", input: "2006-01-02T15:04:05.1234567",
prec: 7,
}, },
{ {
desc: "6 digits", desc: "6 digits",
input: "2006-01-02T15:04:05.123456", input: "2006-01-02T15:04:05.123456",
prec: 6,
}, },
{ {
desc: "5 digits", desc: "5 digits",
input: "2006-01-02T15:04:05.12345", input: "2006-01-02T15:04:05.12345",
prec: 5,
}, },
{ {
desc: "4 digits", desc: "4 digits",
input: "2006-01-02T15:04:05.1234", input: "2006-01-02T15:04:05.1234",
prec: 4,
}, },
{ {
desc: "3 digits", desc: "3 digits",
input: "2006-01-02T15:04:05.123", input: "2006-01-02T15:04:05.123",
prec: 3,
}, },
{ {
desc: "2 digits", desc: "2 digits",
input: "2006-01-02T15:04:05.12", input: "2006-01-02T15:04:05.12",
prec: 2,
}, },
{ {
desc: "1 digit", desc: "1 digit",
input: "2006-01-02T15:04:05.1", input: "2006-01-02T15:04:05.1",
prec: 1,
}, },
{ {
desc: "0 digit", desc: "0 digit",
@@ -2260,7 +2275,7 @@ func TestLocalDateTime(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
expected := toml.LocalDateTime{ expected := toml.LocalDateTime{
toml.LocalDate{golang.Year(), int(golang.Month()), golang.Day()}, 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) require.Equal(t, expected, actual)
}) })