go-sql-driver/mysql · error
invalid time bytes: %s
Error message
invalid time bytes: %s
What it means
Thrown by parseDateTime (utils.go:179) — the default branch of the length switch. It fires when the textual datetime byte length is NOT one of {10, 19, 21, 22, 23, 24, 25, 26} (the only legal MySQL DATE/DATETIME text lengths). The offending bytes are interpolated into the message, which is useful for diagnosing what the server actually sent.
Source
Thrown at utils.go:179
sec, err := parseByte2Digits(b[17], b[18])
if err != nil {
return time.Time{}, err
}
if len(b) == 19 {
return time.Date(year, month, day, hour, min, sec, 0, loc), nil
}
if b[19] != '.' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[19])
}
nsec, err := parseByteNanoSec(b[20:])
if err != nil {
return time.Time{}, err
}
return time.Date(year, month, day, hour, min, sec, nsec, loc), nil
default:
return time.Time{}, fmt.Errorf("invalid time bytes: %s", b)
}
}
func parseByteYear(b []byte) (int, error) {
year, n := 0, 1000
for i := range 4 {
v, err := bToi(b[i])
if err != nil {
return 0, err
}
year += v * n
n /= 10
}
return year, nil
}
func parseByte2Digits(b1, b2 byte) (int, error) {
d1, err := bToi(b1)View on GitHub (pinned to c426bd9379)
Solutions
- Look at the message: the %s shows the exact bytes. Compare against expected 'YYYY-MM-DD[ HH:MM:SS[.ffffff]]'.
- If the format is legitimately different, SELECT the column CAST AS CHAR and parse with the matching time.Parse layout client-side.
- Check the column type with SHOW COLUMNS / DESCRIBE — scan string columns as string, not time.Time.
- Test directly against MySQL (bypassing proxies) to confirm the server itself emits the odd length.
Example fix
// before: column is actually a VARCHAR holding '20200101'
var t time.Time
err := db.QueryRow("SELECT d FROM t").Scan(&t)
// after
var raw string
if err := db.QueryRow("SELECT CAST(d AS CHAR) FROM t").Scan(&raw); err == nil {
t, err = time.ParseInLocation("20060102", raw, time.Local)
} Defensive patterns
Strategy: validation
Validate before calling
// before scanning, confirm the textual length class is one MySQL produces
var raw string
if err := db.QueryRow("SELECT CAST(d AS CHAR) FROM t WHERE id=?", id).Scan(&raw); err != nil {
return err
}
switch len(raw) {
case 10, 19, 21, 22, 23, 24, 25, 26:
t, err := time.ParseInLocation("2006-01-02 15:04:05.999999", raw, time.Local)
_ = t
default:
return fmt.Errorf("unexpected datetime length %d for %q", len(raw), raw)
} Try / catch
var t time.Time
if err := rows.Scan(&t); err != nil {
if strings.Contains(err.Error(), "invalid time bytes") {
// length is unsupported; read as string and handle/log
}
} Prevention
- Scan date-like columns into string first when their format is uncertain, then parse client-side.
- Verify column DDL with SHOW COLUMNS to avoid scanning non-date types into time.Time.
- Lock down server SQL_MODE so the textual datetime shape is stable.
When it happens
Trigger: Scanning a DATE/DATETIME/TIMESTAMP column whose textual wire value has an unexpected length — e.g. an 8-byte 'YYYYMMDD', a 17-byte value, or trailing whitespace/garbage that changes the length class.
Common situations: Server returns a date in a compact 'YYYYMMDD' format (some compatibility modes / non-MySQL servers); a column declared DATETIME but holding a NULL-ish or zero-padded oddity; a proxy stripping or adding bytes; SQL_MODE NO_ZERO_DATE or similar producing unusual representations; a bug where a VARCHAR value reaches a time.Time scan.
Related errors
- not [0-9]
- invalid DATETIME packet length %d
- illegal %s length %d
- illegal %s packet length %d
- MySQL server does not support required protocol 41+
AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04).
Data as JSON: /data/errors/80f0e21011499bb2.json.
Report an issue: GitHub.