How go-sql-driver/mysql Handles Timezone Conversions and DST with `loc` and `parseTime`

The go-sql-driver/mysql delegates all timezone conversions and DST handling to Go's standard time package by passing the loc DSN parameter directly to time.Date() constructors, ensuring accurate IANA timezone database lookups without implementing custom DST logic.

When retrieving DATETIME or TIMESTAMP columns from MySQL using the github.com/go-sql-driver/mysql driver, enabling parseTime=true permits automatic conversion to Go time.Time values. Understanding how the driver manages timezone conversions and DST when the loc parameter is specified requires examining the implementation in dsn.go and utils.go.

How the loc Parameter Configures Timezone Loading

When opening a connection, the driver constructs a Config structure by parsing the DSN string in dsn.go. If the DSN contains a loc parameter (e.g., loc=America/New_York), the code executes a case "loc" branch that loads the IANA timezone location using time.LoadLocation and stores the resulting *time.Location in Config.Loc.

This location configuration—referenced around line 338 in dsn.go during writeDSNParam processing—becomes the authoritative timezone for all temporal conversions on that connection.

Converting MySQL Temporal Data to Go Time Values

During row scanning, the driver transforms raw MySQL date-time values into Go time.Time objects using different parsing strategies for text versus binary protocol results. Both paths utilize the *time.Location stored in the connection's Config.

Text Protocol Implementation

For text-based query results, the parseDateTime function in utils.go (lines 108-179) handles the conversion. This function receives the parsed date-time components (year, month, day, hour, minute, second, nanosecond) from the MySQL string representation, along with the configured location.

The function constructs the final timestamp using:

time.Date(year, month, day, hour, min, sec, nsec, loc)

The loc parameter passed here is exactly the *time.Location loaded from the DSN's loc parameter.

Binary Protocol Implementation

For prepared statements using the binary protocol, parseBinaryDateTime in utils.go (lines 230-274) performs the conversion. After decoding the binary representation of TIME, DATETIME, or TIMESTAMP values, this function similarly invokes time.Date(..., loc) using the location from Config.Loc.

DST Resolution and Edge Case Handling

Because the driver simply forwards the *time.Location to Go's standard time package, all DST rules and timezone offset calculations are delegated to the IANA timezone database. The driver contains no custom DST implementation.

Go's time package automatically applies the correct UTC offset for the specific date, respecting historic and future DST transitions. For edge cases during DST shifts:

  • Ambiguous times occur when clocks fall back and a wall-clock time repeats. Go resolves these to the later UTC offset.
  • Non-existent times occur when clocks spring forward and a wall-clock time is skipped. Go resolves these to the earlier UTC offset.

This ensures consistent timezone conversions and DST handling across all supported locations without driver intervention.

The Deprecated NullTime Exception

A critical limitation exists in nulltime.go: the deprecated NullTime type does not honor the loc DSN parameter. When scanning into NullTime, values are always interpreted as UTC regardless of the configured location. For proper timezone support, use the standard time.Time type with sql.NullTime instead.

Configuration Examples

To enable automatic timezone conversions with DST support, configure both parameters in your DSN:

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "log"
    "time"
)

func main() {
    // Load America/New_York from the IANA database
    dsn := "user:pass@tcp(localhost:3306)/test?parseTime=true&loc=America/New_York"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    var eventTime time.Time
    err := db.QueryRow("SELECT event_at FROM events LIMIT 1").Scan(&eventTime)
    if err != nil {
        log.Fatal(err)
    }
    
    // eventTime contains the correct wall-clock time with DST applied
    log.Printf("Event time: %v", eventTime)
}

DST transitions are handled transparently. For example, inserting a time during a DST gap:

loc, _ := time.LoadLocation("America/New_York")
// March 10, 2024 2:30 AM does not exist (clocks jump from 2:00 to 3:00 AM)
gapTime := time.Date(2024, 3, 10, 2, 30, 0, 0, loc)

_, err := db.Exec("INSERT INTO events (event_at) VALUES (?)", gapTime)
// When retrieved later, the driver resolves the non-existent time to 3:30 AM EDT
// using Go's standard DST resolution rules

Summary

  • The driver loads IANA timezone locations via time.LoadLocation in dsn.go when parsing the loc DSN parameter, storing the result in Config.Loc
  • Both parseDateTime (lines 108-179) and parseBinaryDateTime (lines 230-274) in utils.go pass the configured *time.Location to time.Date()
  • All DST logic and timezone offset calculations are delegated entirely to Go's standard time package
  • The deprecated NullTime type in nulltime.go ignores the loc parameter and treats all values as UTC
  • No custom DST implementation exists in the driver; it relies on the IANA database through Go's standard library

Frequently Asked Questions

Does the go-sql-driver/mysql implement its own DST logic?

No. According to the source code in utils.go, the driver delegates all timezone conversions and DST handling to Go's standard time package. It simply passes the *time.Location configured via the loc DSN parameter to time.Date() constructors, allowing the standard library to apply IANA timezone rules.

What happens when a queried time falls during a DST transition?

Go's time package resolves these edge cases automatically based on the IANA database. For ambiguous times during clock fallbacks, it selects the later UTC offset. For non-existent times during clock spring-forwards, it resolves to the earlier offset. This behavior applies consistently to both text and binary protocol parsing in the driver.

Why are my times always in UTC despite setting loc in the DSN?

If you are scanning into the deprecated NullTime type defined in nulltime.go, the driver explicitly ignores the loc parameter and parses all values as UTC. Switch to the standard time.Time type or sql.NullTime to respect your DSN location configuration.

Which source files contain the timezone conversion logic?

The key files are dsn.go (handling DSN parsing and location loading via time.LoadLocation) and utils.go (containing the parseDateTime and parseBinaryDateTime functions that construct time.Time values using the configured location).

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →