Time Formatting / Parsing
time.Format with Go's reference time, time.Parse, time.RFC3339 and other constants, and location-aware parsing.
Go formats and parses time using a reference time rather than format codes. The reference time is Mon Jan 2 15:04:05 MST 2006 - a specific moment whose components form a mnemonic: month=1, day=2, hour=3, minute=4, second=5, year=6.
Format a time.Time by constructing a layout string from the reference time's components. time.RFC3339 and similar constants provide commonly used layouts. Custom layouts are built from the same reference components.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2024, 3, 15, 14, 30, 0, 0, time.UTC)
// Predefined layouts
fmt.Println(t.Format(time.RFC3339)) // 2024-03-15T14:30:00Z
fmt.Println(t.Format(time.RFC1123)) // Fri, 15 Mar 2024 14:30:00 UTC
fmt.Println(t.Format(time.DateOnly)) // 2024-03-15
fmt.Println(t.Format(time.TimeOnly)) // 14:30:00
// Custom layouts use the reference time components
fmt.Println(t.Format("January 2, 2006")) // March 15, 2024
fmt.Println(t.Format("02 Jan 2006 15:04")) // 15 Mar 2024 14:30
fmt.Println(t.Format("3:04 PM on Monday")) // 2:30 PM on Friday
}Parse a time string with time.Parse(layout, value). The layout must match the reference time pattern. time.ParseInLocation parses relative to a specific timezone to avoid silent UTC interpretation.
package main
import (
"fmt"
"time"
)
func main() {
// Parse RFC3339 (always includes timezone offset)
t, err := time.Parse(time.RFC3339, "2024-03-15T14:30:00Z")
if err != nil {
panic(err)
}
fmt.Println(t) // 2024-03-15 14:30:00 +0000 UTC
// Parse with a custom layout
t2, err := time.Parse("January 2, 2006", "March 15, 2024")
if err != nil {
panic(err)
}
fmt.Println(t2) // 2024-03-15 00:00:00 +0000 UTC
// Parse in a specific location
loc, _ := time.LoadLocation("America/New_York")
t3, _ := time.ParseInLocation("2006-01-02 15:04", "2024-03-15 14:30", loc)
fmt.Println(t3) // 2024-03-15 14:30:00 -0400 EDT
}Convert between timezones with t.In(loc). time.UTC and time.Local are pre-loaded locations. Use time.LoadLocation for named IANA timezone strings like "America/Los_Angeles".
package main
import (
"fmt"
"time"
)
func main() {
utc := time.Date(2024, 3, 15, 18, 0, 0, 0, time.UTC)
fmt.Println("UTC:", utc.Format(time.RFC3339))
ny, _ := time.LoadLocation("America/New_York")
fmt.Println("NYC:", utc.In(ny).Format(time.RFC3339))
tokyo, _ := time.LoadLocation("Asia/Tokyo")
fmt.Println("Tokyo:", utc.In(tokyo).Format(time.RFC3339))
// Format for display without timezone info
fmt.Println(utc.Format("Mon, 02 Jan 2006 15:04:05"))
}In production
Go's reference-time format string is the most surprising API in the stdlib - the numbers (1 2 3 4 5 6 7) correspond to month, day, hour, minute, second, year, timezone offset. Memorize the mnemonic or keep it in a comment near your format strings. Always parse user-supplied time strings with time.ParseInLocation using an explicit time.Location - time.Parse with a layout that lacks timezone info (like "2006-01-02") interprets the result as UTC, which silently converts a user's "today in Tokyo" to yesterday in UTC. Always call .UTC() after parsing before storing. When accepting times from external APIs, validate that the string is RFC3339 - it is the only format that is unambiguous and includes timezone offset. Log and return clear errors when parsing fails; a zero time.Time{} is January 1, year 1, which will produce confusing timestamps if silently accepted.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free