feat: make seconds optional in datetime and time values
TOML v1.1.0 allows times to be specified as HH:MM without the seconds component (previously HH:MM:SS was required). This applies to local times, local datetimes, and offset datetimes.
This commit is contained in:
@@ -162,7 +162,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
|
|||||||
|
|
||||||
const localDateTimeByteMinLen = 11
|
const localDateTimeByteMinLen = 11
|
||||||
if len(b) < localDateTimeByteMinLen {
|
if len(b) < localDateTimeByteMinLen {
|
||||||
return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]")
|
return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM[:SS[.NNNNNNNNN]]")
|
||||||
}
|
}
|
||||||
|
|
||||||
date, err := parseLocalDate(b[:10])
|
date, err := parseLocalDate(b[:10])
|
||||||
@@ -194,10 +194,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
t LocalTime
|
t LocalTime
|
||||||
)
|
)
|
||||||
|
|
||||||
// check if b matches to have expected format HH:MM:SS[.NNNNNN]
|
// check if b matches to have expected format HH:MM[:SS[.NNNNNN]]
|
||||||
const localTimeByteLen = 8
|
const localTimeByteMinLen = 5
|
||||||
if len(b) < localTimeByteLen {
|
if len(b) < localTimeByteMinLen {
|
||||||
return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
|
return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM[:SS[.NNNNNN]]")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -221,20 +221,25 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
if t.Minute > 59 {
|
if t.Minute > 59 {
|
||||||
return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59")
|
return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59")
|
||||||
}
|
}
|
||||||
if b[5] != ':' {
|
|
||||||
return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Second, err = parseDecimalDigits(b[6:8])
|
b = b[5:]
|
||||||
if err != nil {
|
|
||||||
return t, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Second > 59 {
|
if len(b) >= 1 && b[0] == ':' {
|
||||||
return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater than 59")
|
if len(b) < 3 {
|
||||||
}
|
return t, nil, unstable.NewParserError(b, "incomplete seconds")
|
||||||
|
}
|
||||||
|
|
||||||
b = b[8:]
|
t.Second, err = parseDecimalDigits(b[1:3])
|
||||||
|
if err != nil {
|
||||||
|
return t, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Second > 59 {
|
||||||
|
return t, nil, unstable.NewParserError(b[1:3], "seconds cannot be greater than 59")
|
||||||
|
}
|
||||||
|
|
||||||
|
b = b[3:]
|
||||||
|
}
|
||||||
|
|
||||||
if len(b) >= 1 && b[0] == '.' {
|
if len(b) >= 1 && b[0] == '.' {
|
||||||
frac := 0
|
frac := 0
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalTime_UnmarshalText_WithoutSeconds(t *testing.T) {
|
||||||
|
d := toml.LocalTime{}
|
||||||
|
err := d.UnmarshalText([]byte("14:15"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, toml.LocalTime{14, 15, 0, 0, 0}, d)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocalTime_RoundTrip(t *testing.T) {
|
func TestLocalTime_RoundTrip(t *testing.T) {
|
||||||
var d struct{ A toml.LocalTime }
|
var d struct{ A toml.LocalTime }
|
||||||
err := toml.Unmarshal([]byte("a=20:12:01.500"), &d)
|
err := toml.Unmarshal([]byte("a=20:12:01.500"), &d)
|
||||||
|
|||||||
+8
-16
@@ -420,10 +420,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoLeads(t *testing.T) {
|
|||||||
testgenInvalid(t, input)
|
testgenInvalid(t, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoSecs(t *testing.T) {
|
// TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoSecs is removed because
|
||||||
input := "# No seconds in time.\nno-secs = 1987-07-05T17:45Z\n"
|
// TOML v1.1.0 makes seconds optional in date-time values.
|
||||||
testgenInvalid(t, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoT(t *testing.T) {
|
func TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoT(t *testing.T) {
|
||||||
input := "# No \"t\" or \"T\" between the date and time.\nno-t = 1987-07-0517:45:00Z\n"
|
input := "# No \"t\" or \"T\" between the date and time.\nno-t = 1987-07-0517:45:00Z\n"
|
||||||
@@ -1325,10 +1323,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoLeads(t *testing.T) {
|
|||||||
testgenInvalid(t, input)
|
testgenInvalid(t, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoSecs(t *testing.T) {
|
// TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoSecs is removed because
|
||||||
input := "# No seconds in time.\nno-secs = 1987-07-05T17:45\n"
|
// TOML v1.1.0 makes seconds optional in date-time values.
|
||||||
testgenInvalid(t, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoT(t *testing.T) {
|
func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoT(t *testing.T) {
|
||||||
input := "# No \"t\" or \"T\" between the date and time.\nno-t = 1987-07-0517:45:00\n"
|
input := "# No \"t\" or \"T\" between the date and time.\nno-t = 1987-07-0517:45:00\n"
|
||||||
@@ -1360,10 +1356,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_MinuteOver(t *testing.T) {
|
|||||||
testgenInvalid(t, input)
|
testgenInvalid(t, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_NoSecs(t *testing.T) {
|
// TestTOMLTest_Invalid_Tests_Invalid_LocalTime_NoSecs is removed because
|
||||||
input := "# No seconds in time.\nno-secs = 17:45\n"
|
// TOML v1.1.0 makes seconds optional in time values.
|
||||||
testgenInvalid(t, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_SecondOver(t *testing.T) {
|
func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_SecondOver(t *testing.T) {
|
||||||
input := "# time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second\n# ; rules\nd = 00:00:61\n"
|
input := "# time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second\n# ; rules\nd = 00:00:61\n"
|
||||||
@@ -1515,10 +1509,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_String_BadUniEsc7(t *testing.T) {
|
|||||||
testgenInvalid(t, input)
|
testgenInvalid(t, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_String_BasicByteEscapes(t *testing.T) {
|
// TestTOMLTest_Invalid_Tests_Invalid_String_BasicByteEscapes is removed because
|
||||||
input := "answer = \"\\x33\"\n"
|
// TOML v1.1.0 adds \xHH escape sequence support.
|
||||||
testgenInvalid(t, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTOMLTest_Invalid_Tests_Invalid_String_BasicMultilineOutOfRangeUnicodeEscape1(t *testing.T) {
|
func TestTOMLTest_Invalid_Tests_Invalid_String_BasicMultilineOutOfRangeUnicodeEscape1(t *testing.T) {
|
||||||
input := "a = \"\"\"\\UFFFFFFFF\"\"\"\n"
|
input := "a = \"\"\"\\UFFFFFFFF\"\"\"\n"
|
||||||
|
|||||||
+91
-1
@@ -600,6 +600,96 @@ foo = "bar"`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "local-time without seconds",
|
||||||
|
input: `a = 14:15`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]interface{}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]interface{}{
|
||||||
|
"a": toml.LocalTime{Hour: 14, Minute: 15},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "local-datetime without seconds using T",
|
||||||
|
input: `a = 2010-02-03T14:15`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]interface{}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]interface{}{
|
||||||
|
"a": toml.LocalDateTime{
|
||||||
|
LocalDate: toml.LocalDate{2010, 2, 3},
|
||||||
|
LocalTime: toml.LocalTime{Hour: 14, Minute: 15},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "local-datetime without seconds using space",
|
||||||
|
input: `a = 2010-02-03 14:15`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]interface{}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]interface{}{
|
||||||
|
"a": toml.LocalDateTime{
|
||||||
|
LocalDate: toml.LocalDate{2010, 2, 3},
|
||||||
|
LocalTime: toml.LocalTime{Hour: 14, Minute: 15},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "datetime without seconds with Z",
|
||||||
|
input: `a = 2010-02-03T14:15Z`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]time.Time
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]time.Time{
|
||||||
|
"a": time.Date(2010, 2, 3, 14, 15, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "datetime without seconds with offset",
|
||||||
|
input: `a = 2010-02-03T14:15+05:00`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]time.Time
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]time.Time{
|
||||||
|
"a": time.Date(2010, 2, 3, 14, 15, 0, 0, time.FixedZone("", 5*3600)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "local-time with seconds and fractional regression",
|
||||||
|
input: `a = 14:15:30.123`,
|
||||||
|
gen: func() test {
|
||||||
|
var v map[string]interface{}
|
||||||
|
|
||||||
|
return test{
|
||||||
|
target: &v,
|
||||||
|
expected: &map[string]interface{}{
|
||||||
|
"a": toml.LocalTime{Hour: 14, Minute: 15, Second: 30, Nanosecond: 123000000, Precision: 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "local-time missing digit",
|
desc: "local-time missing digit",
|
||||||
input: `a = 12:08:0`,
|
input: `a = 12:08:0`,
|
||||||
@@ -3243,7 +3333,7 @@ world'`,
|
|||||||
{
|
{
|
||||||
desc: "bad char between minutes and seconds",
|
desc: "bad char between minutes and seconds",
|
||||||
data: `a = 2021-03-30 21:312:0`,
|
data: `a = 2021-03-30 21:312:0`,
|
||||||
msg: `expecting colon between minutes and seconds`,
|
msg: `extra characters at the end of a local date time`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid hour value",
|
desc: "invalid hour value",
|
||||||
|
|||||||
Reference in New Issue
Block a user