How to format time in Go/Golang

Viewed: 71258 times

Go uses a special "magic" reference time that might seem weird at first:

The Magic Reference Time is:

01/02 03:04:05PM 2006 MST

Or put another way:

January 2, 2006 at 3:04:05 PM MST

Here's the genius part - the numbers in this date line up in order:

Let me show you with a super simple cheat sheet:


package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // SUPER EASY CHEAT SHEET!

    // Just remember: 1 2 3 4 5 6
    fmt.Println(now.Format("1"))                    // => 1 (month)
    fmt.Println(now.Format("2"))                    // => 2 (day)
    fmt.Println(now.Format("3"))                    // => 3 (hour)
    fmt.Println(now.Format("4"))                    // => 4 (minute)
    fmt.Println(now.Format("5"))                    // => 5 (second)
    fmt.Println(now.Format("6"))                    // => 6 (year)

    // Real-world examples:

    // Want date like "2024-03-27"?
    fmt.Println(now.Format("2006-01-02"))

    // Want time like "15:04"?
    fmt.Println(now.Format("15:04"))

    // Want something like "Mar 27, 2024 3:04pm"?
    fmt.Println(now.Format("Jan 2, 2006 3:04pm"))

    // Want milliseconds? Add .000
    fmt.Println(now.Format("15:04:05.000"))

    // Common formats people use:
    formats := map[string]string{
        "Simple date":       "2006-01-02",          // => 2024-03-27
        "Simple time":       "15:04",               // => 13:45
        "Date and time":     "2006-01-02 15:04:05", // => 2024-03-27 13:45:30
        "Pretty date":       "January 2, 2006",     // => March 27, 2024
        "Short date":        "Jan 2",               // => Mar 27
        "Kitchen time":      "3:04PM",              // => 1:45PM
        "File safe":         "20060102-150405",     // => 20240327-134530
    }

    // Print them all
    for name, format := range formats {
        fmt.Printf("%s: %s\n", name, now.Format(format))
    }
}
            

The trick to remember:

  1. It's all based on one specific time: January 2, 2006 at 3:04:05 PM
  2. Just write the date/time exactly how you want it to look, but use the reference time's numbers

Some easy examples:


// Need just the month and day?
"01-02"                 // => "03-27"

// Need year, month, day?
"2006-01-02"            // => "2024-03-27"

// Need hours and minutes in 24h format?
"15:04"                 // => "13:45"

// Need everything?
"2006-01-02 15:04:05"   // => "2024-03-27 13:45:30"
                

Pro Tips:

Think of it like a template - you're just showing Go how you want the date to look, using that special reference date as your model!

Now let's see how to handle timezones:

If we don't pay attention to the timezones, we can easily get wrong results.


package main

import (
    "fmt"
    "time"
)

func main() {
    // WRONG WAY (dangerous on servers!):
    localTime := time.Now() // Don't use this alone on servers!

    // RIGHT WAY (safe for servers):
    utcTime := time.Now().UTC()

    // Let's see different ways to handle timezones:

    // 1. Always store in UTC (RECOMMENDED FOR SERVERS)
    now := time.Now().UTC()

    // Format with UTC explicitly
    fmt.Println(now.Format("2006-01-02 15:04:05 MST"))   // Shows UTC
    fmt.Println(now.Format("2006-01-02 15:04:05 -0700")) // Shows +0000
    fmt.Println(now.Format(time.RFC3339))                // ISO8601/RFC3339 format

    // 2. Loading specific timezones (when you need to)
    nyc, _ := time.LoadLocation("America/New_York")
    baku, _ := time.LoadLocation("Asia/Baku")

    // Convert UTC to specific timezone
    nycTime := now.In(nyc)
    bakuTime := now.In(baku)

    fmt.Printf("UTC:   %s\n", now.Format(time.RFC3339))
    fmt.Printf("NYC:   %s\n", nycTime.Format(time.RFC3339))
    fmt.Printf("Baku: %s\n", bakuTime.Format(time.RFC3339))

    // 3. Parsing times with timezones
    // Always parse assuming UTC unless timezone is specified
    const format = "2006-01-02 15:04:05"

    // SAFE way to parse times without timezone
    timeStr := "2024-03-27 15:04:05"
    parsedTime, _ := time.Parse(format, timeStr)         // Assumes UTC

    // SAFE way to parse times with specific timezone
    parsedWithZone, _ := time.ParseInLocation(format, timeStr, nyc)

    // 4. Common patterns for server applications
    serverPatterns := map[string]string{
        // Store this format in database (UTC):
        "database": "2006-01-02 15:04:05",

        // Use this for JSON/API responses (includes timezone):
        "api": time.RFC3339,

        // Use this for logs:
        "logs": "2006-01-02 15:04:05 -0700",
    }

    for name, pattern := range serverPatterns {
        fmt.Printf("%s: %s\n", name, now.Format(pattern))
    }
}
        

Here are the GOLDEN RULES for server timezone handling:

1. ALWAYS Store in UTC:


// RIGHT WAY - Store this in your database
timestamp := time.Now().UTC()

// WRONG WAY - Don't store local time
timestamp := time.Now() // Dangerous! Server timezone might change!
        

2. ALWAYS Parse Without Timezone as UTC:


// RIGHT WAY - Parse times without timezone
timeStr := "2024-03-27 15:04:05"
safeTime, _ := time.Parse("2006-01-02 15:04:05", timeStr) // Assumes UTC

// WRONG WAY - Don't use ParseInLocation unless you specifically need a timezone
dangerousTime, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local)
        

3. ALWAYS Use RFC3339 for APIs:


// RIGHT WAY - Use RFC3339 for API responses
type APIResponse struct {
    Timestamp string `json:"timestamp"`
}

response := APIResponse{
    Timestamp: time.Now().UTC().Format(time.RFC3339),
}
// Will output something like: "2024-03-27T15:04:05Z"
        

4. Converting to User's Timezone (when needed):


// RIGHT WAY - Convert UTC to user's timezone only for display
func GetUserTime(utcTime time.Time, userTimezone string) (time.Time, error) {
    location, err := time.LoadLocation(userTimezone)
    if err != nil {
        return time.Time{}, err
    }
    return utcTime.In(location), nil
}

// Usage:
utcTime := time.Now().UTC()
userTime, err := GetUserTime(utcTime, "Asia/Baku")
if err != nil {
    log.Printf("Invalid timezone: %v", err)
}
        

Common Timezone Formats:


// Common formats with timezone information
formats := map[string]string{
    "UTC ISO8601": time.RFC3339,                // "2024-03-27T15:04:05Z"
    "With Zone":   "2006-01-02 15:04:05 MST",   // "2024-03-27 15:04:05 UTC"
    "With Offset": "2006-01-02 15:04:05 -0700", // "2024-03-27 15:04:05 +0000"
}
        

IMPORTANT TIPS:

Official documentation for time in Go: https://pkg.go.dev/time