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

  1. The message itself is generic; enable verbose logging or SELECT CAST(col AS CHAR) to see the actual offending value.
  2. Validate the column type — scan VARCHAR/CHAR date-like columns into a string and parse with time.Parse, handling its error explicitly.
  3. Bypass any proxy/tunnel and reproduce against MySQL directly to confirm the server sends clean digits.
  4. 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

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


AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04). Data as JSON: /data/errors/e674c43ac7ae2dd9.json. Report an issue: GitHub.