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
|
||||
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])
|
||||
@@ -194,10 +194,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
t LocalTime
|
||||
)
|
||||
|
||||
// check if b matches to have expected format HH:MM:SS[.NNNNNN]
|
||||
const localTimeByteLen = 8
|
||||
if len(b) < localTimeByteLen {
|
||||
return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
|
||||
// check if b matches to have expected format HH:MM[:SS[.NNNNNN]]
|
||||
const localTimeByteMinLen = 5
|
||||
if len(b) < localTimeByteMinLen {
|
||||
return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM[:SS[.NNNNNN]]")
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -221,20 +221,25 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
||||
if t.Minute > 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])
|
||||
if err != nil {
|
||||
return t, nil, err
|
||||
}
|
||||
b = b[5:]
|
||||
|
||||
if t.Second > 59 {
|
||||
return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater than 59")
|
||||
}
|
||||
if len(b) >= 1 && b[0] == ':' {
|
||||
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] == '.' {
|
||||
frac := 0
|
||||
|
||||
@@ -67,6 +67,13 @@ func TestLocalTime_UnmarshalMarshalText(t *testing.T) {
|
||||
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) {
|
||||
var d struct{ A toml.LocalTime }
|
||||
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)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoSecs(t *testing.T) {
|
||||
input := "# No seconds in time.\nno-secs = 1987-07-05T17:45Z\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
// TestTOMLTest_Invalid_Tests_Invalid_Datetime_NoSecs is removed because
|
||||
// TOML v1.1.0 makes seconds optional in date-time values.
|
||||
|
||||
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"
|
||||
@@ -1325,10 +1323,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoLeads(t *testing.T) {
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoSecs(t *testing.T) {
|
||||
input := "# No seconds in time.\nno-secs = 1987-07-05T17:45\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
// TestTOMLTest_Invalid_Tests_Invalid_LocalDatetime_NoSecs is removed because
|
||||
// TOML v1.1.0 makes seconds optional in date-time values.
|
||||
|
||||
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"
|
||||
@@ -1360,10 +1356,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_MinuteOver(t *testing.T) {
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Tests_Invalid_LocalTime_NoSecs(t *testing.T) {
|
||||
input := "# No seconds in time.\nno-secs = 17:45\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
// TestTOMLTest_Invalid_Tests_Invalid_LocalTime_NoSecs is removed because
|
||||
// TOML v1.1.0 makes seconds optional in time values.
|
||||
|
||||
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"
|
||||
@@ -1515,10 +1509,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_String_BadUniEsc7(t *testing.T) {
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
|
||||
func TestTOMLTest_Invalid_Tests_Invalid_String_BasicByteEscapes(t *testing.T) {
|
||||
input := "answer = \"\\x33\"\n"
|
||||
testgenInvalid(t, input)
|
||||
}
|
||||
// TestTOMLTest_Invalid_Tests_Invalid_String_BasicByteEscapes is removed because
|
||||
// TOML v1.1.0 adds \xHH escape sequence support.
|
||||
|
||||
func TestTOMLTest_Invalid_Tests_Invalid_String_BasicMultilineOutOfRangeUnicodeEscape1(t *testing.T) {
|
||||
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",
|
||||
input: `a = 12:08:0`,
|
||||
@@ -3243,7 +3333,7 @@ world'`,
|
||||
{
|
||||
desc: "bad char between minutes and seconds",
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user