go-sql-driver/mysql · error
illegal TIME length
Error message
illegal TIME length %d
What it means
Raised by formatBinaryTime (utils.go:460) for the DISPLAY length. The driver computes a target display length for a binary TIME value from the column's decimals; valid lengths are {8 (no fractional), 10-15 (with 1-6 fractional digits)}. An out-of-range length means the column metadata is internally inconsistent. Because packets.go pre-validates decimals, reaching this line implies protocol corruption rather than a normal data value.
Solutions
- Patch/upgrade the server or proxy returning invalid TIME metadata; verify with a direct mysql client.
- Read the TIME column raw ([]byte) or CAST(... AS CHAR) in SQL to bypass binary formatting.
- Run the query without prepared statements to use the text protocol.
- Re-prepare / reconnect to refresh column metadata.
Example fix
// before: driver formats binary TIME db.QueryRow(`SELECT duration FROM t WHERE id=?`, id).Scan(&dur) // after: let the server convert to text var dur string db.QueryRow(`SELECT CAST(duration AS CHAR) FROM t WHERE id=?`, id).Scan(&dur)
Defensive patterns
Strategy: try-catch
Validate before calling
func validTimeDisplayLen(l uint8) bool {
switch l {
case 8, 10, 11, 12, 13, 14, 15:
return true
}
return false
} Type guard
func isCanonicalTimeDisplayLen(l uint8) bool {
switch l {
case 8, 10, 11, 12, 13, 14, 15:
return true
}
return false
} Try / catch
var s string
if err := db.QueryRow(q, id).Scan(&s); err != nil {
if strings.Contains(err.Error(), "illegal TIME length") {
var raw []byte
_ = db.QueryRow(`SELECT CAST(dur AS CHAR) FROM t WHERE id=?`, id).Scan(&raw)
s = string(raw)
}
} Prevention
- Cast TIME columns to CHAR when reading through proxies of unknown compliance.
- Avoid prepared statements for TIME columns through flaky middleware.
- Reconnect to refresh column metadata after ALTER TABLE on TIME columns.
When it happens
Trigger: Reading a TIME column from a prepared-statement result set (parseTime=false) when the column metadata yields a display length outside {8, 10-15}. Reached via formatBinaryTime at packets.go:1414. Practically limited to non-conforming MySQL-compatible servers, stale cached metadata after schema changes, or packet desync.
Common situations: A proxy/sharder returning malformed TIME column definitions, schema drift after ALTER TIME column, or an exotic server encoding TIME differently than the standard binary protocol.
Related errors
- invalid TIME packet length
- illegal length
- illegal packet length
- invalid DATETIME packet length
- can't convert %T to time.Time
AI-assisted analysis of go-sql-driver/mysql@03d76c7e07 (2026-08-07).
Data as JSON: /api/errors/16d3f5c15a746c80.
Report an issue: GitHub.
Appendix: source
Thrown at utils.go:460
digits10[p3], digits01[p3],
)
return appendMicrosecs(dst, src[2:], int(length)-20), nil
}
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
// length expects the deterministic length of the zero value,
// negative time and 100+ hours are automatically added if needed
if len(src) == 0 {
return zeroDateTime[11 : 11+length], nil
}
var dst []byte // return value
switch length {
case
8, // time (can be up to 10 when negative and 100+ hours)
10, 11, 12, 13, 14, 15: // time with fractional seconds
default:
return nil, fmt.Errorf("illegal TIME length %d", length)
}
switch len(src) {
case 8, 12:
default:
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
}
// +2 to enable negative time and 100+ hours
dst = make([]byte, 0, length+2)
if src[0] == 1 {
dst = append(dst, '-')
}
days := binary.LittleEndian.Uint32(src[1:5])
hours := int64(days)*24 + int64(src[5])
if hours >= 100 {
dst = strconv.AppendInt(dst, hours, 10)
} else {
dst = append(dst, digits10[hours], digits01[hours])View on GitHub (pinned to 03d76c7e07)