Time and Date Handling in Go

Time and Date Handling in Go

Go’s time package provides comprehensive support for working with dates, times, durations, and time zones. Understanding time handling is crucial for applications that deal with scheduling, logging, and data processing.

Current Time

Getting the current time:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Current time
    now := time.Now()
    fmt.Println("Current time:", now)
    
    // Individual components
    fmt.Println("Year:", now.Year())
    fmt.Println("Month:", now.Month())
    fmt.Println("Day:", now.Day())
    fmt.Println("Hour:", now.Hour())
    fmt.Println("Minute:", now.Minute())
    fmt.Println("Second:", now.Second())
    fmt.Println("Nanosecond:", now.Nanosecond())
    
    // Formatted time
    fmt.Println("Formatted:", now.Format("2006-01-02 15:04:05"))
}

Time Formatting

Go uses a specific date for formatting reference:

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    
    // Common formats
    fmt.Println("RFC3339:", now.Format(time.RFC3339))
    fmt.Println("RFC822:", now.Format(time.RFC822))
    fmt.Println("Kitchen:", now.Format(time.Kitchen))
    
    // Custom formats (use reference date: Mon Jan 2 15:04:05 MST 2006)
    fmt.Println("YYYY-MM-DD:", now.Format("2006-01-02"))
    fmt.Println("DD/MM/YYYY:", now.Format("02/01/2006"))
    fmt.Println("HH:MM:SS:", now.Format("15:04:05"))
    fmt.Println("Long format:", now.Format("Monday, January 2, 2006 at 3:04 PM"))
    
    // Unix timestamp
    fmt.Println("Unix timestamp:", now.Unix())
    fmt.Println("Unix nano:", now.UnixNano())
}

Parsing Time Strings

Converting strings to time objects:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Parse RFC3339
    t1, err := time.Parse(time.RFC3339, "2023-01-15T10:30:00Z")
    if err != nil {
        fmt.Println("Error parsing RFC3339:", err)
        return
    }
    fmt.Println("Parsed RFC3339:", t1)
    
    // Parse custom format
    t2, err := time.Parse("2006-01-02 15:04:05", "2023-12-25 23:59:59")
    if err != nil {
        fmt.Println("Error parsing custom format:", err)
        return
    }
    fmt.Println("Parsed custom:", t2)
    
    // Parse with location
    loc, _ := time.LoadLocation("America/New_York")
    t3, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-07-04 12:00:00", loc)
    if err != nil {
        fmt.Println("Error parsing with location:", err)
        return
    }
    fmt.Println("Parsed with location:", t3)
}

Time Zones

Working with different time zones:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Current time in UTC
    now := time.Now()
    fmt.Println("Local time:", now)
    fmt.Println("UTC time:", now.UTC())
    
    // Load specific time zone
    nyc, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("Error loading location:", err)
        return
    }
    
    // Convert to different time zone
    nycTime := now.In(nyc)
    fmt.Println("New York time:", nycTime)
    
    // Create time in specific location
    tokyo, _ := time.LoadLocation("Asia/Tokyo")
    tokyoTime := time.Date(2023, 1, 1, 12, 0, 0, 0, tokyo)
    fmt.Println("Tokyo time:", tokyoTime)
    
    // List of common locations
    locations := []string{
        "UTC",
        "America/New_York",
        "Europe/London",
        "Asia/Tokyo",
        "Australia/Sydney",
    }
    
    fmt.Println("\nCurrent time in different locations:")
    for _, locName := range locations {
        loc, err := time.LoadLocation(locName)
        if err != nil {
            continue
        }
        fmt.Printf("%s: %s\n", locName, now.In(loc).Format("15:04 MST"))
    }
}

Durations

Working with time durations:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Create durations
    duration1 := time.Hour * 2
    duration2 := time.Minute * 30
    duration3 := time.Second * 45
    
    fmt.Println("2 hours:", duration1)
    fmt.Println("30 minutes:", duration2)
    fmt.Println("45 seconds:", duration3)
    
    // Parse duration strings
    d1, _ := time.ParseDuration("2h30m")
    d2, _ := time.ParseDuration("1h45m30s")
    d3, _ := time.ParseDuration("300ms")
    
    fmt.Println("Parsed durations:")
    fmt.Println("2h30m:", d1)
    fmt.Println("1h45m30s:", d2)
    fmt.Println("300ms:", d3)
    
    // Duration operations
    total := d1 + d2
    fmt.Println("Total duration:", total)
    
    // Convert to different units
    fmt.Println("Hours:", total.Hours())
    fmt.Println("Minutes:", total.Minutes())
    fmt.Println("Seconds:", total.Seconds())
    fmt.Println("Nanoseconds:", total.Nanoseconds())
}

Time Arithmetic

Adding and subtracting time:

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    
    // Add time
    future := now.Add(time.Hour * 24) // 1 day from now
    fmt.Println("Now:", now)
    fmt.Println("Tomorrow:", future)
    
    // Add different durations
    nextWeek := now.AddDate(0, 0, 7)
    nextMonth := now.AddDate(0, 1, 0)
    nextYear := now.AddDate(1, 0, 0)
    
    fmt.Println("Next week:", nextWeek)
    fmt.Println("Next month:", nextMonth)
    fmt.Println("Next year:", nextYear)
    
    // Subtract time
    past := now.Add(-time.Hour * 2) // 2 hours ago
    fmt.Println("2 hours ago:", past)
    
    // Calculate difference
    diff := future.Sub(now)
    fmt.Println("Difference:", diff)
    fmt.Println("Hours until future:", diff.Hours())
    
    // Compare times
    if future.After(now) {
        fmt.Println("Future is after now")
    }
    
    if past.Before(now) {
        fmt.Println("Past is before now")
    }
    
    if now.Equal(now) {
        fmt.Println("Times are equal")
    }
}

