go-sql-driver/mysql · error
not [0-9]
Error message
not [0-9]
What it means
Thrown by bToi (utils.go:225) — the lowest-level byte-to-digit helper — when a byte expected to be an ASCII digit [0-9] is not. It is the underlying cause behind parseByteYear, parseByte2Digits, and parseByteNanoSec, so a caller sees it as the error from any numeric field of a textual datetime (year, month, day, hour, minute, second, fractional digits).
Source
Thrown at utils.go:225
func parseByteNanoSec(b []byte) (int, error) {
ns, digit := 0, 100000 // max is 6-digits
for i := range b {
v, err := bToi(b[i])
if err != nil {
return 0, err
}
ns += v * digit
digit /= 10
}
// nanoseconds has 10-digits. (needs to scale digits)
// 10 - 6 = 4, so we have to multiple 1000.
return ns * 1000, nil
}
func bToi(b byte) (int, error) {
if b < '0' || b > '9' {
return 0, errors.New("not [0-9]")
}
return int(b - '0'), nil
}
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
switch num {
case 0:
return time.Time{}, nil
case 4:
return time.Date(
int(binary.LittleEndian.Uint16(data[:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
0, 0, 0, 0,
loc,
), nil
case 7:
return time.Date(View on GitHub (pinned to c426bd9379)
Solutions
- The message itself is generic; enable verbose logging or SELECT CAST(col AS CHAR) to see the actual offending value.
- Validate the column type — scan VARCHAR/CHAR date-like columns into a string and parse with time.Parse, handling its error explicitly.
- Bypass any proxy/tunnel and reproduce against MySQL directly to confirm the server sends clean digits.
- Clean the source data if it genuinely contains non-digit characters in date fields.
Example fix
// before
var t time.Time
err := db.QueryRow("SELECT d FROM t").Scan(&t)
// after: parse defensively with a clear layout
var raw string
err := db.QueryRow("SELECT CAST(d AS CHAR) FROM t").Scan(&raw)
var t time.Time
if err == nil {
if t, err = time.ParseInLocation("2006-01-02", raw, time.Local); err != nil {
// raw contains a non-digit; report it
}
} Defensive patterns
Strategy: try-catch
Try / catch
var t time.Time
if err := rows.Scan(&t); err != nil {
if err.Error() == "not [0-9]" || strings.Contains(err.Error(), "not [0-9]") {
// a non-digit byte was in the textual datetime; inspect via CAST(col AS CHAR)
} else {
return err
}
} Prevention
- Don't scan alphanumeric or garbage VARCHAR data into time.Time; clean it first.
- Read suspect columns as a string and validate with a regexp like ^[0-9]{4}-[0-9]{2}-[0-9]{2} before parsing.
- Keep the wire path clean of proxies that corrupt bytes.
When it happens
Trigger: Parsing a textual datetime where one of the digit-group bytes is non-numeric — e.g. '20A0-01-01' (year has 'A'), '2020-0a-01' (month), or fractional '00:00:00.00000x'. Reachable from any time.Time scan of a DATE/DATETIME/TIMESTAMP column.
Common situations: Corrupted/partial bytes from a proxy or flaky network; a VARCHAR column containing alphanumeric garbage being scanned as time.Time; a non-MySQL server emitting non-digit characters; an encoding mismatch (UTF-8 multibyte char landing in a date field).
Related errors
- invalid time bytes: %s
- invalid DATETIME packet length %d
- illegal %s length %d
- illegal %s packet length %d
- bad value for field: `%c`
AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04).
Data as JSON: /data/errors/e674c43ac7ae2dd9.json.
Report an issue: GitHub.