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
- Before binding, validate the time.Time: if t.Year() < 1 || t.Year() > 9999, substitute NULL or a sentinel and skip the value.
- Find where the bogus year originates (parsing, arithmetic, default zero value) and fix the upstream producer.
- If you legitimately need to store NULL, bind sql.NullTime or nil instead of an out-of-range time.Time.
- 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
- Always initialize time.Time values; never bind the zero value blindly.
- Wrap every time.Time bind in a helper that clamps/nulls out-of-range years.
- Validate timestamps from external systems (Unix epoch math, parsing) before storing.
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
- invalid timeTruncate value: %v, error: %w
- logger is nil
- key '%s' is reserved
- invalid time bytes: %s
- not [0-9]
AI-assisted analysis of go-sql-driver/mysql@c426bd9379 (2026-08-04).
Data as JSON: /data/errors/b92efed0f57923fd.json.
Report an issue: GitHub.