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.Precision = digits
return t, b[9+digits:], nil
}
@@ -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,
},
},
}
+18 -7
View File
@@ -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.
+19 -8
View File
@@ -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"))
+18 -3
View File
@@ -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)
})