O pacote time é uma das bibliotecas padrão mais importantes do Go, fornecendo funcionalidades para medir e exibir o tempo. Seja para registrar logs, agendar tarefas, calcular intervalos ou lidar com fusos horários, o pacote time oferece uma API limpa e eficiente. Nesta aula, vamos mergulhar nos conceitos fundamentais e nas práticas comuns desse pacote, preparando você para usar o tempo em seus programas Go com confiança.

Vamos começar entendendo os dois pilares do pacote: o tipo Time, que representa um instante específico, e o tipo Duration, que representa um intervalo de tempo. Em seguida, exploraremos a formatação de datas, que em Go usa um layout de referência peculiar, e depois veremos como criar timers e tickers para executar ações em momentos futuros ou repetidamente. Por fim, abordaremos o tratamento de fusos horários, essencial para aplicações globais.

Time e Duration

O tipo time.Time representa um ponto no tempo, com precisão de nanossegundos. Você pode obter o horário atual com time.Now(), que retorna um Time no fuso local. O tipo time.Duration é uma duração de tempo, representada internamente como um inteiro de 64 bits em nanossegundos. Isso permite operações aritméticas diretas e comparações.

Para criar um Time específico, usamos time.Date(), que recebe ano, mês, dia, hora, minuto, segundo, nanossegundo e um *time.Location. Já para criar uma Duration, usamos constantes como time.Second, time.Minute, etc., ou a função time.ParseDuration para strings como "2h45m".

Exemplo básico:

package main

import (
    "fmt"
    "time"
)

func main() {
    agora := time.Now()
    fmt.Println("Agora:", agora)

    // Criando uma data específica (UTC)
    data := time.Date(2025, time.January, 1, 12, 0, 0, 0, time.UTC)
    fmt.Println("Data:", data)

    // Durações
    d := 2*time.Hour + 30*time.Minute
    fmt.Println("Duração:", d)
    fmt.Println("Em minutos:", d.Minutes())

    // Somando tempo
    futuro := agora.Add(d)
    fmt.Println("Futuro:", futuro)

    // Diferença entre tempos
    diff := futuro.Sub(agora)
    fmt.Println("Diferença:", diff)
}

O método Sub retorna uma Duration representando a diferença entre dois tempos. Já Add soma uma duração a um tempo. É importante notar que Time é imutável; as operações retornam novos valores.

Formatação (layout de referência)

Go adota uma abordagem única para formatar e parsear datas: em vez de usar códigos como %Y ou %m, você especifica um layout usando uma data de referência específica. Essa data é Mon Jan 2 15:04:05 MST 2006 (que corresponde a 01/02/03 04:05:06 PM '06 -0700). Você deve usar essa data como modelo, substituindo as partes que deseja exibir.

Por exemplo, para formatar uma data no formato brasileiro (dd/mm/aaaa), usamos o layout "02/01/2006". Para incluir a hora, "02/01/2006 15:04". O método Format aplica o layout ao Time.

Exemplo:

package main

import (
    "fmt"
    "time"
)

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

    // Layouts comuns
    fmt.Println("Padrão RFC3339:", agora.Format(time.RFC3339))
    fmt.Println("Brasil:", agora.Format("02/01/2006 15:04:05"))
    fmt.Println("Ano-mês-dia:", agora.Format("2006-01-02"))

    // Parse de string para Time
    dataStr := "2025-01-02 15:04:05"
    t, err := time.Parse("2006-01-02 15:04:05", dataStr)
    if err != nil {
        panic(err)
    }
    fmt.Println("Parseado:", t)
}

É fundamental memorizar a data de referência: 01 para dia, 02 para mês, 2006 para ano, 15 para hora no formato 24h, 04 para minutos, 05 para segundos, e MST para fuso. No Go, o mês é representado por January ou Jan, e o dia da semana por Monday ou Mon.

Dica: use as constantes pré-definidas do pacote time como time.RFC3339, time.RFC1123, etc., para formatos comuns.

Timers e tickers

O pacote time oferece duas ferramentas para executar código em momentos futuros: time.Timer e time.Ticker. Um Timer dispara uma única vez após um intervalo especificado, enquanto um Ticker dispara repetidamente em intervalos regulares.

Para criar um timer, usamos time.NewTimer(d), que retorna um ponteiro para Timer com um canal C que recebe um valor Time quando o timer expira. Você pode aguardar no canal ou usar o método Stop para cancelar antes.

Para um ticker, usamos time.NewTicker(d), que também retorna um ponteiro com um canal C. O ticker envia o tempo atual a cada intervalo até ser parado com Stop.

Exemplo prático:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Timer de 1 segundo
    timer := time.NewTimer(1 * time.Second)
    <-timer.C
    fmt.Println("Timer disparou!")

    // Ticker a cada 500ms, para após 3 ticks
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()

    for i := 0; i < 3; i++ {
        t := <-ticker.C
        fmt.Println("Tick em", t)
    }
    fmt.Println("Ticker parado")
}

