decode: preserve nanosecond precision when decoding time (#626)
Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user