Timers and Tickers

Using timers for one-time events and tickers for repeated events:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Timer - fires once after duration
    timer := time.NewTimer(2 * time.Second)
    fmt.Println("Timer started, waiting...")
    
    <-timer.C // Wait for timer
    fmt.Println("Timer fired!")
    
    // Reset timer
    timer.Reset(1 * time.Second)
    <-timer.C
    fmt.Println("Timer fired again!")
    
    // Ticker - fires repeatedly
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()
    
    go func() {
        for i := 0; i < 5; i++ {
            <-ticker.C
            fmt.Println("Tick", i+1)
        }
        ticker.Stop()
    }()
    
    // Wait for ticker to finish
    time.Sleep(3 * time.Second)
}

Sleeping and Waiting

Pause execution:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Starting...")
    
    // Sleep for 1 second
    time.Sleep(time.Second)
    fmt.Println("1 second passed")
    
    // Sleep for duration
    duration, _ := time.ParseDuration("500ms")
    time.Sleep(duration)
    fmt.Println("500ms passed")
    
    // Wait until specific time
    targetTime := time.Now().Add(2 * time.Second)
    <-time.After(time.Until(targetTime))
    fmt.Println("Reached target time")
}

Measuring Execution Time

Benchmarking code execution:

package main

import (
    "fmt"
    "time"
)

func slowOperation() {
    time.Sleep(100 * time.Millisecond)
}

func fastOperation() {
    sum := 0
    for i := 0; i < 1000000; i++ {
        sum += i
    }
}

func measureExecutionTime(fn func(), name string) {
    start := time.Now()
    fn()
    elapsed := time.Since(start)
    fmt.Printf("%s took %v\n", name, elapsed)
}

func main() {
    measureExecutionTime(slowOperation, "Slow operation")
    measureExecutionTime(fastOperation, "Fast operation")
    
    // More precise measurement
    var total time.Duration
    iterations := 100
    
    for i := 0; i < iterations; i++ {
        start := time.Now()
        fastOperation()
        total += time.Since(start)
    }
    
    average := total / time.Duration(iterations)
    fmt.Printf("Average execution time: %v\n", average)
}

Time in JSON

Handling time in JSON serialization:

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Event struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Timestamp time.Time `json:"timestamp"`
}

func main() {
    event := Event{
        ID:        1,
        Name:      "System Startup",
        Timestamp: time.Now(),
    }
    
    // Marshal to JSON (time becomes RFC3339 string)
    jsonData, err := json.MarshalIndent(event, "", "  ")
    if err != nil {
        fmt.Println("Error marshaling:", err)
        return
    }
    
    fmt.Println("JSON:")
    fmt.Println(string(jsonData))
    
    // Unmarshal from JSON
    jsonString := `{
        "id": 2,
        "name": "User Login",
        "timestamp": "2023-01-15T10:30:00Z"
    }`
    
    var decodedEvent Event
    err = json.Unmarshal([]byte(jsonString), &decodedEvent)
    if err != nil {
        fmt.Println("Error unmarshaling:", err)
        return
    }
    
    fmt.Printf("Decoded event: %+v\n", decodedEvent)
    fmt.Println("Timestamp:", decodedEvent.Timestamp.Format("2006-01-02 15:04:05 MST"))
}

Custom Time Formatting

For custom JSON time formats:

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type CustomTime struct {
    time.Time
}

func (ct CustomTime) MarshalJSON() ([]byte, error) {
    return json.Marshal(ct.Time.Format("2006-01-02 15:04:05"))
}

func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var s string
    if err := json.Unmarshal(data, &s); err != nil {
        return err
    }
    
    t, err := time.Parse("2006-01-02 15:04:05", s)
    if err != nil {
        return err
    }
    
    ct.Time = t
    return nil
}

type Record struct {
    ID   int        `json:"id"`
    Time CustomTime `json:"time"`
}

func main() {
    record := Record{
        ID:   1,
        Time: CustomTime{time.Now()},
    }
    
    jsonData, _ := json.MarshalIndent(record, "", "  ")
    fmt.Println("Custom JSON:")
    fmt.Println(string(jsonData))
}

Best Practices

  1. Use UTC for storage: Store times in UTC to avoid timezone issues
  2. Be explicit with locations: Don’t rely on local timezone
  3. Use time.Time for all time values: Avoid storing as strings or ints
  4. Handle parsing errors: Always check for errors when parsing
  5. Use consistent formats: Pick one format and stick with it
  6. Consider monotonic clocks: For measuring elapsed time
  7. Avoid time zones in calculations: Convert to UTC first
  8. Use time.After for timeouts: Instead of time.Sleep in select statements
  9. Be careful with daylight saving: Time zones can be tricky
  10. Test time-dependent code: Use fixed times in tests

Go’s time package is powerful and well-designed. With proper understanding of time zones, formatting, and arithmetic, you can handle temporal data effectively in your applications.

For more on JSON handling, check our JSON tutorial. If you need to work with files, see the file I/O tutorial.

Last updated on