Go — time.Parse Fails: parsing time with the wrong layout
Fix parsing time ... as ... errors in Go by learning the reference-time layout system, mapping real timestamp shapes to layouts, and testing at boundaries.
Symptoms
- parsing time "2026-10-22 13:04:05" as "2006-01-02 15:04:05": cannot parse ":04:05" as "5" style errors
- The same string parses in other languages with strftime patterns like %Y-%m-%d but not in Go
- Errors mention unexpected characters where the format string and the input visually match
Possible causes
- Using strftime tokens (%Y, %d, %H) or made-up layouts (yyyy-MM-dd) instead of the Go reference time
- The layout uses 15 for a 12-hour clock input or misses the leading zero expectations of 04 versus 4
- The input has a timezone offset or fractional seconds that the layout does not describe
- A trailing character (space, Z, timezone abbreviation) exists in the input but not the layout
Diagnosis steps
Read the error as a diff
go run ./parse 2>&1 | head -4Interpretation
The error shows the exact leftover substring and the layout token it could not satisfy — the mismatch position, not just the fact.
Rebuild the layout from the reference time
go doc time.Parse; echo "reference: Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700)"Interpretation
Every layout token is a piece of that fixed reference instant; writing the input shape in reference-time characters produces the correct layout.
Cover the boundary variants
go test -run TestParseFormats -v ./timeutil/Interpretation
Inputs with offsets, fractional seconds, and single- versus double-digit fields each need their own layout or a fallback chain.
Root cause
Go formats time by exact string matching against a reference instant: Mon Jan 2 15:04:05 MST 2006. Layouts must be written using that reference vocabulary, so strftime habits (%Y-%m-%d) or other-language tokens (yyyy-MM-dd) become literal text the parser cannot match, and it fails at the first divergent character.
Fix
Write layouts in reference-time tokens, prefer RFC3339 layouts for interchange, use time.Parse with the exact layout including offsets and fractional seconds, and cover boundary variants in table-driven tests.
Risk note
Lenient parsing of unknown formats by trying many layouts can silently succeed with the wrong interpretation (month and day swaps). Validate parsed results against round-trip formatting.
Verify the fix
Round-trip every accepted format: parse then format and assert the canonical output equals the input or a documented canonicalization.
Symptom walkthrough
The error is a diff, if you know how to read it:
parsing time "2026-10-22 13:04:05" as "2026-01-02 13:04:05":
cannot parse ":04:05" as "2"
The layout here was built with the input’s own numbers instead of the reference time — the parser matched up to a point and then failed on the first divergence. The one fact that fixes the entire class:
Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700)
Layouts are written in those characters. 01 is month, 02 day, 2006 year, 15 hour-24, 04 minute, 05 second, -0700 offset, .000 fractional. Input 2026-10-22 maps to layout 2006-01-02 — the digits of the reference instant, not of your data.
Fix in practice
Map the input shape to reference characters, then let constants carry the common cases:
import "time"
t, err := time.Parse(time.RFC3339, "2026-10-22T13:04:05+08:00")
t, err = time.Parse("2006-01-02 15:04:05.000 -0700", s)
For messy inputs, try candidates in order and validate with a round trip:
layouts := []string{
time.RFC3339,
"2006-01-02 15:04:05",
"02/Jan/2006:15:04:05 -0700", // CLF
}
for _, l := range layouts {
if t, err = time.Parse(l, s); err == nil {
if t.Format(l) == canonical(s) { // guard against month/day swaps
break
}
}
}
The round-trip guard matters: a layout that swaps day and month can parse successfully and be wrong, which is strictly worse than failing.