MySQL Driver parseTime=false Behavior for DATE and DATETIME Columns
When parseTime=false, the go-sql-driver/mysql driver returns DATE and DATETIME columns as raw []byte slices instead of time.Time objects, leaving the MySQL wire format bytes unchanged for application-level handling.
The parseTime DSN parameter in the go-sql-driver/mysql repository controls how temporal columns are materialized in Go. When set to false (the default), the driver bypasses time parsing entirely for DATE, DATETIME, TIMESTAMP, and NEWDATE columns, returning the raw server response as byte slices that can be scanned into []byte, string, or sql.RawBytes.
How parseTime=false Affects Date Column Handling
The driver stores the parseTime flag in the DSN configuration structure (Config.ParseTime in dsn.go). During row fetching, the text-row reader in packets.go evaluates this flag on a per-column basis to determine whether to invoke time parsing or pass through raw bytes.
When the driver encounters a temporal column type (TIMESTAMP, DATETIME, DATE, or NEWDATE), it checks mc.parseTime at packets.go lines 69-78:
-
If
parseTime=true: The driver callsparseDateTime(implemented inutils.golines 108-177) to convert the MySQL byte representation into atime.Timevalue. This routine also maps the MySQL "zero-date" (0000-00-00 00:00:00) to the Go zero valuetime.Time{}. -
If
parseTime=false: The driver assigns the raw byte slice ([]byte) directly to the destination value, bypassingparseDateTimeentirely. The bytes remain in the MySQL wire format (e.g.,"2023-01-15"forDATEor"2023-01-15 12:34:56"forDATETIME).
Source Code Implementation Details
DSN Configuration (dsn.go)
The ParseTime boolean field in the Config struct captures the DSN parameter. This value propagates to the connection struct (mysqlConn) and is accessed during row scanning operations.
Row Reading Logic (packets.go)
In the text protocol row reader, the driver iterates over result set columns. For temporal types, the critical branch at lines 69-78 determines the conversion path:
// Simplified logic from packets.go
case fieldTypeTimestamp, fieldTypeDatetime, fieldTypeDate, fieldTypeNewdate:
if mc.parseTime {
dest[i], err = parseDateTime(string(buf), mc.cfg.Loc)
} else {
dest[i] = buf // raw []byte
}
Date Parsing Routine (utils.go)
The parseDateTime function (lines 108-177) handles string-to-time conversion, including location awareness and zero-date normalization. When parseTime=false, this function is never invoked for temporal columns, eliminating parsing overhead and zero-date special handling.
Practical Behavior and Edge Cases
Zero-Date Handling
With parseTime=false, the MySQL zero-date (0000-00-00 00:00:00) is returned as the literal byte string "0000-00-00 00:00:00" (or "0000-00-00" for DATE). The driver does not convert this to Go's time.Time{} zero value.
Type Scanning Implications
When parseTime=false, destination variables must match the raw byte type:
- Valid scans:
[]byte,string,sql.RawBytes - Invalid scans:
time.Time(results in a conversion error: "unsupported driver -> value type []byte")
Attempting to scan a DATE column into a time.Time variable while parseTime=false produces an error similar to:
sql: Scan error on column index 0, name "d": converting driver.Value type []byte ("2023-01-15") to a time.Time: unsupported driver -> value type []byte
Performance Considerations
Disabling time parsing removes the overhead of parseDateTime, which involves string parsing, validation, and location handling. This can improve throughput for applications that do not require time.Time objects or that perform their own date parsing.
Code Example: parseTime=false in Practice
The following example demonstrates the behavior when querying DATE and DATETIME columns with parseTime=false:
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// DSN with parseTime disabled
dsn := "user:password@tcp(127.0.0.1:3306)/testdb?parseTime=false"
db, err := sql.Open("mysql", dsn)
if err != nil { log.Fatal(err) }
defer db.Close()
// Assume a table: CREATE TABLE dates (d DATE, dt DATETIME);
row := db.QueryRow("SELECT d, dt FROM dates LIMIT 1")
// Scan into raw []byte – works
var rawDate, rawDateTime []byte
if err := row.Scan(&rawDate, &rawDateTime); err != nil {
log.Fatalf("scan raw bytes: %v", err)
}
fmt.Printf("raw DATE: %s\n", rawDate) // e.g. "2023-01-15"
fmt.Printf("raw DATETIME: %s\n", rawDateTime) // e.g. "2023-01-15 12:34:56"
// Scan directly into time.Time – fails because driver returned []byte
row = db.QueryRow("SELECT d FROM dates LIMIT 1")
var t time.Time
if err := row.Scan(&t); err != nil {
// Example error:
// sql: Scan error on column index 0, name "d": converting driver.Value type []byte ("2023-01-15") to a time.Time: unsupported driver -> value type []byte
fmt.Printf("expected error scanning into time.Time: %v\n", err)
}
}
Changing the DSN to parseTime=true causes the driver to invoke parseDateTime from utils.go, returning time.Time values and mapping zero-dates to time.Time{}.
Summary
- Raw bytes returned: With
parseTime=false, the driver returnsDATE,DATETIME,TIMESTAMP, andNEWDATEcolumns as[]byteslices in MySQL wire format. - No time.Time conversion: The
parseDateTimefunction inutils.gois bypassed, eliminating parsing overhead and zero-date normalization. - Scanning requirements: Destination variables must be
[]byte,string, orsql.RawBytes; scanning intotime.Timeproduces a conversion error. - Zero-date literal: MySQL zero-dates (
0000-00-00) are returned as literal byte strings rather than Go's zerotime.Timevalue.
Frequently Asked Questions
What happens if I scan a DATE into time.Time with parseTime=false?
The scan operation fails with a conversion error. Because packets.go returns the column as []byte when parseTime=false, the database/sql package cannot automatically convert that byte slice into a time.Time struct. You must scan into []byte or string first, then manually parse the value.
How does parseTime=false handle MySQL zero dates?
With parseTime=false, the driver does not invoke the zero-date detection logic in utils.go. Instead, the literal byte string "0000-00-00" (for DATE) or "0000-00-00 00:00:00" (for DATETIME) is returned unchanged. This differs from parseTime=true, which maps these values to time.Time{}.
Can I convert the raw bytes to time.Time manually?
Yes. When parseTime=false, you receive the date as a []byte or string (e.g., "2023-01-15 12:34:56"). You can use time.Parse("2006-01-02 15:04:05", string(rawBytes)) to convert it to time.Time. This allows custom location handling or alternative date layouts without driver-level configuration.
Is parseTime=false faster than parseTime=true?
Generally, yes. Disabling parseTime avoids the overhead of parseDateTime in utils.go, which performs string parsing, validation, and location-aware conversion. For high-throughput applications that do not need time.Time objects or that handle date parsing independently, parseTime=false reduces CPU usage per row fetched.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →