← Debugging Guides

阅读中文版本

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.

beginnerdebugginggotimeparsing

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 -4

Interpretation

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.