go-sql-driver/mysql · error

year is not in the range [1, 9999]: {year}

Error message

year is not in the range [1, 9999]: {year}

What it means

Thrown by appendDateTime (utils.go:278) when serializing a Go time.Time to the textual form MySQL expects, if the year is < 1 or > 9999. MySQL DATE/DATETIME cannot represent years outside [1, 9999], so the driver refuses to send such a value rather than produce a value the server would reject. This is a CLIENT-side guard, fired when binding a Go time.Time as a parameter.

Source

Thrown at utils.go:278

			int(data[6]),                              // seconds
			int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
			loc,
		), nil
	}
	return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
}

func appendDateTime(buf []byte, t time.Time, timeTruncate time.Duration) ([]byte, error) {
	if timeTruncate > 0 {
		t = t.Truncate(timeTruncate)
	}

	year, month, day := t.Date()
	hour, min, sec := t.Clock()
	nsec := t.Nanosecond()

	if year < 1 || year > 9999 {
		return buf, errors.New("year is not in the range [1, 9999]: " + strconv.Itoa(year)) // use errors.New instead of fmt.Errorf to avoid year escape to heap
	}
	year100 := year / 100
	year1 := year % 100

	var localBuf [len("2006-01-02T15:04:05.999999999")]byte // does not escape
	localBuf[0], localBuf[1], localBuf[2], localBuf[3] = digits10[year100], digits01[year100], digits10[year1], digits01[year1]
	localBuf[4] = '-'
	localBuf[5], localBuf[6] = digits10[month], digits01[month]
	localBuf[7] = '-'
	localBuf[8], localBuf[9] = digits10[day], digits01[day]

	if hour == 0 && min == 0 && sec == 0 && nsec == 0 {
		return append(buf, localBuf[:10]...), nil
	}

	localBuf[10] = ' '
	localBuf[11], localBuf[12] = digits10[hour], digits01[hour]
	localBuf[13] = ':'

View on GitHub (pinned to c426bd9379)

Solutions

  1. Before binding, validate the time.Time: if t.Year() < 1 || t.Year() > 9999, substitute NULL or a sentinel and skip the value.
  2. Find where the bogus year originates (parsing, arithmetic, default zero value) and fix the upstream producer.
  3. If you legitimately need to store NULL, bind sql.NullTime or nil instead of an out-of-range time.Time.
  4. Add a unit test asserting all inserted dates fall in [1, 9999].

Example fix

// before
db.Exec("INSERT INTO t(created) VALUES(?)", t)

// after: clamp or null out-of-range years
var arg any = t
if t.Year() < 1 || t.Year() > 9999 {
    arg = nil // store NULL
}
db.Exec("INSERT INTO t(created) VALUES(?)", arg)
Defensive patterns

Strategy: validation

Validate before calling

// guard any time.Time before binding it as a MySQL parameter
func mysqlTimeArg(t time.Time) any {
    y := t.Year()
    if y < 1 || y > 9999 {
        return nil // store NULL instead
    }
    return t
}
db.Exec("INSERT INTO t(created) VALUES(?)", mysqlTimeArg(t))

Type guard

// isMySQLOKTime reports whether t fits MySQL's [1,9999] year range
func isMySQLOKTime(t time.Time) bool {
    y := t.Year()
    return y >= 1 && y <= 9999
}

Prevention

When it happens

Trigger: Executing a parameterized query with a time.Time argument (INSERT/UPDATE/WHERE on a DATE/DATETIME/TIMESTAMP column) whose Year() falls outside [1, 9999]. Common offenders: the Go zero value time.Time{} (year 1, actually OK) but far-future dates, year 0, or negative years; dates produced by bad arithmetic or parsed from malformed input.

Common situations: Inserting time.Time{} that was never set and got manipulated; dates from external systems with bogus years (e.g. 0000, 10000+); unit tests with placeholder dates like time.Date(99999, ...); converting a Unix timestamp that overflowed into a wild year.

Related errors


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