Timers e tickers são amplamente usados em concorrência, timeouts e tarefas periódicas. Lembre-se de sempre chamar Stop quando não precisar mais deles para liberar recursos.

Fusos

O Go trata fusos horários através do tipo time.Location. Você pode obter um fuso usando time.LoadLocation com o nome da zona (ex.: "America/Sao_Paulo", "UTC"). O pacote time já carrega a base de dados de fusos do sistema ou a embutida no binário.

Para converter um Time de um fuso para outro, usamos o método In(loc), que retorna um novo Time representando o mesmo instante, mas no fuso especificado. É importante lembrar que Time internamente guarda o instante absoluto (Unix epoch), e o fuso afeta apenas a representação.

Exemplo:

package main

import (
    "fmt"
    "time"
)

func main() {
    // Horário atual em UTC
    agoraUTC := time.Now().UTC()
    fmt.Println("UTC:", agoraUTC)

    // Carregar fuso de São Paulo
    sp, err := time.LoadLocation("America/Sao_Paulo")
    if err != nil {
        panic(err)
    }
    agoraSP := agoraUTC.In(sp)
    fmt.Println("São Paulo:", agoraSP)

    // Criar data em um fuso específico
    dataSP := time.Date(2025, time.January, 1, 12, 0, 0, 0, sp)
    fmt.Println("Data em SP:", dataSP)
    fmt.Println("Em UTC:", dataSP.UTC())
}

Para obter o fuso local, use time.Local, que é o fuso configurado no sistema. Também é possível criar um fuso fixo com time.FixedZone, útil para testes ou fusos sem banco de dados.

Boas Práticas e Observações Finais

Ao trabalhar com tempo em Go, algumas práticas ajudam a evitar erros comuns:

  • Prefira armazenar timestamps em UTC e converter apenas para exibição.
  • Use time.Time como valor, nunca como ponteiro, a menos que seja estritamente necessário.
  • Em APIs, evite passar time.Duration em milissegundos; use a própria constante ou time.ParseDuration.
  • Para medir tempo de execução, use time.Since(t) em vez de fazer subtração manual.
  • Quando usar timers e tickers, sempre chame Stop para evitar vazamentos de goroutines.

O pacote time é vasto, mas dominar esses conceitos básicos já permite resolver a maioria dos problemas do dia a dia. Explore a documentação oficial para descobrir funcionalidades avançadas como time.AfterFunc, time.Tick (simplificado) e operações com meses.

Referências

Exercícios

  1. Exercício 1: Escreva um programa que exiba a data atual no formato brasileiro (dd/mm/aaaa) e a hora no formato 24h (hh:mm:ss).
  2. ✓ Resposta:
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        agora := time.Now()
        fmt.Println(agora.Format("02/01/2006 15:04:05"))
    }
  3. Exercício 2: Crie uma função que receba uma string no formato "2006-01-02" e retorne o número de dias desde essa data até hoje (considere apenas datas no passado).
  4. ✓ Resposta:
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func diasDesde(dataStr string) int {
        data, err := time.Parse("2006-01-02", dataStr)
        if err != nil {
            panic(err)
        }
        hoje := time.Now()
        // Considera apenas a parte da data (zero hora)
        hojeZero := time.Date(hoje.Year(), hoje.Month(), hoje.Day(), 0, 0, 0, 0, time.UTC)
        dataZero := time.Date(data.Year(), data.Month(), data.Day(), 0, 0, 0, 0, time.UTC)
        diff := hojeZero.Sub(dataZero)
        return int(diff.Hours() / 24)
    }
    
    func main() {
        fmt.Println(diasDesde("2025-01-01"))
    }
  5. Exercício 3: Implemente um programa que use um timer para aguardar 2 segundos e depois imprima "Tempo esgotado!".
  6. ✓ Resposta:
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        timer := time.NewTimer(2 * time.Second)
        <-timer.C
        fmt.Println("Tempo esgotado!")
    }
  7. Exercício 4: Escreva um programa que crie um ticker que dispare a cada 1 segundo e imprima o horário atual em UTC, parando após 5 ticks.
  8. ✓ Resposta:
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ticker := time.NewTicker(1 * time.Second)
        defer ticker.Stop()
        for i := 0; i < 5; i++ {
            t := <-ticker.C
            fmt.Println(t.UTC())
        }
    }
  9. Exercício 5: Converta um horário específico (por exemplo, 2025-03-15 10:00:00) de UTC para o fuso de Nova York (America/New_York) e imprima ambos.
  10. ✓ Resposta:
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        dataUTC := time.Date(2025, 3, 15, 10, 0, 0, 0, time.UTC)
        loc, err := time.LoadLocation("America/New_York")
        if err != nil {
            panic(err)
        }
        dataNY := dataUTC.In(loc)
        fmt.Println("UTC:", dataUTC)
        fmt.Println("NY:", dataNY)
    }