Skip to content

time: Parse incorrectly parses fractional seconds with more than 9 digits #48685

@jtbandes

Description

@jtbandes

What version of Go are you using (go version)?

1.17.1 (play.golang.org)

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

play.golang.org

What did you do?

https://play.golang.org/p/S21r1Xz1VlZ

package main

import (
	"fmt"
	"time"
)

func main() {
	t, err := time.Parse(time.RFC3339, "2021-09-29T16:04:33.00123456789Z")
	fmt.Println(t, t.Nanosecond(), err)
	t, err = time.Parse(time.RFC3339, "2021-09-29T16:04:33.10123456789Z")
	fmt.Println(t, t.Nanosecond(), err)
}

What did you expect to see?

either fractional seconds truncated to nanosecond precision:

2021-09-29 16:04:33.001234567 +0000 UTC 1234567 <nil>
2021-09-29 16:04:33.101234567 +0000 UTC 101234567 <nil>

or fractional seconds rounded to nearest nanosecond:

2021-09-29 16:04:33.001234568 +0000 UTC 1234568 <nil>
2021-09-29 16:04:33.101234568 +0000 UTC 101234568 <nil>

or an error in both cases.

What did you see instead?

2021-09-29 16:04:33.123456789 +0000 UTC 123456789 <nil>
0001-01-01 00:00:00 +0000 UTC 0 parsing time "2021-09-29T16:04:33.10123456789Z": fractional second out of range

It appears that if more than 9 digits are provided for the fractional second part, they are interpreted all together as a numeric value for the nanoseconds part, regardless of leading zeros. Thus .0012345678Z is interpreted the same as .012345678Z.

Also, when more than 9 significant digits are provided, rather than truncating or rounding to the nearest nanosecond, parsing produces a fractional second out of range error because this error condition is triggered:

go/src/time/format.go

Lines 1426 to 1428 in 163871f

if ns < 0 || 1e9 <= ns {
rangeErrString = "fractional second"
return

This bug affects json.Unmarshal / time.UnmarshalJSON which uses the RFC3339 format internally:

*t, err = Parse(`"`+RFC3339+`"`, string(data))

The code at fault is probably one or more of these call sites of parseNanoseconds:

go/src/time/format.go

Lines 1082 to 1086 in 163871f

// No fractional second in the layout but we have one in the input.
n := 2
for ; n < len(value) && isDigit(value, n); n++ {
}
nsec, rangeErrString, err = parseNanoseconds(value, n)

go/src/time/format.go

Lines 1194 to 1201 in 163871f

// stdFracSecond0 requires the exact number of digits as specified in
// the layout.
ndigit := 1 + digitsLen(std)
if len(value) < ndigit {
err = errBad
break
}
nsec, rangeErrString, err = parseNanoseconds(value, ndigit)

go/src/time/format.go

Lines 1209 to 1215 in 163871f

// Take any number of digits, even more than asked for,
// because it is what the stdSecond case would do.
i := 0
for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' {
i++
}
nsec, rangeErrString, err = parseNanoseconds(value, 1+i)

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions